docs: plan — Gitea replaces GitLab (Phase 2B); SSH on both :22 and :2222

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-21 15:46:34 +02:00
parent 80f3fbecef
commit 08cee0de0d
2 changed files with 481 additions and 3 deletions

View File

@@ -0,0 +1,475 @@
# Gitea Replaces GitLab + Cockpit Registry to Scaleway — Implementation Plan (Phase 2B)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans (INLINE, with checkpoints).
> **Do NOT run subagent-driven** — this touches live git access, the container registry, and the cockpit
> deploy; each cutover step needs a human-confirmed gate. Steps use checkbox (`- [ ]`) syntax.
**Goal:** Stand up Gitea (chart + existing Postgres) as the git host behind the unchanged `git.fxhnt.ai`,
migrate both repos, and move the cockpit image to Scaleway Container Registry — so GitLab can be removed in 2C.
**Architecture:** Gitea comes up internal-only; repos migrate + validate over a port-forward; the cockpit
image moves to Scaleway registry and is build+rollout tested; THEN `git.fxhnt.ai` (HTTPS + SSH `:2222`)
cuts over to Gitea in one switch; finally a Gitea webhook → Argo Events auto-deploys the cockpit. GitLab
stays running (unaddressed) as the rollback net until 2C.
**Tech Stack:** Gitea Helm chart (`gitea-charts/gitea`), existing in-cluster PostgreSQL, nginx+socat
Tailscale proxy, Scaleway Container Registry, Argo Events, kaniko.
**Spec:** `docs/superpowers/specs/2026-06-21-gitea-replace-gitlab-design.md`
**TWO REPOS:** infra changes → `foxhunt` (`~/Work/foxhunt`); cockpit build/deploy → `fxhnt`
(`~/Work/fxhnt`). Each gets its own branch + commits.
---
## Shared env
```bash
export SCW_ACCESS_KEY=$(kubectl get secret scaleway-credentials -n foxhunt -o jsonpath='{.data.access-key}'|base64 -d)
export SCW_SECRET_KEY=$(kubectl get secret scaleway-credentials -n foxhunt -o jsonpath='{.data.secret-key}'|base64 -d)
export SCW_DEFAULT_PROJECT_ID=$(kubectl get secret scaleway-credentials -n foxhunt -o jsonpath='{.data.project-id}'|base64 -d)
export SCW_DEFAULT_REGION=fr-par SCW_DEFAULT_ZONE=fr-par-2
```
---
## Task 0: Branches
- [ ] **Step 1**
```bash
cd /home/jgrusewski/Work/foxhunt && git checkout -b chore/gitea-replace-gitlab
cd /home/jgrusewski/Work/fxhnt && git checkout -b chore/gitea-cockpit-registry
```
---
## Task 1: Postgres `gitea` DB + secrets
**Files:** none committed (cluster secrets). Record secret names only.
- [ ] **Step 1: Read existing postgres superuser creds**
```bash
PGU=$(kubectl get deploy postgres -n foxhunt -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="POSTGRES_USER")].value}')
PGPOD=$(kubectl get pod -n foxhunt -l app=postgres -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || kubectl get pod -n foxhunt | grep '^postgres-' | grep -v backup | awk '{print $1}' | head -1)
echo "postgres user=$PGU pod=$PGPOD"
```
Expected: a username + a running pod name. (If `POSTGRES_USER` is via secretRef not value, read it from the referenced secret instead.)
- [ ] **Step 2: Generate a Gitea DB password + create role/db**
```bash
GITEA_DB_PW=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
kubectl exec -n foxhunt "$PGPOD" -- psql -U "$PGU" -v ON_ERROR_STOP=1 -c \
"CREATE ROLE gitea LOGIN PASSWORD '$GITEA_DB_PW';" -c \
"CREATE DATABASE gitea OWNER gitea ENCODING 'UTF8';" 2>&1 | grep -iE "CREATE|already exists|Error"
echo "$GITEA_DB_PW" # capture for the next step
```
Expected: `CREATE ROLE` + `CREATE DATABASE` (or `already exists` if re-run).
- [ ] **Step 3: Create the k8s secrets Gitea needs** (DB creds + admin)
```bash
GITEA_ADMIN_PW=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
kubectl create secret generic gitea-db -n foxhunt \
--from-literal=password="$GITEA_DB_PW" --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic gitea-admin -n foxhunt \
--from-literal=username=gitadmin --from-literal=password="$GITEA_ADMIN_PW" \
--from-literal=email=jeroen@bizworx.nl --dry-run=client -o yaml | kubectl apply -f -
echo "ADMIN PW (save in your password manager): $GITEA_ADMIN_PW"
```
Expected: both secrets `created`/`configured`. **Save the admin password** — needed for Gitea login.
---
## Task 2: Gitea Helm values + install (internal-only)
**Files:** Create `infra/k8s/gitea/values.yaml` (foxhunt repo).
- [ ] **Step 1: Add the Gitea Helm repo**
```bash
helm repo add gitea-charts https://dl.gitea.com/charts/ && helm repo update gitea-charts
helm search repo gitea-charts/gitea --versions | head -3
```
Expected: lists chart versions (use the latest stable in Step 3).
- [ ] **Step 2: Write `infra/k8s/gitea/values.yaml`**
```yaml
# Gitea — lightweight git host replacing GitLab (Phase 2B). External Postgres (existing in-cluster
# `postgres`), no bundled DB/redis/memcached, Actions off (Argo does CI). Internal-only until cutover.
replicaCount: 1
image:
rootless: true
# Disable all bundled subcharts — reuse the existing in-cluster postgres
postgresql:
enabled: false
postgresql-ha:
enabled: false
redis-cluster:
enabled: false
redis:
enabled: false
persistence:
enabled: true
size: 5Gi
storageClass: sbs-default-retain
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: "1", memory: 512Mi }
service:
http: { type: ClusterIP, port: 3000 }
ssh: { type: ClusterIP, port: 22 }
actions:
enabled: false
gitea:
admin:
existingSecret: gitea-admin
config:
server:
ROOT_URL: https://git.fxhnt.ai/
DOMAIN: git.fxhnt.ai
SSH_DOMAIN: git.fxhnt.ai
SSH_PORT: "22" # advertise clean git@git.fxhnt.ai in NEW clone URLs; legacy :2222 still
# works via the proxy (Task 5). Port 22 is free — nothing host-SSHes the git node.
DISABLE_SSH: "false"
database:
DB_TYPE: postgres
HOST: postgres.foxhunt.svc.cluster.local:5432
NAME: gitea
USER: gitea
service:
DISABLE_REGISTRATION: "true"
cache:
ADAPTER: memory
additionalConfigFromEnvs:
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef: { name: gitea-db, key: password }
```
- [ ] **Step 3: Install (internal-only — no public hostname yet)**
```bash
cd /home/jgrusewski/Work/foxhunt
helm install gitea gitea-charts/gitea -n foxhunt -f infra/k8s/gitea/values.yaml --wait --timeout 5m 2>&1 | tail -5
```
Expected: `STATUS: deployed`. (If `--wait` times out, continue; verify pod in Step 4.)
- [ ] **Step 4: Verify Gitea is healthy**
```bash
kubectl get pods -n foxhunt | grep gitea
kubectl run gitea-probe --rm -i --restart=Never -n foxhunt --image=curlimages/curl -- \
curl -s http://gitea-http.foxhunt.svc.cluster.local:3000/api/healthz
```
Expected: a JSON health blob with `"status": "pass"`. Pod `Running`.
- [ ] **Step 5: Commit values (foxhunt repo)**
```bash
cd /home/jgrusewski/Work/foxhunt && git add infra/k8s/gitea/values.yaml
git commit -m "feat(infra): Gitea Helm values (external postgres, internal-only) — Phase 2B"
```
---
## Task 3: Migrate both repos via port-forward + validate
**Files:** none committed (git data operations).
- [ ] **Step 1: Port-forward Gitea + create the repos via API**
```bash
kubectl port-forward -n foxhunt svc/gitea-http 3000:3000 >/tmp/gitea-pf.log 2>&1 & echo $! >/tmp/gitea-pf.pid
sleep 3
GA=gitadmin; GP=$(kubectl get secret gitea-admin -n foxhunt -o jsonpath='{.data.password}'|base64 -d)
for r in fxhnt foxhunt; do
curl -s -u "$GA:$GP" -X POST http://localhost:3000/api/v1/user/repos \
-H 'Content-Type: application/json' -d "{\"name\":\"$r\",\"private\":true}" -o /dev/null -w "$r: %{http_code}\n"
done
```
Expected: `fxhnt: 201` and `foxhunt: 201`.
- [ ] **Step 2: Mirror-push both repos from local clones**
```bash
GA=gitadmin; GP=$(kubectl get secret gitea-admin -n foxhunt -o jsonpath='{.data.password}'|base64 -d)
( cd /home/jgrusewski/Work/fxhnt && git push --mirror "http://$GA:$GP@localhost:3000/$GA/fxhnt.git" 2>&1 | tail -2 )
( cd /home/jgrusewski/Work/foxhunt && git push --mirror "http://$GA:$GP@localhost:3000/$GA/foxhunt.git" 2>&1 | tail -2 )
```
Expected: both report refs pushed (no error). foxhunt is 876 MB — may take a minute.
- [ ] **Step 3: VALIDATE — clone back + diff top SHAs vs GitLab**
```bash
GA=gitadmin; GP=$(kubectl get secret gitea-admin -n foxhunt -o jsonpath='{.data.password}'|base64 -d)
for r in fxhnt foxhunt; do
gitea_sha=$(git ls-remote "http://$GA:$GP@localhost:3000/$GA/$r.git" HEAD | awk '{print $1}')
gitlab_sha=$(cd /home/jgrusewski/Work/$r && git ls-remote origin HEAD | awk '{print $1}')
[ "$gitea_sha" = "$gitlab_sha" ] && echo "$r: MATCH ($gitea_sha)" || echo "$r: MISMATCH gitea=$gitea_sha gitlab=$gitlab_sha"
done
```
Expected: `fxhnt: MATCH` and `foxhunt: MATCH`. **STOP if any MISMATCH.**
- [ ] **Step 4: Mark foxhunt archived (read-only)**
```bash
GA=gitadmin; GP=$(kubectl get secret gitea-admin -n foxhunt -o jsonpath='{.data.password}'|base64 -d)
curl -s -u "$GA:$GP" -X PATCH "http://localhost:3000/api/v1/repos/$GA/foxhunt" \
-H 'Content-Type: application/json' -d '{"archived":true}' -o /dev/null -w "archive foxhunt: %{http_code}\n"
kill $(cat /tmp/gitea-pf.pid) 2>/dev/null
```
Expected: `archive foxhunt: 200`.
---
## Task 4: Move cockpit image to Scaleway Container Registry
**Files:** Modify `~/Work/fxhnt/infra/argo/cockpit-build-deploy.yaml`,
`~/Work/fxhnt/infra/k8s/orchestration/dagster.yaml` (+ dashboard manifest if separate).
- [ ] **Step 1: Create the Scaleway registry pull/push secret**
```bash
kubectl create secret docker-registry scw-registry -n foxhunt \
--docker-server=rg.fr-par.scw.cloud \
--docker-username=nologin --docker-password="$SCW_SECRET_KEY" \
--dry-run=client -o yaml | kubectl apply -f -
```
Expected: secret `created`/`configured`. (Scaleway registry auth: username `nologin`, password = a
Scaleway secret key.)
- [ ] **Step 2: Repoint kaniko build → Scaleway registry**
In `~/Work/fxhnt/infra/argo/cockpit-build-deploy.yaml`, change the kaniko args:
```
--destination=rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit:latest \
--cache-repo=rg.fr-par.scw.cloud/bizworx/cache \
```
Remove the `--insecure-registry=…gitlab-registry…:5000` and `--skip-tls-verify-registry=…` lines
(Scaleway is TLS). Mount the `scw-registry` dockerconfig for kaniko (replace the `gitlab-registry`
dockerconfig volume): set the kaniko `DOCKER_CONFIG`/volume to the `scw-registry` secret at
`/kaniko/.docker/config.json`.
- [ ] **Step 3: Repoint the deploy image refs**
In `~/Work/fxhnt/infra/k8s/orchestration/dagster.yaml` (and the dashboard manifest), change every
`image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/fxhnt-cockpit:latest`
`image: rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit:latest`, and add under each pod spec:
```yaml
imagePullSecrets:
- name: scw-registry
```
- [ ] **Step 4: Run one cockpit build → Scaleway + verify the image exists**
```bash
cd /home/jgrusewski/Work/fxhnt && ./scripts/argo-deploy-cockpit.sh 2>&1 | tail -8
# verify the pushed image
scw registry image list namespace-id=6561a90a-f4ba-4f44-b43f-be6e27cbdfca 2>/dev/null | grep -i fxhnt-cockpit || echo "(verify via Scaleway console)"
```
Expected: workflow `Succeeded`; `fxhnt-cockpit` image listed in the `bizworx` namespace.
- [ ] **Step 5: Verify a rollout pulls from Scaleway**
```bash
kubectl rollout restart deploy/dagster deploy/fxhnt-dashboard -n foxhunt 2>/dev/null
kubectl rollout status deploy/dagster -n foxhunt --timeout=180s
kubectl get pods -n foxhunt -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | grep -i "rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit" | head
```
Expected: rollout succeeds; pods now reference the `rg.fr-par.scw.cloud/bizworx/...` image (no `ImagePullBackOff`).
- [ ] **Step 6: Commit (fxhnt repo)**
```bash
cd /home/jgrusewski/Work/fxhnt
git add infra/argo/cockpit-build-deploy.yaml infra/k8s/orchestration/dagster.yaml
git commit -m "feat(infra): cockpit image -> Scaleway Container Registry (Phase 2B)"
```
---
## Task 5: Cut `git.fxhnt.ai` + SSH `:2222` over to Gitea
**Files:** Modify `infra/k8s/gitlab/tailscale-proxy.yaml` (foxhunt repo). **CHECKPOINT before applying.**
- [ ] **Step 1: Repoint nginx `git.fxhnt.ai:443` → Gitea**
In `infra/k8s/gitlab/tailscale-proxy.yaml`, in the `server_name git.fxhnt.ai;` (`listen 443`) block,
change:
```
proxy_pass http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181;
```
to:
```
proxy_pass http://gitea-http.foxhunt.svc.cluster.local:3000;
```
Delete the entire `listen 5050 ssl; server_name git.fxhnt.ai;` registry block (Scaleway registry is
external — not proxied).
- [ ] **Step 2: Repoint the socat SSH proxy → Gitea SSH, on BOTH `:22` and `:2222`**
Nothing ever SSHes into the git node itself, so port `22` is free for git-SSH. Expose Gitea SSH on both
the standard `:22` (clean `git@git.fxhnt.ai` URLs) and the legacy `:2222` (existing remotes keep working —
no churn). In the `ssh-proxy` container args, change the existing `:2222` target to Gitea:
```
- "TCP-LISTEN:2222,fork,reuseaddr,sndbuf=1048576,rcvbuf=1048576"
- "TCP:gitea-ssh.foxhunt.svc.cluster.local:22,sndbuf=1048576,rcvbuf=1048576"
```
Then add a SECOND socat container in the same pod for `:22` (duplicate the `ssh-proxy` container, rename
`ssh-proxy-22`, listen on `22`, `containerPort: 22`):
```yaml
- name: ssh-proxy-22
image: alpine/socat:latest
args:
- "TCP-LISTEN:22,fork,reuseaddr,sndbuf=1048576,rcvbuf=1048576"
- "TCP:gitea-ssh.foxhunt.svc.cluster.local:22,sndbuf=1048576,rcvbuf=1048576"
ports:
- containerPort: 22
```
Ensure the Tailscale node/proxy advertises both ports `22` and `2222` (the tailnet sidecar's exposed
ports / `serve` config — add `22` alongside the existing `2222`).
- [ ] **Step 3: Apply + restart the proxy**
```bash
cd /home/jgrusewski/Work/foxhunt
kubectl apply -f infra/k8s/gitlab/tailscale-proxy.yaml
kubectl rollout restart deploy/tailscale-gitlab-proxy -n foxhunt
kubectl rollout status deploy/tailscale-gitlab-proxy -n foxhunt --timeout=120s
```
Expected: rollout complete.
- [ ] **Step 4: GATE — verify `git.fxhnt.ai` serves Gitea**
```bash
curl -sk https://git.fxhnt.ai/api/healthz | head -c 200; echo
echo "--- SSH on :2222 (legacy remotes) ---"; git ls-remote ssh://git@git.fxhnt.ai:2222/gitadmin/fxhnt.git HEAD 2>&1 | head -1
echo "--- SSH on :22 (clean URLs) ---"; git ls-remote ssh://git@git.fxhnt.ai/gitadmin/fxhnt.git HEAD 2>&1 | head -1
```
Expected: Gitea healthz JSON; BOTH ssh ls-remotes (`:2222` and `:22`) return the HEAD sha (proves Gitea
web + SSH on both ports on the canonical name). **STOP + rollback (revert this file, re-apply) if any fail.**
- [ ] **Step 5: Commit (foxhunt repo)**
```bash
cd /home/jgrusewski/Work/foxhunt && git add infra/k8s/gitlab/tailscale-proxy.yaml
git commit -m "feat(infra): cut git.fxhnt.ai + SSH :2222 over to Gitea; drop :5050 registry (Phase 2B)"
```
---
## Task 6: Wire Gitea push webhook → Argo Events auto-deploy
**Files:** Create `infra/k8s/argo/events/gitea-push-eventsource.yaml` +
`infra/k8s/argo/events/gitea-deploy-sensor.yaml` (foxhunt repo).
- [ ] **Step 1: Create the webhook eventsource**
Create `infra/k8s/argo/events/gitea-push-eventsource.yaml`:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: gitea-push
namespace: foxhunt
spec:
service:
ports:
- port: 12000
targetPort: 12000
webhook:
fxhnt-push:
port: "12000"
endpoint: /push
method: POST
```
- [ ] **Step 2: Create the sensor that submits the cockpit workflow**
Create `infra/k8s/argo/events/gitea-deploy-sensor.yaml`:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: gitea-deploy
namespace: foxhunt
spec:
dependencies:
- name: push
eventSourceName: gitea-push
eventName: fxhnt-push
filters:
data:
- path: body.ref
type: string
value: ["refs/heads/main", "refs/heads/master"]
triggers:
- template:
name: submit-cockpit
argoWorkflow:
operation: submit
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: fxhnt-cockpit-
namespace: foxhunt
spec:
workflowTemplateRef:
name: fxhnt-cockpit
```
- [ ] **Step 3: Apply + register the webhook in Gitea**
```bash
cd /home/jgrusewski/Work/foxhunt
kubectl apply -f infra/k8s/argo/events/gitea-push-eventsource.yaml -f infra/k8s/argo/events/gitea-deploy-sensor.yaml
kubectl get pods -n foxhunt | grep -iE "gitea-push|gitea-deploy"
# register webhook (in-cluster URL) on the fxhnt repo
GA=gitadmin; GP=$(kubectl get secret gitea-admin -n foxhunt -o jsonpath='{.data.password}'|base64 -d)
kubectl port-forward -n foxhunt svc/gitea-http 3000:3000 >/tmp/gitea-pf.log 2>&1 & echo $! >/tmp/gitea-pf.pid; sleep 3
curl -s -u "$GA:$GP" -X POST "http://localhost:3000/api/v1/repos/$GA/fxhnt/hooks" -H 'Content-Type: application/json' -d '{
"type":"gitea","active":true,"events":["push"],
"config":{"url":"http://gitea-push-eventsource-svc.foxhunt.svc.cluster.local:12000/push","content_type":"json"}
}' -o /dev/null -w "hook: %{http_code}\n"
kill $(cat /tmp/gitea-pf.pid) 2>/dev/null
```
Expected: eventsource + sensor pods `Running`; `hook: 201`.
- [ ] **Step 4: GATE — test push triggers a deploy**
```bash
cd /home/jgrusewski/Work/fxhnt
git commit --allow-empty -m "test: trigger Gitea->Argo cockpit deploy"
git push origin main # origin already = git.fxhnt.ai (now Gitea)
sleep 10
kubectl get wf -n foxhunt --sort-by=.metadata.creationTimestamp 2>/dev/null | grep fxhnt-cockpit | tail -2
```
Expected: a new `fxhnt-cockpit-*` workflow appears (triggered by the push). **If none fires**, check the
eventsource pod logs; fallback is the manual `./scripts/argo-deploy-cockpit.sh` (still works).
- [ ] **Step 5: Commit (foxhunt repo)**
```bash
cd /home/jgrusewski/Work/foxhunt && git add infra/k8s/argo/events/gitea-push-eventsource.yaml infra/k8s/argo/events/gitea-deploy-sensor.yaml
git commit -m "feat(infra): Gitea push webhook -> Argo Events cockpit auto-deploy (Phase 2B)"
```
---
## Task 7: Final verification
- [ ] **Step 1: Footprint + health summary**
```bash
echo "=== Gitea (new) ==="; kubectl top pods -n foxhunt 2>/dev/null | grep -i gitea
echo "=== cockpit healthy on Scaleway image ==="; curl -sk -o /dev/null -w "dashboard.fxhnt.ai: %{http_code}\n" https://dashboard.fxhnt.ai
echo "=== git.fxhnt.ai serves Gitea ==="; curl -sk https://git.fxhnt.ai/api/healthz | head -c 80; echo
echo "=== GitLab still running (fallback for 2C) ==="; kubectl get pods -n foxhunt | grep -c gitlab | xargs echo "gitlab pods:"
```
Expected: Gitea ≤~300 MiB; dashboard 200; git.fxhnt.ai healthz pass; GitLab pods still present.
---
## Rollback
- **Cutover (Task 5):** `git checkout infra/k8s/gitlab/tailscale-proxy.yaml` + `kubectl apply` + rollout
restart → `git.fxhnt.ai`/SSH back to GitLab.
- **Registry (Task 4):** revert the fxhnt manifests + rollout restart → pods pull the GitLab image again
(still present until 2C).
- **Gitea:** `helm uninstall gitea -n foxhunt`; drop the `gitea` DB. GitLab was never touched.
---
## Acceptance criteria (from spec)
- Gitea running on existing postgres, healthz pass, ≤~300 MiB. ✅ Task 2, Task 7
- Both repos in Gitea, SHAs match GitLab, foxhunt archived. ✅ Task 3
- Cockpit builds to + deploys from Scaleway registry; dashboard 200. ✅ Task 4, Task 7
- `git.fxhnt.ai` (HTTPS + SSH :2222) serves Gitea; remotes unchanged. ✅ Task 5
- Push to fxhnt main auto-triggers a cockpit deploy. ✅ Task 6
- GitLab still running as fallback. ✅ Task 7
```

View File

@@ -23,8 +23,10 @@ breaking the live cockpit deploy.
- **Repos:** migrate **both**`fxhnt` (active) and `foxhunt` (876 MB, flagged **archived/read-only**) —
via `git push --mirror` from existing local clones.
- **Canonical hostname stays `git.fxhnt.ai`** (no permanent `gitea.fxhnt.ai`, no remote-URL churn).
Gitea comes up internal-only; validate over a port-forward; then cut `git.fxhnt.ai` (HTTPS) + SSH
`:2222` over to Gitea in one switch. GitLab keeps running (unaddressed) as the 2C-removal fallback.
Gitea comes up internal-only; validate over a port-forward; then cut `git.fxhnt.ai` (HTTPS) + SSH over
to Gitea in one switch. **SSH exposed on BOTH `:22` and `:2222`** — nothing host-SSHes the git node so
`:22` is free; Gitea advertises the clean `git@git.fxhnt.ai` (`:22`) in new clone URLs while existing
`:2222` remotes keep working (no churn). GitLab keeps running (unaddressed) as the 2C-removal fallback.
- **Container registry:** cockpit image → **Scaleway Container Registry** (`rg.fr-par.scw.cloud/bizworx/`,
namespace exists). Retire the in-cluster GitLab registry. External/managed, no registry pod.
- **Argo integration:** Gitea push webhook → Argo Events webhook eventsource → sensor → submit
@@ -42,7 +44,8 @@ breaking the live cockpit deploy.
3. **Exposure:**
- HTTPS: extend the nginx `*.fxhnt.ai` proxy (`infra/k8s/gitlab/tailscale-proxy.yaml`) — at cutover,
repoint the `git.fxhnt.ai` server block `proxy_pass` → `gitea-http.foxhunt.svc:3000`.
- SSH: repoint the Tailscale-exposed `:2222` → Gitea SSH service at cutover.
- SSH: expose Gitea SSH on BOTH `:22` and `:2222` via the Tailscale proxy (two socat listeners) at
cutover; advertise `:22` in clone URLs, keep `:2222` for existing remotes.
- Registry `:5050` server block: removed (Scaleway registry is external, not proxied here).
4. **Registry migration:**
- `fxhnt/infra/argo/cockpit-build-deploy.yaml`: kaniko `--destination` + `--cache-repo` →