Initial cursor-devbox: image, deploy manifests, and Flux SHA automation.
build-and-push / build (push) Failing after 1m42s
build-and-push / build (push) Failing after 1m42s
Owns Dockerfile/CI and node1 workload YAML; ImagePolicy elects run_number-sha tags and ImageUpdateAutomation commits them into deploy/.
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
name: build-and-push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
- 'entrypoint.sh'
|
||||
- '.gitea/workflows/build-and-push.yaml'
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: dcr.trinitysan.by
|
||||
username: ${{ secrets.DCR_USERNAME }}
|
||||
password: ${{ secrets.DCR_PASSWORD }}
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
dcr.trinitysan.by/infra/cursor-devbox:latest
|
||||
dcr.trinitysan.by/infra/cursor-devbox:${{ github.sha }}
|
||||
dcr.trinitysan.by/infra/cursor-devbox:${{ github.run_number }}-${{ github.sha }}
|
||||
cache-from: type=registry,ref=dcr.trinitysan.by/infra/cursor-devbox:build-cache
|
||||
cache-to: type=registry,ref=dcr.trinitysan.by/infra/cursor-devbox:build-cache,mode=max
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# Cursor Remote-SSH / personal devbox: OpenSSH + docker CLI (DinD sidecar at runtime).
|
||||
FROM ubuntu:latest
|
||||
|
||||
ARG KUBECTL_VERSION=v1.31.2
|
||||
ARG FLUX_VERSION=2.9.4
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
LANG=C.UTF-8
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
chromium \
|
||||
curl \
|
||||
docker.io \
|
||||
git \
|
||||
golang \
|
||||
jq \
|
||||
nodejs \
|
||||
npm \
|
||||
openssh-server \
|
||||
python3 \
|
||||
python3-venv \
|
||||
ripgrep \
|
||||
rustc \
|
||||
sudo \
|
||||
unzip \
|
||||
vim-tiny \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/run/sshd /etc/ssh/sshd_config.d /usr/share/devbox-http \
|
||||
&& echo 'devbox ok' > /usr/share/devbox-http/index.html
|
||||
|
||||
# kubectl (same pattern as services/devops-ai)
|
||||
RUN curl -fsSL --retry 5 --retry-all-errors --retry-delay 3 \
|
||||
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" \
|
||||
-o /usr/local/bin/kubectl \
|
||||
&& chmod +x /usr/local/bin/kubectl
|
||||
|
||||
# flux CLI
|
||||
RUN curl -fsSL --retry 5 --retry-all-errors --retry-delay 3 \
|
||||
"https://github.com/fluxcd/flux2/releases/download/v${FLUX_VERSION}/flux_${FLUX_VERSION}_linux_amd64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin flux \
|
||||
&& chmod +x /usr/local/bin/flux
|
||||
|
||||
# Passwordless sudo for the interactive developer user.
|
||||
# Official ubuntu images ship uid 1000 as user "ubuntu" — rename it.
|
||||
RUN usermod -l developer ubuntu \
|
||||
&& groupmod -n developer ubuntu \
|
||||
&& usermod -d /home/developer -m developer \
|
||||
&& echo 'developer ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/developer \
|
||||
&& chmod 440 /etc/sudoers.d/developer
|
||||
|
||||
# SSH hardening: keys only, no root login.
|
||||
RUN printf '%s\n' \
|
||||
'PasswordAuthentication no' \
|
||||
'KbdInteractiveAuthentication no' \
|
||||
'PermitRootLogin no' \
|
||||
'AllowUsers developer' \
|
||||
'PubkeyAuthentication yes' \
|
||||
'X11Forwarding no' \
|
||||
'AllowTcpForwarding yes' \
|
||||
'ClientAliveInterval 30' \
|
||||
'ClientAliveCountMax 3' \
|
||||
> /etc/ssh/sshd_config.d/99-devbox.conf \
|
||||
&& ssh-keygen -A
|
||||
|
||||
RUN curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash \
|
||||
&& curl -fsSL https://raw.githubusercontent.com/gastownhall/beads/main/scripts/install.sh | bash
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 22 8080
|
||||
USER root
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,43 @@
|
||||
# cursor-devbox
|
||||
|
||||
Personal Cursor Remote-SSH workspace image and GitOps for **node1-k3s**.
|
||||
|
||||
- **Image**: `dcr.trinitysan.by/infra/cursor-devbox`
|
||||
- **Deploy**: `deploy/` (Namespace, StatefulSet, ingress)
|
||||
- **Flux image automation**: `flux/` (ImageRepository / ImagePolicy / ImageUpdateAutomation)
|
||||
|
||||
Infra on node1 only keeps thin Flux glue (`GitRepository` + Kustomizations + auth SecretAdapters) in `infrastructure/node1-k3s/130-cursor-devbox.yaml`.
|
||||
|
||||
## Access
|
||||
|
||||
| Endpoint | Detail |
|
||||
|----------|--------|
|
||||
| SSH | `ssh -p 2222 developer@devbox.trinitysan.by` |
|
||||
| HTTP health | `https://devbox.trinitysan.by/` |
|
||||
|
||||
## CI
|
||||
|
||||
Gitea Actions (`.gitea/workflows/build-and-push.yaml`) on `master` (Dockerfile / entrypoint changes) and `workflow_dispatch` pushes:
|
||||
|
||||
| Tag | Purpose |
|
||||
|-----|---------|
|
||||
| `:latest` | Convenience |
|
||||
| `:<git-sha>` | Immutable full SHA |
|
||||
| `:<run_number>-<git-sha>` | Sortable; elected by Flux ImagePolicy |
|
||||
|
||||
Org secrets: `DCR_USERNAME`, `DCR_PASSWORD` (infra org).
|
||||
|
||||
## Local build
|
||||
|
||||
```bash
|
||||
docker build -t dcr.trinitysan.by/infra/cursor-devbox:local .
|
||||
```
|
||||
|
||||
## Flux update flow
|
||||
|
||||
1. CI pushes `run_number-sha` to DCR
|
||||
2. ImageRepository scans; ImagePolicy picks highest `run_number`
|
||||
3. ImageUpdateAutomation commits the new tag into `deploy/020-statefulset.yaml`
|
||||
4. Kustomization applies; StatefulSet rolls (active SSH sessions drop)
|
||||
|
||||
Requires a Gitea PAT with **read+write** on this repo (`GITEA_CURSOR_DEVBOX_GITOPS_TOKEN` on node1, see infra `060-external-secrets.yaml.template`).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Namespace, DCR pull secret, SSH allowlist, Service.
|
||||
# Reconciled by Flux Kustomization cursor-devbox (path ./deploy) on node1-k3s.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
labels:
|
||||
name: cursor-devbox
|
||||
annotations:
|
||||
linkerd.io/inject: disabled
|
||||
|
||||
---
|
||||
# Replicate federated DCR RO credentials for kubelet imagePullSecrets.
|
||||
apiVersion: secret-adapter.trinitysan.by/v1
|
||||
kind: SecretAdapter
|
||||
metadata:
|
||||
name: dcr-registry-auth-ro
|
||||
namespace: cursor-devbox
|
||||
spec:
|
||||
name: dcr-registry-auth-ro
|
||||
namespace: cursor-devbox
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
waitForSources: true
|
||||
syncInterval: 60
|
||||
stringData:
|
||||
.dockerconfigjson: ${federated-secrets.dcr-registry-auth-ro.".dockerconfigjson"}
|
||||
|
||||
---
|
||||
# Hardcoded allowlist: laptop SSH public keys (not secrets).
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cursor-devbox-authorized-keys
|
||||
namespace: cursor-devbox
|
||||
data:
|
||||
authorized_keys: |
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDbyPCd5RhlTZYCIgmrSGR6ugnwI513vzHf0qvUP3pGf8mYVd68G6CLu9OXJxFv+LKo2eT8H3ToxLpuG0iiIWz+4rJrNXTJInU2lqrcMsHhLoo1XCs7OAARYiIp7Pl4XKcLdbiXwdL2XUsHImv7cjmaD6jGJ3yR+q9t6jORQEB/h6nUc0MMkNKLIrUyJSlZtWRp22ymGnEqiwKl9doukii6QBhY1k+i9oW09ln6gbFTol9KoONkJ74b2iKw5abZwyjjXuXaE1b98/nmI30MYs8VMJC/w9pEZbmVEflb1k4+C1IE5llwmievjNWpWlZt38uzvGTLbiC9mMIewwFm4UsT5ASLpYKBdFrCaK4JIOTCdJt+SMInkzz0rArCNPMBwZIjBgCFSM33nJsX4gbf/l/rnVtLjXr86rF5s9SOSVyQmjPJoBigqyGw45H2CrB7iTqqWGCpPTnXbzzspiIQGYXLW5+5/4wARCPO0KXYcPgg4Vzh5z+dMcFKJvc8XhocZCc= aaa@LAPTOP-08PG1IL5
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK+dk9MtPgRZr7638SASpbJK7z+XkmBesXRlozTFqvT0X6gKk+75kyqNR2W+ZXDwiiWuXS2t9Dbdk9Xpuj2uWaUE0D+n3NOFEQV27Zd1ZTMeLlf3ukSLhb7un0pFHXQXvPn2Iwhh5zCyTDvIWU1rHUgBmakmxoRZ6zwJdsZX/yZHurts//7Dvb2xKKYAq4MFMAB+s6Ch80q6kCnHcByJVFyVLfOUVcNzHGUQbbj4pft2QYiTVxqTBZdvdWQvdBRkKjV2oK/mRUfGsdwmk+ldT65hdcFjbG3oG4hD3g2+AJC+/vN/xqlCeht/TU/Ei4GMF3UB3ank7z1rsGbU1T5lDoFWBWMg+tgw7ZdbYD+HIi/kuQU3FKM2xekB2fgpzuu5GQynjflygBmuQQp5/HAJDoxrkt+Yn79QX6aan8xXcHnt600Bm7SWfnsCl6BpcaD/8f2Lr8CcFCN5pcG020gvLSVdok71dB27NQ/2HOax1i0WQTZVjDrL6kMwRb37njr/k= svv@LAPTOP-08PG1IL5
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: cursor-devbox
|
||||
annotations:
|
||||
service.kubernetes.io/topology-mode: Auto
|
||||
spec:
|
||||
selector:
|
||||
app: cursor-devbox
|
||||
ports:
|
||||
- name: ssh
|
||||
port: 22
|
||||
targetPort: ssh
|
||||
protocol: TCP
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,113 @@
|
||||
# StatefulSet: OpenSSH workspace + DinD sidecar.
|
||||
# Image tag is updated by Flux ImageUpdateAutomation (SHA-bearing run_number-sha tags).
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: cursor-devbox
|
||||
spec:
|
||||
serviceName: cursor-devbox
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cursor-devbox
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cursor-devbox
|
||||
annotations:
|
||||
linkerd.io/inject: disabled
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 30
|
||||
imagePullSecrets:
|
||||
- name: dcr-registry-auth-ro
|
||||
containers:
|
||||
- name: ssh
|
||||
image: dcr.trinitysan.by/infra/cursor-devbox:latest # {"$imagepolicy": "flux-system:cursor-devbox"}
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 22
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
value: tcp://127.0.0.1:2375
|
||||
- name: HOME
|
||||
value: /home/developer
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_PTRACE
|
||||
resources:
|
||||
requests:
|
||||
cpu: "750m"
|
||||
memory: 3Gi
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: 8Gi
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: ssh
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: ssh
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
volumeMounts:
|
||||
- name: home
|
||||
mountPath: /home/developer
|
||||
subPath: home
|
||||
- name: authorized-keys
|
||||
mountPath: /etc/ssh-authorized-keys
|
||||
readOnly: true
|
||||
- name: dind
|
||||
image: docker:27-dind
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --host=tcp://0.0.0.0:2375
|
||||
- --tls=false
|
||||
env:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: ""
|
||||
securityContext:
|
||||
privileged: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 2375
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
volumeMounts:
|
||||
- name: home
|
||||
mountPath: /var/lib/docker
|
||||
subPath: docker
|
||||
- name: dind-run
|
||||
mountPath: /var/run
|
||||
volumes:
|
||||
- name: authorized-keys
|
||||
configMap:
|
||||
name: cursor-devbox-authorized-keys
|
||||
- name: dind-run
|
||||
emptyDir: {}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: home
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 30Gi
|
||||
@@ -0,0 +1,33 @@
|
||||
# Traefik TCP SSH + TrinityIngress HTTP health.
|
||||
|
||||
---
|
||||
# Traefik TCP :2222 → OpenSSH in the pod (plain SSH; HostSNI catch-all).
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: cursor-devbox-ssh
|
||||
namespace: cursor-devbox
|
||||
spec:
|
||||
entryPoints:
|
||||
- ssh
|
||||
routes:
|
||||
- match: HostSNI(`*`)
|
||||
services:
|
||||
- name: cursor-devbox
|
||||
port: 22
|
||||
|
||||
---
|
||||
# DNS A record + HTTPS front for the tiny HTTP health page; SSH uses Traefik TCP :2222.
|
||||
apiVersion: ingress.trinitysan.by/v1
|
||||
kind: TrinityIngress
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: cursor-devbox
|
||||
spec:
|
||||
hostname: devbox.trinitysan.by
|
||||
targetTraefikIP: "91.210.106.53"
|
||||
serviceRef:
|
||||
namespace: cursor-devbox
|
||||
name: cursor-devbox
|
||||
port: 8080
|
||||
certificateReclaimPolicy: Retain
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
HOME_DIR=/home/developer
|
||||
HOST_KEY_DIR="${HOME_DIR}/.ssh-host"
|
||||
AUTH_KEYS_SRC=/etc/ssh-authorized-keys/authorized_keys
|
||||
|
||||
mkdir -p "${HOME_DIR}/.ssh" "${HOME_DIR}/workspace" "${HOST_KEY_DIR}"
|
||||
|
||||
# Persist host keys on the PVC so SSH fingerprints stay stable across restarts.
|
||||
if [ ! -f "${HOST_KEY_DIR}/ssh_host_ed25519_key" ]; then
|
||||
ssh-keygen -t ed25519 -f "${HOST_KEY_DIR}/ssh_host_ed25519_key" -N "" -q
|
||||
fi
|
||||
if [ ! -f "${HOST_KEY_DIR}/ssh_host_rsa_key" ]; then
|
||||
ssh-keygen -t rsa -b 4096 -f "${HOST_KEY_DIR}/ssh_host_rsa_key" -N "" -q
|
||||
fi
|
||||
|
||||
cat > /etc/ssh/sshd_config.d/98-hostkeys.conf <<EOF
|
||||
HostKey ${HOST_KEY_DIR}/ssh_host_ed25519_key
|
||||
HostKey ${HOST_KEY_DIR}/ssh_host_rsa_key
|
||||
EOF
|
||||
|
||||
if [ -f "${AUTH_KEYS_SRC}" ]; then
|
||||
install -m 600 -o developer -g developer "${AUTH_KEYS_SRC}" "${HOME_DIR}/.ssh/authorized_keys"
|
||||
fi
|
||||
# sshd StrictModes rejects auth if $HOME is group/world-writable.
|
||||
chmod 755 "${HOME_DIR}"
|
||||
chmod 700 "${HOME_DIR}/.ssh"
|
||||
chown -R developer:developer "${HOME_DIR}"
|
||||
|
||||
# DinD sidecar listens on TCP; ensure SSH login shells see DOCKER_HOST.
|
||||
grep -q '^DOCKER_HOST=' /etc/environment 2>/dev/null \
|
||||
|| echo 'DOCKER_HOST=tcp://127.0.0.1:2375' >> /etc/environment
|
||||
if [ ! -f "${HOME_DIR}/.profile" ] || ! grep -q 'DOCKER_HOST' "${HOME_DIR}/.profile" 2>/dev/null; then
|
||||
printf '\nexport DOCKER_HOST=tcp://127.0.0.1:2375\n' >> "${HOME_DIR}/.profile"
|
||||
chown developer:developer "${HOME_DIR}/.profile"
|
||||
fi
|
||||
|
||||
# Tiny HTTP for TrinityIngress / health (Traefik terminates TLS).
|
||||
python3 -m http.server 8080 --bind 0.0.0.0 --directory /usr/share/devbox-http >/tmp/http-8080.log 2>&1 &
|
||||
|
||||
exec /usr/sbin/sshd -D -e
|
||||
@@ -0,0 +1,58 @@
|
||||
# Flux image automation for cursor-devbox (applied into flux-system on node1).
|
||||
# GitRepository cursor-devbox is defined in infra glue (130-cursor-devbox.yaml).
|
||||
# CI tags: latest, <sha>, <run_number>-<sha> — policy elects the last form.
|
||||
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: dcr.trinitysan.by/infra/cursor-devbox
|
||||
interval: 1m0s
|
||||
secretRef:
|
||||
name: dcr-registry-auth-ro
|
||||
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: cursor-devbox
|
||||
filterTags:
|
||||
pattern: '^(?P<n>[0-9]+)-[0-9a-f]{40}$'
|
||||
extract: '$n'
|
||||
policy:
|
||||
numerical:
|
||||
order: asc
|
||||
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: cursor-devbox
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 5m0s
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: cursor-devbox
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: master
|
||||
commit:
|
||||
author:
|
||||
email: fluxcdbot@users.noreply.gitea.trinitysan.by
|
||||
name: fluxcdbot
|
||||
messageTemplate: |
|
||||
chore(deploy): update cursor-devbox image to {{range .Updated.Images}}{{.}}{{end}}
|
||||
push:
|
||||
branch: master
|
||||
update:
|
||||
path: ./deploy
|
||||
strategy: Setters
|
||||
Reference in New Issue
Block a user