opencode UI route via traefik labels on a swarm relay, not the file-directory provider
Enabling FILE_PROVIDER_DIRECTORY_ENABLED in the coop-cloud traefik recipe REPLACES its single provider file (wildcard cert + `security` middleware), which took every cc-ci front door down for two minutes on 2026-09-07. Reverted. The route is now what every recipe does: a one-container swarm stack (`opencode-ui`, alpine/socat relay to nginx on the docker bridge) carrying the traefik router labels, deployed by opencode-ui-route.service. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FqkQq3CDmFWcQ7u1LzoyRz
This commit is contained in:
@@ -158,9 +158,12 @@ Everything in this section is **outside git**. Do it as root over SSH, in this o
|
|||||||
|
|
||||||
The combined host is NOT on the tailnet (operator decision 2026-09-07): ssh is key-only on the
|
The combined host is NOT on the tailnet (operator decision 2026-09-07): ssh is key-only on the
|
||||||
public IP, the CI front doors are public via traefik, and the opencode UI is public on the same
|
public IP, the CI front doors are public via traefik, and the opencode UI is public on the same
|
||||||
443 — traefik routes `oc.ci.commoninternet.net` (a dynamic-config file that
|
443 — traefik routes `oc.ci.commoninternet.net` to an nginx vhost on the docker bridge that
|
||||||
`opencode-ui-route.service` drops into the traefik stack's file-providers volume) to an nginx
|
enforces basic auth, via traefik labels on a tiny swarm relay stack (`opencode-ui`) that
|
||||||
vhost on the docker bridge that enforces basic auth. fail2ban guards sshd and that login
|
`opencode-ui-route.service` deploys. (Not via traefik's file *directory* provider: enabling
|
||||||
|
that in the coop-cloud traefik recipe REPLACES its provider file, which holds the wildcard cert
|
||||||
|
and the `security` middleware, and every front door goes down — it did, for two minutes, on
|
||||||
|
2026-09-07.) fail2ban guards sshd and that login
|
||||||
(`nix/hosts/cc-ci/configuration.nix`; the nginx jail bans in the DOCKER-USER chain because the
|
(`nix/hosts/cc-ci/configuration.nix`; the nginx jail bans in the DOCKER-USER chain because the
|
||||||
traffic is docker-forwarded, not host INPUT).
|
traffic is docker-forwarded, not host INPUT).
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
cc-ci-orchestrator.ciSshHost = "127.0.0.1";
|
cc-ci-orchestrator.ciSshHost = "127.0.0.1";
|
||||||
# The opencode UI: traefik (public 443, the *.ci.commoninternet.net cert) → nginx basic auth.
|
# The opencode UI: traefik (public 443, the *.ci.commoninternet.net cert) → nginx basic auth.
|
||||||
cc-ci-orchestrator.opencodeUiHost = "oc.ci.commoninternet.net";
|
cc-ci-orchestrator.opencodeUiHost = "oc.ci.commoninternet.net";
|
||||||
cc-ci-orchestrator.opencodeUiTraefikProvidersDir =
|
cc-ci-orchestrator.opencodeUiTraefikNetwork = "proxy";
|
||||||
"/var/lib/docker/volumes/traefik_ci_commoninternet_net_file-providers/_data";
|
|
||||||
|
|
||||||
# ---- no tailscale on this host (operator 2026-09-07) --------------------------------------
|
# ---- no tailscale on this host (operator 2026-09-07) --------------------------------------
|
||||||
# Everything is reached over the public IP: ssh (keys only), the CI front doors via traefik,
|
# Everything is reached over the public IP: ssh (keys only), the CI front doors via traefik,
|
||||||
|
|||||||
@@ -40,17 +40,18 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
opencodeUiTraefikProvidersDir = lib.mkOption {
|
opencodeUiTraefikNetwork = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = null;
|
default = null;
|
||||||
example = "/var/lib/docker/volumes/traefik_ci_commoninternet_net_file-providers/_data";
|
example = "proxy";
|
||||||
description = ''
|
description = ''
|
||||||
Host path of the traefik stack's file-providers directory. When set, a dynamic-config
|
Name of the swarm overlay network the cc-ci traefik watches (cc-ci's swarm.nix creates
|
||||||
file routing opencodeUiHost on the `web-secure` entrypoint to nginx is written there after
|
`proxy`). When set, a one-container swarm stack `opencode-ui` (a socat TCP relay to
|
||||||
deploy-proxy. Traefik only watches that directory when the coop-cloud traefik recipe is
|
nginx on the docker bridge) is deployed with traefik labels routing opencodeUiHost on
|
||||||
deployed with FILE_PROVIDER_DIRECTORY_ENABLED=1 in its app env
|
the `web-secure` entrypoint — the same label mechanism every cc-ci service and recipe
|
||||||
(/root/.abra/servers/default/<traefik domain>.env) — see README "Stage the workspace".
|
uses, so it coexists with the traefik recipe's own file provider (the wildcard cert and
|
||||||
null = no traefik route.
|
the `security` middleware live there; switching traefik to a file *directory* replaces
|
||||||
|
that file and takes every front door down — learned 2026-09-07). null = no route.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,30 +224,37 @@ SSHCFG
|
|||||||
# Only docker's bridge may reach the plain-HTTP backend; the public interface stays closed.
|
# Only docker's bridge may reach the plain-HTTP backend; the public interface stays closed.
|
||||||
networking.firewall.interfaces.docker_gwbridge.allowedTCPPorts = [ cfg.opencodeUiBackendPort ];
|
networking.firewall.interfaces.docker_gwbridge.allowedTCPPorts = [ cfg.opencodeUiBackendPort ];
|
||||||
|
|
||||||
# The traefik side of the route: a dynamic-config file in the stack's watched providers dir.
|
# The traefik side of the route: a swarm service carrying the router labels. It is a plain
|
||||||
systemd.services.opencode-ui-route = lib.mkIf (cfg.opencodeUiTraefikProvidersDir != null) {
|
# TCP relay (socat) from the overlay network to nginx on the docker bridge; traefik's
|
||||||
description = "traefik route: ${cfg.opencodeUiHost} -> nginx (opencode UI basic auth)";
|
# X-Forwarded-For passes through untouched, which is what nginx's real_ip reads.
|
||||||
|
systemd.services.opencode-ui-route = lib.mkIf (cfg.opencodeUiTraefikNetwork != null) {
|
||||||
|
description = "swarm stack opencode-ui: traefik labels ${cfg.opencodeUiHost} -> nginx basic auth";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "deploy-proxy.service" "docker.service" ];
|
after = [ "deploy-proxy.service" "docker.service" "nginx.service" ];
|
||||||
wants = [ "deploy-proxy.service" ];
|
wants = [ "deploy-proxy.service" ];
|
||||||
|
path = [ pkgs.docker ];
|
||||||
serviceConfig = { Type = "oneshot"; RemainAfterExit = true; };
|
serviceConfig = { Type = "oneshot"; RemainAfterExit = true; };
|
||||||
script = ''
|
script = ''
|
||||||
install -d -m 0755 "${cfg.opencodeUiTraefikProvidersDir}"
|
docker stack deploy --detach=true -c ${pkgs.writeText "opencode-ui-stack.yml" ''
|
||||||
install -m 0644 ${pkgs.writeText "opencode-ui.yml" ''
|
# Deployed by opencode-ui-route.service (nix/modules/orchestrator-host.nix). Do not edit.
|
||||||
# Written by opencode-ui-route.service (nix/modules/orchestrator-host.nix). Do not edit.
|
version: "3.8"
|
||||||
http:
|
services:
|
||||||
routers:
|
relay:
|
||||||
opencode-ui:
|
image: alpine/socat:1.8.0.3
|
||||||
rule: Host(`${cfg.opencodeUiHost}`)
|
command: ["TCP-LISTEN:${toString cfg.opencodeUiBackendPort},fork,reuseaddr", "TCP:172.18.0.1:${toString cfg.opencodeUiBackendPort}"]
|
||||||
entryPoints: [web-secure]
|
networks: [ ${cfg.opencodeUiTraefikNetwork} ]
|
||||||
service: opencode-ui
|
deploy:
|
||||||
tls: {}
|
replicas: 1
|
||||||
services:
|
labels:
|
||||||
opencode-ui:
|
- "traefik.enable=true"
|
||||||
loadBalancer:
|
- "traefik.http.routers.opencode-ui.rule=Host(`${cfg.opencodeUiHost}`)"
|
||||||
servers:
|
- "traefik.http.routers.opencode-ui.entrypoints=web-secure"
|
||||||
- url: http://172.18.0.1:${toString cfg.opencodeUiBackendPort}
|
- "traefik.http.routers.opencode-ui.tls=true"
|
||||||
''} "${cfg.opencodeUiTraefikProvidersDir}/opencode-ui.yml"
|
- "traefik.http.services.opencode-ui.loadbalancer.server.port=${toString cfg.opencodeUiBackendPort}"
|
||||||
|
networks:
|
||||||
|
${cfg.opencodeUiTraefikNetwork}:
|
||||||
|
external: true
|
||||||
|
''} opencode-ui
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user