-
Notifications
You must be signed in to change notification settings - Fork 9
deploy: add Docker relay container, nginx alternative, and CN mirror docs #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Keep the relay Docker build context lean and free of machine-local state. | ||
| # web/dist is built inside the image (stage 1); web/node_modules would bloat | ||
| # the context and is never committed upstream. | ||
| .git/ | ||
| .venv/ | ||
| __pycache__/ | ||
| *.pyc | ||
| .pytest_cache/ | ||
| web/node_modules/ | ||
| web/dist/ | ||
| web/test-results/ | ||
| *.log | ||
| # Never leak a populated env file into the image. `cp env.relay.docker.example | ||
| # env.relay` is the documented first step; the real env holds the login | ||
| # password, session secret, and wrapper token, so it must never reach the | ||
| # build context. | ||
| deploy/env.relay |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Multi-stage image for the cc-remote Relay. | ||
| # | ||
| # The official deploy path is a systemd venv staged by setup-vps.sh in front of | ||
| # a managed Caddy (see deploy/README.md and install-relay.sh). This container | ||
| # runs the exact same `python -m cc_remote.relay` release in an isolated root | ||
| # filesystem — for VPSes that already manage services with Docker, or where the | ||
| # relay should be pinned to its own writable state volume. | ||
| # | ||
| # The repository does not track web/dist, so the first stage builds the web | ||
| # client from source (npm run build) and the second stage only carries the | ||
| # built artifact. Python wheels are installed with the same hash-locked, | ||
| # binary-only rules the official installer uses; http-ece ships only an sdist | ||
| # on PyPI, so it is exempted with --no-binary exactly as setup-vps.sh does. | ||
| # | ||
| # Mirrors: PyPI.org by default. In mainland China build with | ||
| # docker build -f deploy/Dockerfile \ | ||
| # --build-arg PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple \ | ||
| # --build-arg PIP_EXTRA_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \ | ||
| # -t cc-remote-relay . | ||
|
|
||
| FROM node:24-alpine AS web-build | ||
| WORKDIR /src/web | ||
| COPY web/package.json web/package-lock.json ./ | ||
| RUN npm ci | ||
| COPY web/ ./ | ||
| RUN npm run build | ||
|
|
||
| FROM python:3.13-slim-bookworm AS relay | ||
|
|
||
| ENV PYTHONUNBUFFERED=1 \ | ||
| PIP_NO_CACHE_DIR=1 \ | ||
| PIP_DISABLE_PIP_VERSION_CHECK=1 \ | ||
| RELAY_HOST=127.0.0.1 \ | ||
| RELAY_PORT=8765 \ | ||
| WEB_STATIC_DIR=/app/web/dist \ | ||
| PUSH_DB_PATH=/app/state/relay-push.sqlite3 \ | ||
| DEVICE_DB_PATH=/app/state/relay-devices.sqlite3 \ | ||
| SESSION_TTL_SECONDS=604800 \ | ||
| LOG_LEVEL=INFO | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Relay application code, deploy reference files, and the hash-locked | ||
| # dependency list (the installed wheels are the ones setup-vps.sh pins). | ||
| COPY cc_remote/ ./cc_remote/ | ||
| COPY deploy/ ./deploy/ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When users follow Useful? React with 👍 / 👎. |
||
| COPY requirements-relay.lock requirements-relay.lock | ||
|
|
||
| # The systemd unit runs the relay as a dedicated `ccremote` user | ||
| # (deploy/cc-remote-relay.service); mirror that inside the container. State is | ||
| # written to /app/state — mount it as a volume (see docker-compose.yml). | ||
| RUN groupadd --gid 10001 ccremote \ | ||
| && useradd --uid 10001 --gid ccremote --home-dir /app \ | ||
| --shell /usr/sbin/nologin ccremote \ | ||
| && mkdir -p /app/state \ | ||
| && chown -R ccremote:ccremote /app/state | ||
|
|
||
| ARG PIP_INDEX_URL=https://pypi.org/simple | ||
| ARG PIP_EXTRA_INDEX_URL= | ||
| RUN pip install --index-url "$PIP_INDEX_URL" \ | ||
| ${PIP_EXTRA_INDEX_URL:+--extra-index-url "$PIP_EXTRA_INDEX_URL"} \ | ||
| --require-hashes --only-binary=:all: --no-binary=http-ece \ | ||
| -r requirements-relay.lock | ||
|
|
||
| # Built web client from the first stage. | ||
| COPY --from=web-build --chown=ccremote:ccremote /src/web/dist ./web/dist/ | ||
|
|
||
| EXPOSE 8765 | ||
|
|
||
| HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ | ||
| CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8765/healthz', timeout=4)" || exit 1 | ||
|
|
||
| USER ccremote | ||
|
|
||
| CMD ["python", "-m", "cc_remote.relay"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # cc-remote Relay container. | ||
| # | ||
| # The official deploy is a systemd venv + managed Caddy (see deploy/README.md). | ||
| # This compose file runs the same relay inside a container for isolated, | ||
| # memory-bounded VPSes, or for any host that already terminates TLS itself. | ||
| # | ||
| # Public TLS/WebSocket termination is expected to stay with nginx or another | ||
| # host reverse proxy: the relay is published only to the host loopback here, | ||
| # and the relay only trusts forwarded transport metadata from loopback peers. | ||
| # See nginx-reverse-proxy.conf.example for the nginx alternative to Caddy. | ||
| # | ||
| # Usage (from this deploy/ directory): | ||
| # cp env.relay.docker.example env.relay # then fill in the secrets | ||
| # docker compose up -d --build | ||
| # curl https://your-domain/healthz # -> {"ok":true,...} | ||
| services: | ||
| relay: | ||
| build: | ||
| context: .. | ||
| dockerfile: deploy/Dockerfile | ||
| image: cc-remote-relay:latest | ||
| container_name: cc-remote-relay | ||
| restart: unless-stopped | ||
| # Host networking so the relay sees loopback peers. The relay only trusts | ||
| # forwarded transport metadata (X-Forwarded-Proto / X-Forwarded-For) from | ||
| # 127.0.0.1 and ::1; the Docker bridge gateway is not one of those, so a | ||
| # normal `ports:` mapping would make it ignore the proxy headers and reject | ||
| # the HTTPS PUBLIC_ORIGIN. With host networking the relay binds loopback | ||
| # only (RELAY_HOST=127.0.0.1), which the host nginx/Caddy reaches directly. | ||
| # Linux hosts only; the `ports:` mapping is ignored under host networking. | ||
| network_mode: host | ||
| env_file: | ||
| - env.relay | ||
| volumes: | ||
| # Persistent device registry + Web Push subscription databases. The named | ||
| # volume inherits /app/state ownership from the image (the ccremote | ||
| # user), so no host-side chown is needed. | ||
| - relay-state:/app/state | ||
| read_only: true | ||
| tmpfs: | ||
| - /tmp | ||
|
|
||
| volumes: | ||
| relay-state: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # deploy/env.relay — environment for the Docker container (docker-compose.yml). | ||
| # | ||
| # cp env.relay.docker.example env.relay # then fill in the secrets below | ||
| # | ||
| # Generate each secret with: openssl rand -hex 32 | ||
| # setup-vps.sh rejects placeholders, LOGIN_PASSWORD shorter than 16 characters, | ||
| # and SESSION_SECRET / WRAPPER_TOKEN shorter than 32 characters. The relay | ||
| # enforces the same rules at startup, so the container will fail fast on any | ||
| # placeholder left behind. | ||
| # | ||
| # The Dockerfile already sets the container-appropriate defaults, so these do | ||
| # not need to appear in this file: | ||
| # RELAY_HOST=127.0.0.1 (host networking; loopback-bound, see compose) | ||
| # RELAY_PORT=8765 | ||
| # WEB_STATIC_DIR=/app/web/dist | ||
| # PUSH_DB_PATH=/app/state/relay-push.sqlite3 | ||
| # DEVICE_DB_PATH=/app/state/relay-devices.sqlite3 | ||
| # SESSION_TTL_SECONDS=604800 | ||
| # Override any of them here if you need a different value. | ||
|
|
||
| # Exact browser origin allowed to open a cookie-authenticated WebSocket. | ||
| PUBLIC_ORIGIN=https://cc-remote.example.com | ||
| # Leave at 0 for the normal domain + TLS path (see the nginx example / Caddy). | ||
| ALLOW_INSECURE_HTTP=0 | ||
| # Web login password (what you type in the browser to log in). REQUIRED. | ||
| LOGIN_PASSWORD=REPLACE_WITH_A_STRONG_PASSWORD | ||
| # HMAC secret for signing web session tokens. REQUIRED. | ||
| SESSION_SECRET=REPLACE_WITH_openssl_rand_hex_32 | ||
| # Must match the wrapper's WRAPPER_TOKEN. | ||
| WRAPPER_TOKEN=REPLACE_WITH_STRONG_WRAPPER_TOKEN | ||
| # Optional durable Web Push completion notifications. Configure all three | ||
| # VAPID values; keep the private key readable only by the relay service user. | ||
| # PUSH_VAPID_PUBLIC_KEY=REPLACE_WITH_VAPID_PUBLIC_KEY | ||
| # PUSH_VAPID_PRIVATE_KEY=REPLACE_WITH_VAPID_PRIVATE_KEY | ||
| # PUSH_VAPID_SUBJECT=mailto:admin@example.com | ||
| LOG_LEVEL=INFO |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # cc-remote Relay behind nginx. | ||
| # | ||
| # The official deploy uses a managed Caddy that terminates TLS and reverse | ||
| # proxies the WebSocket (deploy/Caddyfile). If you already run nginx as the | ||
| # only front on this host, use this file as the alternative. The relay | ||
| # deliberately trusts forwarded transport metadata only from loopback peers, so | ||
| # nginx MUST run on the same host and proxy to the relay's loopback address | ||
| # (with the Docker compose file, that is 127.0.0.1:8765 on host networking). | ||
| # | ||
| # Bootstrap on a fresh host: | ||
| # 1. Point an A/AAAA record for cc-remote.example.com at this VPS and open | ||
| # 80/443. | ||
| # 2. Install this file with ONLY the HTTP block active (the 443 block below | ||
| # is commented out until a certificate exists - otherwise `nginx -t` | ||
| # fails before the ACME challenge can even be served). | ||
| # 3. Issue the certificate: `certbot certonly --webroot -w /var/www/html | ||
| # -d cc-remote.example.com`, then uncomment the 443 block (or let | ||
| # `certbot --nginx` fill it in once the challenge can be served). | ||
| # 4. `nginx -t && systemctl reload nginx`. | ||
|
|
||
| server { | ||
| listen 80; | ||
| listen [::]:80; | ||
| server_name cc-remote.example.com; | ||
|
|
||
| # Serves the ACME challenge so certbot can complete the certificate | ||
| # issuance before the TLS block below is enabled. | ||
| location /.well-known/acme-challenge/ { | ||
| root /var/www/html; | ||
| } | ||
|
|
||
| location / { | ||
| return 301 https://$host$request_uri; | ||
| } | ||
| } | ||
|
|
||
| # TLS block. Keep it commented out until the certificate exists (see the | ||
| # bootstrap steps above), then uncomment it - or let certbot --nginx manage it. | ||
| #server { | ||
| # listen 443 ssl; | ||
| # listen [::]:443 ssl; | ||
| # server_name cc-remote.example.com; | ||
| # | ||
| # ssl_certificate /etc/letsencrypt/live/cc-remote.example.com/fullchain.pem; | ||
| # ssl_certificate_key /etc/letsencrypt/live/cc-remote.example.com/privkey.pem; | ||
| # | ||
| # # The /ws WebSocket carries wrapper and browser connections, so the upgrade | ||
| # # headers and long timeouts are required - keep-alive settings alone are | ||
| # # not enough. | ||
| # location / { | ||
| # proxy_pass http://127.0.0.1:8765; | ||
| # proxy_http_version 1.1; | ||
| # proxy_set_header Upgrade $http_upgrade; | ||
| # proxy_set_header Connection "upgrade"; | ||
| # proxy_set_header Host $host; | ||
| # proxy_set_header X-Real-IP $remote_addr; | ||
| # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| # proxy_set_header X-Forwarded-Proto $scheme; | ||
| # proxy_read_timeout 3600s; | ||
| # proxy_send_timeout 3600s; | ||
| # } | ||
| #} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mirror recipe does not work for the documented Linux installation path:
deploy/install.shcrosses intosudo envat lines 120–128 while forwarding onlyCC_REMOTE_INSTALL_USERand the optional password-file variable, so normal sudo environment filtering removes both exportedUV_*values before the bundled uv runs. Localsudo --helpidentifies-E/--preserve-envas the option for retaining these variables; alternatively, add both variables to the explicitsudo_envarray.Useful? React with 👍 / 👎.