Merge: Gitea replaces GitLab + cockpit registry to Scaleway (Phase 2B)

Gitea (chart + existing postgres) is the git host behind git.fxhnt.ai (tailnet proxy,
SSH :22); both repos migrated (foxhunt archived); cockpit image -> Scaleway Container
Registry; build clones Gitea; Gitea webhook -> Argo auto-deploy. GitLab now idle (removed in 2C).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-21 17:40:05 +02:00
10 changed files with 292 additions and 36 deletions

View File

@@ -130,8 +130,8 @@ gitea:
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.
SSH_PORT: "22" # clean git@git.fxhnt.ai clone URLs. Port 22 is free (nothing host-SSHes
# the git node); :2222 retired, local remotes updated in Task 5 Step 2b.
DISABLE_SSH: "false"
database:
DB_TYPE: postgres
@@ -297,28 +297,26 @@ to:
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`**
- [ ] **Step 2: Repoint the socat SSH proxy to Gitea SSH on `:22` (drop `: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:
Nothing host-SSHes the git node, so use the standard port `22` and retire `:2222` entirely (clean
`git@git.fxhnt.ai` URLs). In the `ssh-proxy` container args, change the listen port to `22` and the 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`).
Update the container `ports:` entry from `containerPort: 2222``containerPort: 22`. Ensure the Tailscale
node/proxy advertises port `22` (update the tailnet sidecar's exposed ports / `serve` config: `22`,
replacing `2222`).
- [ ] **Step 2b: Update local git remotes from `:2222` → default (port 22)**
```bash
cd /home/jgrusewski/Work/foxhunt && git remote set-url origin ssh://git@git.fxhnt.ai/gitadmin/foxhunt.git
cd /home/jgrusewski/Work/fxhnt && git remote set-url origin ssh://git@git.fxhnt.ai/gitadmin/fxhnt.git
cd /home/jgrusewski/Work/foxhunt && git remote -v | grep fetch
```
Expected: remotes now show `ssh://git@git.fxhnt.ai/...` (no `:2222`).
- [ ] **Step 3: Apply + restart the proxy**
```bash
@@ -332,11 +330,10 @@ 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
echo "--- SSH on :22 (git@git.fxhnt.ai) ---"; 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.**
Expected: Gitea healthz JSON; the `:22` ssh ls-remote returns the HEAD sha (proves Gitea web + SSH on the
canonical name). **STOP + rollback (revert this file, re-apply) if either fails.**
- [ ] **Step 5: Commit (foxhunt repo)**
```bash

View File

@@ -24,9 +24,9 @@ breaking the live cockpit deploy.
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 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.
to Gitea in one switch. **SSH on `:22`** (nothing host-SSHes the git node, so the standard port is free);
clone URLs become the clean `git@git.fxhnt.ai` and `:2222` is retired (local remotes updated once).
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
@@ -44,8 +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: 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.
- SSH: expose Gitea SSH on `:22` via the Tailscale proxy (socat) at cutover; retire `:2222`; update the
two local remotes to `ssh://git@git.fxhnt.ai/...`.
- 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` →

View File

@@ -0,0 +1,56 @@
# On push to fxhnt main/master in Gitea, submit the fxhnt-cockpit build+deploy workflow (Phase 2B).
# Gitea push payload: { "ref": "refs/heads/main", "after": "<new-sha>", ... }
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: gitea-deploy
namespace: foxhunt
labels:
app.kubernetes.io/name: gitea-deploy
app.kubernetes.io/part-of: foxhunt
spec:
template:
# pod intentionally NOT part-of:foxhunt (avoids default-deny-all; matches working sensors)
serviceAccountName: argo-workflow
nodeSelector:
k8s.scaleway.com/pool-name: platform
dependencies:
- name: gitea-push-dep
eventSourceName: gitea-push
eventName: push
filters:
data:
- path: body.ref
type: string
value:
- "refs/heads/main"
- "refs/heads/master"
triggers:
- template:
name: deploy-cockpit
conditions: gitea-push-dep
argoWorkflow:
operation: submit
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: fxhnt-cockpit-
namespace: foxhunt
spec:
serviceAccountName: argo-workflow
podGC:
strategy: OnPodCompletion
ttlStrategy:
secondsAfterCompletion: 3600
workflowTemplateRef:
name: fxhnt-cockpit
arguments:
parameters:
- name: commit-sha
parameters:
- src:
dependencyName: gitea-push-dep
dataKey: body.after
dest: spec.arguments.parameters.0.value

View File

@@ -0,0 +1,28 @@
# Gitea push webhook → Argo Events. Replaces the old gitlab-push eventsource (Phase 2B).
# Gitea posts to gitea-push-eventsource-svc:12000/push on push to the fxhnt repo.
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: gitea-push
namespace: foxhunt
labels:
app.kubernetes.io/name: gitea-push
app.kubernetes.io/part-of: foxhunt
spec:
template:
# NOTE: pod is intentionally NOT part-of:foxhunt — that label triggers default-deny-all. The working
# workflow-trigger/gitlab-push eventsource pods have no part-of, so they stay default-allow.
metadata:
labels:
app.kubernetes.io/name: gitea-push
nodeSelector:
k8s.scaleway.com/pool-name: platform
service:
ports:
- port: 12000
targetPort: 12000
webhook:
push:
port: "12000"
endpoint: /push
method: POST

View File

@@ -0,0 +1,40 @@
# The gitea chart's gitea-http/gitea-ssh services are HEADLESS (clusterIP: None), so they resolve to the
# pod IP (100.64.x.x). That range OVERLAPS the Tailscale CGNAT range (100.64.0.0/10), so the tailscale
# sidecar in the tailscale-gitlab-proxy pod swallows traffic to the pod IP. These normal ClusterIP services
# give Gitea a service IP in the service CIDR (10.32.x.x, outside the tailscale range) — the proxy nginx +
# socat target these instead. (GitLab worked because its webservice svc was a normal ClusterIP.)
apiVersion: v1
kind: Service
metadata:
name: gitea-web
namespace: foxhunt
labels:
app.kubernetes.io/name: gitea
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitea
app.kubernetes.io/instance: gitea
ports:
- name: http
port: 3000
targetPort: 3000
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: gitea-sshd
namespace: foxhunt
labels:
app.kubernetes.io/name: gitea
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitea
app.kubernetes.io/instance: gitea
ports:
- name: ssh
port: 22
targetPort: 2222 # rootless gitea listens on 2222 internally (can't bind privileged 22)
protocol: TCP

View File

@@ -0,0 +1,58 @@
# Gitea NetworkPolicy (foxhunt ns has default-deny-all). Egress: postgres + DNS + in-cluster webhook
# target + general HTTPS. Ingress: from platform/argo pods + the tailscale proxy → :3000 (web) and :22 (ssh).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gitea
namespace: foxhunt
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: gitea
policyTypes:
- Ingress
- Egress
ingress:
# web (3000) + ssh (22) from platform + argo-workflow pods (cockpit build clones over HTTP)
- from:
- podSelector:
matchExpressions:
- key: app.kubernetes.io/part-of
operator: In
values: ["foxhunt", "argo-workflows"]
ports:
- { port: 3000, protocol: TCP }
- { port: 2222, protocol: TCP }
# web + ssh from the tailscale proxy (nginx + socat) that fronts git.fxhnt.ai
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: tailscale-gitlab-proxy
ports:
- { port: 3000, protocol: TCP }
- { port: 2222, protocol: TCP }
egress:
# PostgreSQL
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgres
ports:
- { port: 5432, protocol: TCP }
# DNS
- ports:
- { port: 53, protocol: UDP }
- { port: 53, protocol: TCP }
# Argo Events webhook target (gitea push hook → gitea-push eventsource :12000). The eventsource pod is
# not part-of:foxhunt (stays default-allow), so allow egress to any in-namespace pod on 12000.
- to:
- podSelector: {}
ports:
- { port: 12000, protocol: TCP }
# general HTTPS (avatars, external clones) — external only, exclude cluster CIDRs
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: ["10.32.0.0/16", "172.16.0.0/16"]
ports:
- { port: 443, protocol: TCP }

View File

@@ -0,0 +1,73 @@
# 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
# RWO PVC (sbs-default-retain) → only one pod can mount it. RollingUpdate deadlocks (new pod can't
# attach while old holds it). Recreate terminates old before starting new.
strategy:
type: Recreate
# Disable all bundled subcharts — reuse the existing in-cluster postgres, no redis/memcached.
postgresql:
enabled: false
postgresql-ha:
enabled: false
redis-cluster:
enabled: false
redis:
enabled: false
# chart 12.x replaced redis with valkey — disable both, gitea uses embedded queue/cache/session
valkey-cluster:
enabled: false
valkey:
enabled: false
persistence:
enabled: true
size: 5Gi
storageClass: sbs-default-retain
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: "1", memory: 2Gi } # 2Gi headroom: indexing the 876MB foxhunt pack OOMed at 512Mi
service:
http: { type: ClusterIP, port: 3000 }
ssh: { type: ClusterIP, port: 22 }
gitea:
admin:
existingSecret: gitea-admin
config:
actions:
ENABLED: "false" # Argo does CI/CD; Gitea Actions off (subchart removed in chart 12.x)
server:
ROOT_URL: https://git.fxhnt.ai/
DOMAIN: git.fxhnt.ai
SSH_DOMAIN: git.fxhnt.ai
SSH_PORT: "22" # clean git@git.fxhnt.ai clone URLs; :2222 retired (Task 5).
DISABLE_SSH: "false"
database:
DB_TYPE: postgres
HOST: postgres.foxhunt.svc.cluster.local:5432
NAME: gitea
USER: gitea
service:
DISABLE_REGISTRATION: "true"
webhook:
# allow delivery to in-cluster targets (Argo Events eventsource on the 10.32.x service CIDR);
# default blocks private hosts. Argo auto-deploy webhook needs this.
ALLOWED_HOST_LIST: "private"
# No redis/memcached — use embedded adapters
cache:
ADAPTER: memory
session:
PROVIDER: db
queue:
TYPE: level
additionalConfigFromEnvs:
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef: { name: gitea-db, key: password }

View File

@@ -75,10 +75,10 @@ spec:
- name: ssh-proxy
image: alpine/socat:latest
args:
- "TCP-LISTEN:2222,fork,reuseaddr,sndbuf=1048576,rcvbuf=1048576"
- "TCP:gitlab-gitlab-shell.foxhunt.svc.cluster.local:2222,sndbuf=1048576,rcvbuf=1048576"
- "TCP-LISTEN:22,fork,reuseaddr,sndbuf=1048576,rcvbuf=1048576"
- "TCP:gitea-sshd.foxhunt.svc.cluster.local:22,sndbuf=1048576,rcvbuf=1048576"
ports:
- containerPort: 2222
- containerPort: 22
resources:
requests:
cpu: 25m
@@ -152,7 +152,7 @@ data:
return 301 https://$host$request_uri;
}
# GitLab — git.fxhnt.ai
# Gitea — git.fxhnt.ai (replaced GitLab, Phase 2B cutover)
server {
listen 443 ssl;
server_name git.fxhnt.ai;
@@ -164,7 +164,7 @@ data:
client_max_body_size 0;
location / {
proxy_pass http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181;
proxy_pass http://gitea-web.foxhunt.svc.cluster.local:3000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@@ -1,4 +1,4 @@
# Postgres: accepts connections from foxhunt app pods, GitLab, and Grafana
# Postgres: accepts connections from foxhunt app pods, GitLab, Grafana, and Gitea
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
@@ -23,6 +23,9 @@ spec:
- podSelector:
matchLabels:
app.kubernetes.io/name: grafana
- podSelector: # Gitea (Phase 2B) reuses the in-cluster postgres
matchLabels:
app.kubernetes.io/name: gitea
ports:
- protocol: TCP
port: 5432

View File

@@ -8,6 +8,7 @@ terraform {
inputs = {
dns_zone = "fxhnt.ai"
# Tailscale IP of foxhunt-gitlab proxy pod
git_ip = "100.90.76.85"
# Tailscale IP of the foxhunt-gitlab proxy pod (live node foxhunt-gitlab; the old 100.90.76.85
# was a stale prior IP — its duplicate A records across *.fxhnt.ai were removed 2026-06-21).
git_ip = "100.95.225.27"
}