From 968e0a1045a7839c0b7fbe006af7f6318c55f256 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Sun, 1 Nov 2026 13:05:24 -0500 Subject: [PATCH] VPS: add the WristCode gateway stack --- server/vps/Wristcode/compose.yml | 33 +++++++++++++++++++ server/vps/Wristcode/podman-wristcode.service | 24 ++++++++++++++ server/vps/verify-rootless | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 server/vps/Wristcode/compose.yml create mode 100644 server/vps/Wristcode/podman-wristcode.service diff --git a/server/vps/Wristcode/compose.yml b/server/vps/Wristcode/compose.yml new file mode 100644 index 0000000..3f111f5 --- /dev/null +++ b/server/vps/Wristcode/compose.yml @@ -0,0 +1,33 @@ +# WristCode gateway: the backend for the WristCode Apple Watch app (git.gbrown.org/gib/wristcode). +# It holds a WebSocket to every T3 Code server, serves the watch's HTTPS API, and sends APNs pushes. +# NPM serves wristcode.gbrown.org and forwards to wristcode-gateway:8080 over nginx-bridge. +# +# .env beside this file (mode 600): +# APNS_KEY_ID=... the APNs key's ID +# APNS_TEAM_ID=GQJNM5YK2M +# APNS_KEY_PATH=/data/apns.p8 the .p8 file, in ./data +# +# Operator CLI: podman exec -it wristcode-gateway wristcode +networks: + nginx-bridge: + external: true # ALWAYS external; see AGENTS.md §5 + +services: + wristcode-gateway: + image: git.gbrown.org/gib/wristcode-gateway:latest + container_name: wristcode-gateway + hostname: wristcode-gateway + domainname: wristcode.gbrown.org + networks: ['nginx-bridge'] + env_file: [.env] + environment: + - TZ=America/New_York + # The image runs as `bun` (uid 1000). keep-id maps it to gib, so ./data stays gib-owned (AGENTS.md §3.2). + userns_mode: "keep-id:uid=1000,gid=1000" + volumes: + - ./data:/data:z # lowercase :z survives update-containers recreates, as in Agentchat + restart: unless-stopped + healthcheck: + test: wget -qO- http://127.0.0.1:8080/v1/health || exit 1 + interval: 30s + start_period: 10s diff --git a/server/vps/Wristcode/podman-wristcode.service b/server/vps/Wristcode/podman-wristcode.service new file mode 100644 index 0000000..c92e774 --- /dev/null +++ b/server/vps/Wristcode/podman-wristcode.service @@ -0,0 +1,24 @@ +# TEMPLATE for a rootless user unit. +# Install to ~/.config/systemd/user/podman-.service then: +# systemctl --user daemon-reload && systemctl --user enable --now podman-.service +# Requires linger: sudo loginctl enable-linger $USER +[Unit] +Description=Podman Compose: wristcode +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=300 +StartLimitBurst=3 + +[Service] +Type=oneshot +WorkingDirectory=%h/Server/Wristcode +ExecStart=/usr/bin/podman compose up -d +ExecStop=/usr/bin/podman compose down +RemainAfterExit=yes +TimeoutStartSec=300 +TimeoutStopSec=60 +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=default.target diff --git a/server/vps/verify-rootless b/server/vps/verify-rootless index 1bf023e..68f002f 100755 --- a/server/vps/verify-rootless +++ b/server/vps/verify-rootless @@ -17,7 +17,7 @@ p=$(sysctl -n net.ipv4.ip_unprivileged_port_start 2>/dev/null) || bad "ip_unprivileged_port_start=$p (NPM cannot bind :80/:443)" echo "== user units ==" -for s in postgresql bang completeuphoria sierraandtyler uptime portainer beszel authentik tools isponsorblocktv vaultwarden infisical rustdesk nginx-proxy-manager gbrown-agent; do +for s in postgresql bang completeuphoria sierraandtyler uptime portainer beszel authentik tools isponsorblocktv vaultwarden infisical rustdesk nginx-proxy-manager gbrown-agent wristcode; do st=$(systemctl --user is-active "podman-$s.service" 2>/dev/null) [[ "$st" == "active" ]] && ok "$s" || bad "$s is $st" done