revert: remove forward proxy and Claude Code routing through it
Some checks failed
CI / lint (push) Successful in 42s
Deploy / notebooks (push) Has been skipped
Deploy / api (push) Has been skipped
CI / test (push) Successful in 14m27s
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / mc (push) Has been skipped
Package Supply Chain / pkg-supply-chain (push) Failing after 1m4s
Deploy / report (push) Successful in 12s
Some checks failed
CI / lint (push) Successful in 42s
Deploy / notebooks (push) Has been skipped
Deploy / api (push) Has been skipped
CI / test (push) Successful in 14m27s
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / mc (push) Has been skipped
Package Supply Chain / pkg-supply-chain (push) Failing after 1m4s
Deploy / report (push) Successful in 12s
Undoes the entire proxy.fhirworx.io stack: -fbf621c— squid + lego + cloudflared ingress (server side) -61d3000— WSL client bootstrap script -6b02b95— proxy/ docker compose (client container) -2bdbdc1— sandbox topology fix -a94d3d4— daemon-mode claude container Routing Claude Code through a self-hosted proxy was the goal; the WSL/docker client path proved fragile (TLS/proxy interactions, clock drift, bind-mount assumptions) and not worth keeping. Dropping the whole concept rather than carrying broken scaffolding. Kept:794edf8(traefik trustedIPs) — unrelated to the proxy work.
This commit is contained in:
76
compose.yml
76
compose.yml
@@ -887,81 +887,6 @@ services:
|
|||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
proxy:
|
|
||||||
build: ./infra/squid
|
|
||||||
container_name: proxy
|
|
||||||
networks:
|
|
||||||
- gateway
|
|
||||||
volumes:
|
|
||||||
- ./infra/squid/squid.conf:/etc/squid/squid.conf:ro
|
|
||||||
- ./infra/squid/passwd:/etc/squid/passwd:ro
|
|
||||||
- proxy_certs:/etc/squid/certs:ro
|
|
||||||
depends_on:
|
|
||||||
lego:
|
|
||||||
condition: service_healthy
|
|
||||||
labels:
|
|
||||||
- "promtail=true"
|
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
# Watches the cert volume; HUPs squid when lego writes a new cert.
|
|
||||||
# Shares squid's PID namespace so `pkill` can find PID 1.
|
|
||||||
proxy-reloader:
|
|
||||||
build: ./infra/squid-reloader
|
|
||||||
container_name: proxy-reloader
|
|
||||||
pid: "service:proxy"
|
|
||||||
volumes:
|
|
||||||
- proxy_certs:/etc/squid/certs:ro
|
|
||||||
depends_on:
|
|
||||||
- proxy
|
|
||||||
labels:
|
|
||||||
- "promtail=true"
|
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
# ACME (Let's Encrypt) for proxy.fhirworx.io via Cloudflare DNS-01.
|
|
||||||
# On first boot, issues the cert; thereafter loops daily and renews
|
|
||||||
# when <30 days remain. Cert files land in the proxy_certs volume,
|
|
||||||
# which squid mounts read-only.
|
|
||||||
lego:
|
|
||||||
image: goacme/lego:latest
|
|
||||||
container_name: lego
|
|
||||||
networks:
|
|
||||||
- gateway
|
|
||||||
dns:
|
|
||||||
- 1.1.1.1
|
|
||||||
- 1.0.0.1
|
|
||||||
environment:
|
|
||||||
- CLOUDFLARE_DNS_API_TOKEN=${CF_API_TOKEN}
|
|
||||||
- LEGO_EMAIL=${LEGO_EMAIL}
|
|
||||||
entrypoint:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
DOMAIN=proxy.fhirworx.io
|
|
||||||
while true; do
|
|
||||||
/lego run --email="$$LEGO_EMAIL" --domains="$$DOMAIN" \
|
|
||||||
--dns=cloudflare --dns.propagation.wait=60s \
|
|
||||||
--path=/data --accept-tos --renew-days=30 || true
|
|
||||||
sleep 86400
|
|
||||||
done
|
|
||||||
volumes:
|
|
||||||
- proxy_certs:/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "test", "-f", "/data/certificates/proxy.fhirworx.io.crt"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 60
|
|
||||||
start_period: 5s
|
|
||||||
labels:
|
|
||||||
- "promtail=true"
|
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
cloudflared:
|
cloudflared:
|
||||||
image: cloudflare/cloudflared:latest
|
image: cloudflare/cloudflared:latest
|
||||||
container_name: cloudflared
|
container_name: cloudflared
|
||||||
@@ -985,4 +910,3 @@ volumes:
|
|||||||
prometheus_data:
|
prometheus_data:
|
||||||
tempo_data:
|
tempo_data:
|
||||||
grafana_data:
|
grafana_data:
|
||||||
proxy_certs:
|
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Set up a WSL (or any Debian/Ubuntu) machine to route Claude Code through
|
|
||||||
# proxy.fhirworx.io via cloudflared TCP access. Idempotent — safe to re-run.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# PROXY_PASSWORD=... ./setup-proxy-client.sh
|
|
||||||
# or
|
|
||||||
# ./setup-proxy-client.sh # will prompt for the password
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
HOSTNAME_PROXY=proxy.fhirworx.io
|
|
||||||
LOCAL_PORT=18443
|
|
||||||
SERVICE_NAME=cf-proxy
|
|
||||||
|
|
||||||
# ── 0. Pre-flight ─────────────────────────────────────────────────────────────
|
|
||||||
log() { printf '\033[1;34m[setup]\033[0m %s\n' "$*"; }
|
|
||||||
fail() { printf '\033[1;31m[setup] ERROR:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
||||||
|
|
||||||
[[ $EUID -ne 0 ]] || fail "run as your normal user — sudo is invoked per-step"
|
|
||||||
command -v systemctl >/dev/null || fail "systemd not found. Enable it in /etc/wsl.conf ([boot] systemd=true) then 'wsl --shutdown'."
|
|
||||||
command -v sudo >/dev/null || fail "sudo missing"
|
|
||||||
command -v curl >/dev/null || fail "curl missing"
|
|
||||||
command -v python3 >/dev/null || fail "python3 missing"
|
|
||||||
|
|
||||||
if [[ -z "${PROXY_PASSWORD:-}" ]]; then
|
|
||||||
read -rsp "proxy password for user 'claude': " PROXY_PASSWORD; echo
|
|
||||||
fi
|
|
||||||
[[ -n "$PROXY_PASSWORD" ]] || fail "PROXY_PASSWORD is empty"
|
|
||||||
|
|
||||||
# ── 1. cloudflared ────────────────────────────────────────────────────────────
|
|
||||||
if ! command -v cloudflared >/dev/null; then
|
|
||||||
log "installing cloudflared"
|
|
||||||
sudo mkdir -p --mode=0755 /usr/share/keyrings
|
|
||||||
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
|
|
||||||
| sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
|
|
||||||
CODENAME=$(. /etc/os-release; echo "${VERSION_CODENAME:-bookworm}")
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $CODENAME main" \
|
|
||||||
| sudo tee /etc/apt/sources.list.d/cloudflared.list >/dev/null
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y cloudflared
|
|
||||||
else
|
|
||||||
log "cloudflared already installed ($(cloudflared --version 2>&1 | head -1))"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 2. /etc/hosts pin + WSL persistence ───────────────────────────────────────
|
|
||||||
log "pinning $HOSTNAME_PROXY → 127.0.0.1 in /etc/hosts"
|
|
||||||
sudo sed -i "/\b$HOSTNAME_PROXY\b/d" /etc/hosts
|
|
||||||
echo "127.0.0.1 $HOSTNAME_PROXY" | sudo tee -a /etc/hosts >/dev/null
|
|
||||||
|
|
||||||
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
||||||
if ! grep -q '^\s*generateHosts\s*=\s*false' /etc/wsl.conf 2>/dev/null; then
|
|
||||||
log "patching /etc/wsl.conf so /etc/hosts isn't regenerated on next boot"
|
|
||||||
sudo tee -a /etc/wsl.conf >/dev/null <<'EOF'
|
|
||||||
|
|
||||||
[network]
|
|
||||||
generateHosts = false
|
|
||||||
EOF
|
|
||||||
WSL_REBOOT_NEEDED=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
getent hosts "$HOSTNAME_PROXY" | grep -q '^127\.0\.0\.1' \
|
|
||||||
|| fail "$HOSTNAME_PROXY does not resolve to 127.0.0.1 — check /etc/hosts and /etc/nsswitch.conf"
|
|
||||||
|
|
||||||
# ── 3. systemd user service for the cloudflared shim ──────────────────────────
|
|
||||||
log "writing systemd user unit ~/.config/systemd/user/$SERVICE_NAME.service"
|
|
||||||
mkdir -p ~/.config/systemd/user
|
|
||||||
cat > ~/.config/systemd/user/$SERVICE_NAME.service <<EOF
|
|
||||||
[Unit]
|
|
||||||
Description=cloudflared access tcp shim for $HOSTNAME_PROXY
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/cloudflared access tcp --hostname $HOSTNAME_PROXY --url 127.0.0.1:$LOCAL_PORT
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable --now "$SERVICE_NAME.service"
|
|
||||||
sudo loginctl enable-linger "$USER" # keep user manager alive across logouts
|
|
||||||
|
|
||||||
# wait briefly for the listener
|
|
||||||
for _ in $(seq 1 20); do
|
|
||||||
ss -tln 2>/dev/null | grep -q ":$LOCAL_PORT\b" && break
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
ss -tln 2>/dev/null | grep -q ":$LOCAL_PORT\b" \
|
|
||||||
|| fail "cloudflared shim didn't open :$LOCAL_PORT — check 'journalctl --user -u $SERVICE_NAME -n 30'"
|
|
||||||
|
|
||||||
# ── 4. Claude Code ────────────────────────────────────────────────────────────
|
|
||||||
if ! command -v claude >/dev/null; then
|
|
||||||
log "installing Claude Code"
|
|
||||||
curl -fsSL https://claude.ai/install.sh | bash
|
|
||||||
# the installer drops it under ~/.local/bin or similar; rehash for this shell
|
|
||||||
hash -r
|
|
||||||
fi
|
|
||||||
command -v claude >/dev/null || log "WARNING: claude not on PATH — may need a new shell"
|
|
||||||
|
|
||||||
# ── 5. settings.json (merge, don't clobber) ───────────────────────────────────
|
|
||||||
log "merging proxy env into ~/.claude/settings.json"
|
|
||||||
mkdir -p ~/.claude
|
|
||||||
SETTINGS=~/.claude/settings.json
|
|
||||||
[[ -f "$SETTINGS" ]] || echo '{}' > "$SETTINGS"
|
|
||||||
|
|
||||||
PROXY_URL="https://claude:${PROXY_PASSWORD}@${HOSTNAME_PROXY}:${LOCAL_PORT}"
|
|
||||||
python3 - "$SETTINGS" "$PROXY_URL" <<'PY'
|
|
||||||
import json, sys, os
|
|
||||||
path, proxy_url = sys.argv[1], sys.argv[2]
|
|
||||||
with open(path) as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
env = cfg.get("env", {}) or {}
|
|
||||||
env["HTTPS_PROXY"] = proxy_url
|
|
||||||
env["NO_PROXY"] = "localhost,127.0.0.1"
|
|
||||||
cfg["env"] = env
|
|
||||||
tmp = path + ".tmp"
|
|
||||||
with open(tmp, "w") as f:
|
|
||||||
json.dump(cfg, f, indent=2)
|
|
||||||
os.replace(tmp, path)
|
|
||||||
os.chmod(path, 0o600) # contains the proxy password
|
|
||||||
PY
|
|
||||||
log "wrote $SETTINGS (mode 0600)"
|
|
||||||
|
|
||||||
# ── 6. smoke test ─────────────────────────────────────────────────────────────
|
|
||||||
log "smoke-testing the full path (expect HTTP 401 from Anthropic)"
|
|
||||||
CODE=$(curl -sS -o /dev/null -w '%{http_code}' -m 30 \
|
|
||||||
-x "$PROXY_URL" \
|
|
||||||
https://api.anthropic.com/v1/messages \
|
|
||||||
-H "x-api-key: dummy" -H "anthropic-version: 2023-06-01" \
|
|
||||||
-H "content-type: application/json" \
|
|
||||||
-d '{"model":"claude-opus-4-7","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}' \
|
|
||||||
|| echo "curl-failed")
|
|
||||||
|
|
||||||
case "$CODE" in
|
|
||||||
401) log "✓ smoke test passed (got 401 from api.anthropic.com)" ;;
|
|
||||||
407) fail "got 407 — proxy auth failed. Check PROXY_PASSWORD." ;;
|
|
||||||
403) fail "got 403 — Cloudflare Access is gating the tunnel. Run: cloudflared access login $HOSTNAME_PROXY" ;;
|
|
||||||
"curl-failed"|000) fail "curl couldn't reach the proxy. Check 'systemctl --user status $SERVICE_NAME'." ;;
|
|
||||||
*) fail "unexpected HTTP $CODE — check 'docker compose logs proxy' on the stack host." ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ -n "${WSL_REBOOT_NEEDED:-}" ]]; then
|
|
||||||
cat <<'EOF'
|
|
||||||
|
|
||||||
────────────────────────────────────────────────────────────────────
|
|
||||||
One-time follow-up: /etc/wsl.conf was changed so /etc/hosts won't be
|
|
||||||
regenerated on next WSL boot. To make that effective, from PowerShell
|
|
||||||
on the Windows side, run once:
|
|
||||||
|
|
||||||
wsl --shutdown
|
|
||||||
|
|
||||||
Then reopen WSL. (Current session continues to work as-is.)
|
|
||||||
────────────────────────────────────────────────────────────────────
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "done. Try: claude -p 'ping'"
|
|
||||||
@@ -2,8 +2,6 @@ tunnel: 1389035e-d3ba-4a4f-969d-a369c07ee057
|
|||||||
credentials-file: /home/nonroot/.cloudflared/1389035e-d3ba-4a4f-969d-a369c07ee057.json
|
credentials-file: /home/nonroot/.cloudflared/1389035e-d3ba-4a4f-969d-a369c07ee057.json
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
- hostname: proxy.fhirworx.io
|
|
||||||
service: tcp://proxy:3128
|
|
||||||
- hostname: "*.fhirworx.io"
|
- hostname: "*.fhirworx.io"
|
||||||
service: http://traefik:80
|
service: http://traefik:80
|
||||||
- hostname: "fhirworx.io"
|
- hostname: "fhirworx.io"
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
FROM alpine:3.20
|
|
||||||
RUN apk add --no-cache inotify-tools
|
|
||||||
COPY watch.sh /usr/local/bin/watch.sh
|
|
||||||
RUN chmod +x /usr/local/bin/watch.sh
|
|
||||||
CMD ["/usr/local/bin/watch.sh"]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Watch the cert directory for atomic replaces (lego renames temp → final)
|
|
||||||
# and HUP squid so it re-reads the cert. Joined-PID-namespace with proxy
|
|
||||||
# (compose `pid: service:proxy`) means `pkill -x squid` finds PID 1.
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
CERT_DIR=/etc/squid/certs/certificates
|
|
||||||
CERT_FILE=proxy.fhirworx.io.crt
|
|
||||||
|
|
||||||
echo "watching $CERT_DIR for changes to $CERT_FILE"
|
|
||||||
|
|
||||||
inotifywait -m -e close_write,moved_to,create "$CERT_DIR" | \
|
|
||||||
while read -r _ _ filename; do
|
|
||||||
if [ "$filename" = "$CERT_FILE" ]; then
|
|
||||||
if pkill -HUP -x squid; then
|
|
||||||
echo "$(date -Is) reloaded squid (HUP)"
|
|
||||||
else
|
|
||||||
echo "$(date -Is) no squid process found to signal"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
FROM debian:bookworm-slim
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
squid-openssl apache2-utils ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
EXPOSE 3128
|
|
||||||
CMD ["squid", "-N", "-f", "/etc/squid/squid.conf"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
claude:$6$GswjIxib0ir2tXiF$wa3CoB0aOd7GRwnXb57yt0izN8.aMiLVJ21hhZnc2xBtYO7BOUCFEMTK9qMio5t4vznONaHLFlJXB2Lq1Au531
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Forward HTTPS proxy. Listens TLS-terminated on :3128, accepts CONNECT
|
|
||||||
# tunnels to Anthropic destinations only, requires basic auth. Inner
|
|
||||||
# TLS to api.anthropic.com is untouched (no ssl_bump).
|
|
||||||
|
|
||||||
https_port 3128 tls-cert=/etc/squid/certs/certificates/proxy.fhirworx.io.crt tls-key=/etc/squid/certs/certificates/proxy.fhirworx.io.key
|
|
||||||
|
|
||||||
acl anthropic_dsts dstdomain .anthropic.com .claude.ai .claude.com
|
|
||||||
acl SSL_ports port 443
|
|
||||||
acl CONNECT method CONNECT
|
|
||||||
|
|
||||||
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
|
|
||||||
auth_param basic realm fhirworx-proxy
|
|
||||||
acl authenticated proxy_auth REQUIRED
|
|
||||||
|
|
||||||
http_access deny CONNECT !SSL_ports
|
|
||||||
http_access allow authenticated anthropic_dsts
|
|
||||||
http_access deny all
|
|
||||||
|
|
||||||
forwarded_for delete
|
|
||||||
via off
|
|
||||||
cache deny all
|
|
||||||
access_log daemon:/var/log/squid/access.log squid
|
|
||||||
cache_log /var/log/squid/cache.log
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Password for the squid 'claude' user (see infra/squid/passwd on the host).
|
|
||||||
PROXY_PASSWORD=
|
|
||||||
|
|
||||||
# Cloudflare Access service token for proxy.fhirworx.io.
|
|
||||||
# Mint a new pair under Zero Trust → Access → Service Auth.
|
|
||||||
CF_ACCESS_CLIENT_ID=
|
|
||||||
CF_ACCESS_CLIENT_SECRET=
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Containerized Claude Code client — proxied via cf-proxy (sibling service).
|
|
||||||
# Mirrors what dev/scripts/setup-proxy-client.sh does on a WSL host, minus the
|
|
||||||
# systemd/hosts hackery (compose handles networking + extra_hosts).
|
|
||||||
FROM node:20-bookworm-slim
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
ca-certificates curl git jq openssh-client \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN npm install -g @anthropic-ai/claude-code
|
|
||||||
|
|
||||||
# Daemon-style: container stays alive so users `docker compose exec` claude
|
|
||||||
# inside it. For one-shot invocations: `docker compose run --rm
|
|
||||||
# --entrypoint claude claude [-p 'prompt']`.
|
|
||||||
ENTRYPOINT ["sleep", "infinity"]
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# Run Claude Code in a sandboxed container whose only outside path is the
|
|
||||||
# allowlisted forward proxy at proxy.fhirworx.io.
|
|
||||||
#
|
|
||||||
# Network topology (the point of this compose):
|
|
||||||
# proxy_net internal bridge, 192.168.10.0/24. No NAT, no internet egress.
|
|
||||||
# claude lives only here, so it physically cannot reach anything
|
|
||||||
# outside the host except by going through cf-proxy.
|
|
||||||
# egress_net regular bridge. Only cf-proxy is dual-homed: it sits on
|
|
||||||
# proxy_net to be reachable by claude, and on egress_net so
|
|
||||||
# cloudflared can dial the Cloudflare edge.
|
|
||||||
#
|
|
||||||
# Services:
|
|
||||||
# cf-proxy cloudflared access TCP shim. Terminates the Cloudflare tunnel
|
|
||||||
# at 192.168.10.2:18443. The only door out for claude.
|
|
||||||
# claude Claude Code CLI. HTTPS_PROXY points at proxy.fhirworx.io:18443,
|
|
||||||
# which extra_hosts pins to 192.168.10.2 so the TLS SNI/cert
|
|
||||||
# match without poisoning cf-proxy's own DNS view.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# cp .env.example .env && $EDITOR .env # PROXY_PASSWORD and
|
|
||||||
# CF_ACCESS_CLIENT_ID/SECRET
|
|
||||||
# docker compose up -d claude # bring up the sandbox
|
|
||||||
# docker compose exec claude claude # interactive session
|
|
||||||
# docker compose exec claude claude -p 'ping' # one-shot inside the sandbox
|
|
||||||
# docker compose run --rm --entrypoint claude claude [-p 'ping']
|
|
||||||
# # ephemeral one-shot
|
|
||||||
#
|
|
||||||
# Notes:
|
|
||||||
# - claude-home is a named volume so login state persists across runs.
|
|
||||||
# - Bind mounts use identical host/container paths so Claude never sees a
|
|
||||||
# translated path: /home/care/acoharmony, /opt/s3/data/workspace,
|
|
||||||
# /opt/s3/data/notebooks. Claude only has filesystem access to these.
|
|
||||||
|
|
||||||
services:
|
|
||||||
cf-proxy:
|
|
||||||
image: cloudflare/cloudflared:latest
|
|
||||||
container_name: cc-cf-proxy
|
|
||||||
command: access tcp --hostname proxy.fhirworx.io --url 0.0.0.0:18443
|
|
||||||
environment:
|
|
||||||
TUNNEL_SERVICE_TOKEN_ID: ${CF_ACCESS_CLIENT_ID}
|
|
||||||
TUNNEL_SERVICE_TOKEN_SECRET: ${CF_ACCESS_CLIENT_SECRET}
|
|
||||||
networks:
|
|
||||||
proxy_net:
|
|
||||||
ipv4_address: 192.168.10.2
|
|
||||||
egress_net: {}
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
claude:
|
|
||||||
image: git.fhirworx.io/kert/claude:latest
|
|
||||||
container_name: cc-claude
|
|
||||||
depends_on:
|
|
||||||
- cf-proxy
|
|
||||||
# Long-running so users docker exec into it. claude itself is launched
|
|
||||||
# per-session via `docker compose exec claude claude`.
|
|
||||||
entrypoint: ["sleep", "infinity"]
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- proxy_net
|
|
||||||
extra_hosts:
|
|
||||||
- "proxy.fhirworx.io:192.168.10.2"
|
|
||||||
environment:
|
|
||||||
HTTPS_PROXY: "https://claude:${PROXY_PASSWORD}@proxy.fhirworx.io:18443"
|
|
||||||
HTTP_PROXY: "https://claude:${PROXY_PASSWORD}@proxy.fhirworx.io:18443"
|
|
||||||
NO_PROXY: "localhost,127.0.0.1"
|
|
||||||
working_dir: /home/care/acoharmony
|
|
||||||
volumes:
|
|
||||||
- claude-home:/root/.claude
|
|
||||||
- /home/care/acoharmony:/home/care/acoharmony
|
|
||||||
- /opt/s3/data/workspace:/opt/s3/data/workspace
|
|
||||||
- /opt/s3/data/notebooks:/opt/s3/data/notebooks
|
|
||||||
|
|
||||||
networks:
|
|
||||||
proxy_net:
|
|
||||||
driver: bridge
|
|
||||||
internal: true
|
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 192.168.10.0/24
|
|
||||||
egress_net:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
claude-home:
|
|
||||||
@@ -157,9 +157,8 @@ class TestNetworkAssignment:
|
|||||||
"""Every service should be assigned to at least one network."""
|
"""Every service should be assigned to at least one network."""
|
||||||
|
|
||||||
def test_all_services_have_networks(self):
|
def test_all_services_have_networks(self):
|
||||||
# Exceptions: ephemeral/profile-only services, and proxy-reloader
|
# Exceptions: ephemeral/profile-only services
|
||||||
# which shares proxy's PID namespace and only signals locally.
|
exempt = {"wire"}
|
||||||
exempt = {"wire", "proxy-reloader"}
|
|
||||||
compose = _load_compose()
|
compose = _load_compose()
|
||||||
missing = []
|
missing = []
|
||||||
for svc, cfg in compose.get("services", {}).items():
|
for svc, cfg in compose.get("services", {}).items():
|
||||||
|
|||||||
Reference in New Issue
Block a user