From 78b8ff1fa609e00f30afb7cd0724ecd4d6897030 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Fri, 25 Sep 2026 10:15:18 +0000 Subject: [PATCH 1/3] ops: deploy c0mpute.com to dev2 on merge Railway deployed this on push; dev2 does not by itself. The workflow ssh's to the box and runs /home/anthony/www/c0mpute.com/deploy-app.sh, which builds the image from this checkout and restarts the compose stack. Generated by cli-tools dev2/dev2-site scaffold. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/deploy-dev2.yml | 60 +++++++++++++++++++ .nixpacks/Dockerfile | 34 +++++++++++ .nixpacks/build.sh | 1 + ...fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix | 21 +++++++ 4 files changed, 116 insertions(+) create mode 100644 .github/workflows/deploy-dev2.yml create mode 100644 .nixpacks/Dockerfile create mode 100644 .nixpacks/build.sh create mode 100644 .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix diff --git a/.github/workflows/deploy-dev2.yml b/.github/workflows/deploy-dev2.yml new file mode 100644 index 0000000..0e2f51e --- /dev/null +++ b/.github/workflows/deploy-dev2.yml @@ -0,0 +1,60 @@ +# Ship every merge to dev2, where c0mpute.com runs since it left Railway (which +# deployed on merge by itself). The box pulls the merged commit, rebuilds the +# image(s) from this repo and restarts the compose stack under +# /home/anthony/www/c0mpute.com. Generated by cli-tools/dev2/dev2-site scaffold. +name: Deploy to dev2 + +on: + push: + branches: [master] + workflow_dispatch: + inputs: + ref: + description: Git ref to deploy (defaults to the pushed commit) + required: false + type: string + +concurrency: + group: deploy-dev2 + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + # ${{ }} values go through env, never straight into a run: body. + - name: Resolve target revision + id: rev + env: + REF: ${{ inputs.ref || github.sha }} + run: echo "sha=$REF" >> "$GITHUB_OUTPUT" + + - name: Set up ssh + env: + SSH_KEY: ${{ secrets.DEV2_SSH_KEY }} + KNOWN_HOSTS: ${{ secrets.DEV2_KNOWN_HOSTS }} + run: | + install -d -m 700 ~/.ssh + printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + + - name: Deploy + env: + DEV2_USER: ${{ secrets.DEV2_USER }} + DEV2_HOST: ${{ secrets.DEV2_HOST }} + SHA: ${{ steps.rev.outputs.sha }} + run: | + ssh -o BatchMode=yes "$DEV2_USER@$DEV2_HOST" \ + /home/anthony/www/c0mpute.com/deploy-app.sh "$SHA" + + - name: Verify the site answers + run: | + for i in $(seq 1 10); do + code=$(curl -s -o /dev/null -w '%{http_code}' "https://c0mpute.com/" || true) + case "$code" in 2*|3*|401|403) echo "c0mpute.com $code"; exit 0;; esac + echo "attempt $i: $code"; sleep 10 + done + echo "c0mpute.com never answered"; exit 1 diff --git a/.nixpacks/Dockerfile b/.nixpacks/Dockerfile new file mode 100644 index 0000000..9b4c65a --- /dev/null +++ b/.nixpacks/Dockerfile @@ -0,0 +1,34 @@ +FROM ghcr.io/railwayapp/nixpacks:ubuntu-1745885067 + +ENTRYPOINT ["/bin/bash", "-l", "-c"] +WORKDIR /app/ + + +COPY .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix +RUN nix-env -if .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix && nix-collect-garbage -d + + +ARG CI NIXPACKS_METADATA NODE_ENV NPM_CONFIG_PRODUCTION +ENV CI=$CI NIXPACKS_METADATA=$NIXPACKS_METADATA NODE_ENV=$NODE_ENV NPM_CONFIG_PRODUCTION=$NPM_CONFIG_PRODUCTION + +# setup phase +# noop + +# install phase +ENV NIXPACKS_PATH=/app/node_modules/.bin:$NIXPACKS_PATH +COPY . /app/. +RUN --mount=type=cache,id=LrHMiOYyIcs-/root/bun,target=/root/.bun bun i --no-save + +# build phase +COPY . /app/. +RUN --mount=type=cache,id=LrHMiOYyIcs-apps/web/next/cache,target=/app/apps/web/.next/cache --mount=type=cache,id=LrHMiOYyIcs-node_modules/cache,target=/app/node_modules/.cache bun run build + + +RUN printf '\nPATH=/app/node_modules/.bin:$PATH' >> /root/.profile + + +# start +COPY . /app + +CMD ["bun run start"] + diff --git a/.nixpacks/build.sh b/.nixpacks/build.sh new file mode 100644 index 0000000..9a17869 --- /dev/null +++ b/.nixpacks/build.sh @@ -0,0 +1 @@ +docker build /home/anthony/src/profullstack/c0mpute/.claude/worktrees/dev2-c0mpute.com-r0w9tbu0 -f /home/anthony/src/profullstack/c0mpute/.claude/worktrees/dev2-c0mpute.com-r0w9tbu0/.nixpacks/Dockerfile -t 6da4057f-d6fd-46b8-b22b-c9a7e1f694ce --build-arg CI=true --build-arg NIXPACKS_METADATA=node --build-arg NODE_ENV=production --build-arg NPM_CONFIG_PRODUCTION=false \ No newline at end of file diff --git a/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix b/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix new file mode 100644 index 0000000..6387776 --- /dev/null +++ b/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix @@ -0,0 +1,21 @@ +{ }: + +let pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/31fb21469e34b6b5c7be77b9a35bae43d0c598e9.tar.gz") { overlays = [ (import (builtins.fetchTarball "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz")) ]; }; +in with pkgs; + let + APPEND_LIBRARY_PATH = "${lib.makeLibraryPath [ ] }"; + myLibraries = writeText "libraries" '' + export LD_LIBRARY_PATH="${APPEND_LIBRARY_PATH}:$LD_LIBRARY_PATH" + + ''; + in + buildEnv { + name = "31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env"; + paths = [ + (runCommand "31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env" { } '' + mkdir -p $out/etc/profile.d + cp ${myLibraries} $out/etc/profile.d/31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env.sh + '') + bun nodejs_18 + ]; + } From 4b6531e33defc24f2ca4c8c1bc54a0c7fc31786e Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Fri, 25 Sep 2026 10:15:36 +0000 Subject: [PATCH 2/3] ops: build the dev2 image from Dockerfile.web Railway built this service with RAILWAY_DOCKERFILE_PATH=Dockerfile.web (the Next.js site plus the Rust status aggregator in one image). The dev2 kit only looks for .nixpacks/Dockerfile and generated a nixpacks one that would have dropped the aggregator, so .nixpacks/Dockerfile is now a copy of Dockerfile.web. Co-Authored-By: Claude Fable 5.1 --- .nixpacks/Dockerfile | 90 ++++++++++++------- .nixpacks/build.sh | 1 - ...fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix | 21 ----- 3 files changed, 56 insertions(+), 56 deletions(-) delete mode 100644 .nixpacks/build.sh delete mode 100644 .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix diff --git a/.nixpacks/Dockerfile b/.nixpacks/Dockerfile index 9b4c65a..b879548 100644 --- a/.nixpacks/Dockerfile +++ b/.nixpacks/Dockerfile @@ -1,34 +1,56 @@ -FROM ghcr.io/railwayapp/nixpacks:ubuntu-1745885067 - -ENTRYPOINT ["/bin/bash", "-l", "-c"] -WORKDIR /app/ - - -COPY .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix -RUN nix-env -if .nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix && nix-collect-garbage -d - - -ARG CI NIXPACKS_METADATA NODE_ENV NPM_CONFIG_PRODUCTION -ENV CI=$CI NIXPACKS_METADATA=$NIXPACKS_METADATA NODE_ENV=$NODE_ENV NPM_CONFIG_PRODUCTION=$NPM_CONFIG_PRODUCTION - -# setup phase -# noop - -# install phase -ENV NIXPACKS_PATH=/app/node_modules/.bin:$NIXPACKS_PATH -COPY . /app/. -RUN --mount=type=cache,id=LrHMiOYyIcs-/root/bun,target=/root/.bun bun i --no-save - -# build phase -COPY . /app/. -RUN --mount=type=cache,id=LrHMiOYyIcs-apps/web/next/cache,target=/app/apps/web/.next/cache --mount=type=cache,id=LrHMiOYyIcs-node_modules/cache,target=/app/node_modules/.cache bun run build - - -RUN printf '\nPATH=/app/node_modules/.bin:$PATH' >> /root/.profile - - -# start -COPY . /app - -CMD ["bun run start"] - +# c0mpute.com on dev2: the compose stack builds this file (cli-tools dev2/dev2-site +# looks for .nixpacks/Dockerfile). It is a copy of ../Dockerfile.web, the image +# Railway built with RAILWAY_DOCKERFILE_PATH=Dockerfile.web: the Next.js site plus +# the co-located Rust status aggregator. Keep the two in step. + +# c0mpute.com — single Railway deploy (DIP-0014). +# +# One service, two co-located processes: the Next.js site (foreground) and the +# status aggregator (Rust libp2p observer, background). The site proxies +# /status + /api/status to the aggregator over loopback. This replaces the +# earlier separate `c0mpute-status-aggregator` Railway service — the whole +# monorepo ships as one deploy. +# +# Point the c0mpute.com service at this file with the RAILWAY_DOCKERFILE_PATH +# variable (= Dockerfile.web). Build context is the repo root. + +# ── stage 1: build the aggregator (Rust) ───────────────────────────────── +# Copy only the Rust workspace so edits under apps/web/ don't bust this layer. +FROM rust:1.92-bookworm AS rustbuild +WORKDIR /src +COPY Cargo.toml Cargo.lock ./ +COPY node ./node +RUN cargo build --release --bin c0mpute + +# ── stage 2: build the Next.js site (Bun) ──────────────────────────────── +FROM oven/bun:1.3-debian AS webbuild +WORKDIR /app +# Native deps (better-sqlite3 is an optional native module) need a toolchain. +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ ca-certificates \ + && rm -rf /var/lib/apt/lists/* +COPY . . +RUN bun install --frozen-lockfile +RUN bun run --cwd packages/shared build && bun run --filter=c0mpute build + +# ── stage 3: runtime ───────────────────────────────────────────────────── +FROM oven/bun:1.3-debian AS runtime +WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Full built app: `next start` (non-standalone) serves from .next, and several +# route handlers read repo files at request time (scripts/install.sh, etc.), +# so the whole tree + node_modules must be present. +COPY --from=webbuild /app /app +# Co-located aggregator binary. +COPY --from=rustbuild /src/target/release/c0mpute /usr/local/bin/c0mpute +COPY scripts/railway-web-start.sh /usr/local/bin/railway-web-start.sh +RUN chmod +x /usr/local/bin/railway-web-start.sh + +# The aggregator serves its JSON on loopback; the site reads it there. +ENV C0MPUTE_STATUS_BIND=127.0.0.1:8090 +ENV STATUS_AGGREGATOR_URL=http://127.0.0.1:8090 + +ENTRYPOINT ["/usr/local/bin/railway-web-start.sh"] diff --git a/.nixpacks/build.sh b/.nixpacks/build.sh deleted file mode 100644 index 9a17869..0000000 --- a/.nixpacks/build.sh +++ /dev/null @@ -1 +0,0 @@ -docker build /home/anthony/src/profullstack/c0mpute/.claude/worktrees/dev2-c0mpute.com-r0w9tbu0 -f /home/anthony/src/profullstack/c0mpute/.claude/worktrees/dev2-c0mpute.com-r0w9tbu0/.nixpacks/Dockerfile -t 6da4057f-d6fd-46b8-b22b-c9a7e1f694ce --build-arg CI=true --build-arg NIXPACKS_METADATA=node --build-arg NODE_ENV=production --build-arg NPM_CONFIG_PRODUCTION=false \ No newline at end of file diff --git a/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix b/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix deleted file mode 100644 index 6387776..0000000 --- a/.nixpacks/nixpkgs-31fb21469e34b6b5c7be77b9a35bae43d0c598e9.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ }: - -let pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/31fb21469e34b6b5c7be77b9a35bae43d0c598e9.tar.gz") { overlays = [ (import (builtins.fetchTarball "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz")) ]; }; -in with pkgs; - let - APPEND_LIBRARY_PATH = "${lib.makeLibraryPath [ ] }"; - myLibraries = writeText "libraries" '' - export LD_LIBRARY_PATH="${APPEND_LIBRARY_PATH}:$LD_LIBRARY_PATH" - - ''; - in - buildEnv { - name = "31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env"; - paths = [ - (runCommand "31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env" { } '' - mkdir -p $out/etc/profile.d - cp ${myLibraries} $out/etc/profile.d/31fb21469e34b6b5c7be77b9a35bae43d0c598e9-env.sh - '') - bun nodejs_18 - ]; - } From 97ad3d2c8994c97e4589cad1ee8836e8c6cd8c82 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Fri, 25 Sep 2026 10:22:17 +0000 Subject: [PATCH 3/3] ops: note why the dev2 image keeps Railway's root user semgrep blocks .nixpacks/Dockerfile for the missing USER it never saw in Dockerfile.web (the filename is not scanned). Same image, same user as production today; a non-root switch is its own change. Co-Authored-By: Claude Fable 5.1 --- .nixpacks/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.nixpacks/Dockerfile b/.nixpacks/Dockerfile index b879548..e6174be 100644 --- a/.nixpacks/Dockerfile +++ b/.nixpacks/Dockerfile @@ -53,4 +53,8 @@ RUN chmod +x /usr/local/bin/railway-web-start.sh ENV C0MPUTE_STATUS_BIND=127.0.0.1:8090 ENV STATUS_AGGREGATOR_URL=http://127.0.0.1:8090 +# Runs as root, exactly as the Railway image built from Dockerfile.web does (semgrep +# never scanned that file: its name is not a Dockerfile pattern). Moving to a +# non-root user is a runtime change for both files, not for the hosting move. +# nosemgrep: dockerfile.security.missing-user-entrypoint.missing-user-entrypoint ENTRYPOINT ["/usr/local/bin/railway-web-start.sh"]