From 7b2934a6ac9236d10fc7fd23cddf326eefce8cb2 Mon Sep 17 00:00:00 2001 From: Igor Pugachevsky Date: Wed, 1 Jul 2026 15:34:14 +0700 Subject: [PATCH 1/2] fix: prepare Railway volume before dropping privileges Co-authored-by: Codex --- Dockerfile | 2 -- contracts/distribution/railway-template.json | 1 + docs/llms-full.txt | 5 +++++ docs/railway-template.md | 5 +++++ scripts/hardening_gate.sh | 1 + scripts/public_readiness_gate.sh | 1 + scripts/railway_start.sh | 5 +++++ scripts/railway_template_packet.py | 5 +++++ 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b788b66..1ddd552 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,6 @@ RUN python -m pip install --no-cache-dir --upgrade pip \ && chmod +x /app/scripts/railway_start.sh \ && chown -R zero:zero /app -USER zero - EXPOSE 8765 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ diff --git a/contracts/distribution/railway-template.json b/contracts/distribution/railway-template.json index 5278437..d25cd00 100644 --- a/contracts/distribution/railway-template.json +++ b/contracts/distribution/railway-template.json @@ -9,6 +9,7 @@ "partner_docs_name_template_url": true, "partner_docs_present": true, "railway_healthcheck_configured": true, + "railway_start_drops_root_after_volume_chown": true, "railway_start_names_runtime_version": true, "railway_start_uses_durable_journal": true, "railway_start_uses_foundation_state_dir": true, diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 0fbe578..0d6eafc 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -2320,6 +2320,9 @@ captured. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`; do not hardcode a port. +- The container starts as root only long enough for + `/app/scripts/railway_start.sh` to create and chown Railway's mounted + `/data` volume, then the paper API process runs as the `zero` user. ### Deployment Dependencies @@ -2389,6 +2392,8 @@ before any live authority ceremony. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`. +- Startup repairs mounted volume ownership, then drops to the `zero` user + before launching the API. ### Why Deploy ZERO on Railway? diff --git a/docs/railway-template.md b/docs/railway-template.md index 794d383..22ec1bb 100644 --- a/docs/railway-template.md +++ b/docs/railway-template.md @@ -53,6 +53,9 @@ captured. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`; do not hardcode a port. +- The container starts as root only long enough for + `/app/scripts/railway_start.sh` to create and chown Railway's mounted + `/data` volume, then the paper API process runs as the `zero` user. ### Deployment Dependencies @@ -122,6 +125,8 @@ before any live authority ceremony. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`. +- Startup repairs mounted volume ownership, then drops to the `zero` user + before launching the API. ### Why Deploy ZERO on Railway? diff --git a/scripts/hardening_gate.sh b/scripts/hardening_gate.sh index 2ab88c2..db44876 100755 --- a/scripts/hardening_gate.sh +++ b/scripts/hardening_gate.sh @@ -157,6 +157,7 @@ contains "zero.registry_launch_packet.v1" docs/registry-launch.md contains "zero.railway_template_packet.v1" contracts/distribution/railway-template.json contains "pending_marketplace_republish_after_runtime_package_update" contracts/distribution/railway-template.json contains "zero-engine==0.1.6" contracts/distribution/railway-template.json +contains "railway_start_drops_root_after_volume_chown" contracts/distribution/railway-template.json contains "pending_after_marketplace_republish" contracts/distribution/railway-template.json contains "template_published_partner_pending" contracts/distribution/railway-template.json contains "docs/assets/zero-template-icon.svg" contracts/distribution/railway-template.json diff --git a/scripts/public_readiness_gate.sh b/scripts/public_readiness_gate.sh index 068ccdf..9abf545 100755 --- a/scripts/public_readiness_gate.sh +++ b/scripts/public_readiness_gate.sh @@ -315,6 +315,7 @@ file_contains "https://railway.com/deploy/zero-paper-runtime" README.md file_contains "zero.railway_template_packet.v1" contracts/distribution/railway-template.json file_contains "pending_marketplace_republish_after_runtime_package_update" contracts/distribution/railway-template.json file_contains "zero-engine==0.1.6" contracts/distribution/railway-template.json +file_contains "railway_start_drops_root_after_volume_chown" contracts/distribution/railway-template.json file_contains "pending_after_marketplace_republish" contracts/distribution/railway-template.json file_contains "template_published_partner_pending" contracts/distribution/railway-template.json file_contains "ZERO Paper Runtime" contracts/distribution/railway-template.json diff --git a/scripts/railway_start.sh b/scripts/railway_start.sh index 36f2916..ad57aeb 100755 --- a/scripts/railway_start.sh +++ b/scripts/railway_start.sh @@ -31,4 +31,9 @@ case "${ZERO_HYPERLIQUID_LIVE_PRICES}" in ;; esac +if [ "$(id -u)" = "0" ]; then + chown -R zero:zero "$(dirname "${ZERO_JOURNAL_PATH}")" "${ZERO_FOUNDATION_STATE_DIR}" + exec su zero -s /bin/sh -c 'exec "$@"' -- "${args[@]}" +fi + exec "${args[@]}" diff --git a/scripts/railway_template_packet.py b/scripts/railway_template_packet.py index 46b2643..0c7eb58 100755 --- a/scripts/railway_template_packet.py +++ b/scripts/railway_template_packet.py @@ -78,6 +78,11 @@ def build_packet() -> dict[str, Any]: 'ZERO_FOUNDATION_STATE_DIR="${ZERO_FOUNDATION_STATE_DIR:-/data/.zero}"' in start_script ), + "railway_start_drops_root_after_volume_chown": ( + 'chown -R zero:zero "$(dirname "${ZERO_JOURNAL_PATH}")" "${ZERO_FOUNDATION_STATE_DIR}"' + in start_script + and "exec su zero -s /bin/sh" in start_script + ), } return { From bc2a3d03aabfa87d6f53157469b78ba2d91c3740 Mon Sep 17 00:00:00 2001 From: Igor Pugachevsky Date: Wed, 1 Jul 2026 15:40:11 +0700 Subject: [PATCH 2/2] fix: preserve Railway runtime argv after root drop --- contracts/distribution/railway-template.json | 1 + scripts/railway_smoke.sh | 16 +++++++++++++++- scripts/railway_start.sh | 2 +- scripts/railway_template_packet.py | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/contracts/distribution/railway-template.json b/contracts/distribution/railway-template.json index d25cd00..727a5b8 100644 --- a/contracts/distribution/railway-template.json +++ b/contracts/distribution/railway-template.json @@ -11,6 +11,7 @@ "railway_healthcheck_configured": true, "railway_start_drops_root_after_volume_chown": true, "railway_start_names_runtime_version": true, + "railway_start_preserves_argv_after_su": true, "railway_start_uses_durable_journal": true, "railway_start_uses_foundation_state_dir": true, "railway_start_uses_port": true, diff --git a/scripts/railway_smoke.sh b/scripts/railway_smoke.sh index 6aa5660..951a8c9 100755 --- a/scripts/railway_smoke.sh +++ b/scripts/railway_smoke.sh @@ -15,7 +15,21 @@ cleanup() { docker logs "${CONTAINER_NAME}" >&2 || true fi docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true - rm -rf "${STATE_DIR}" + if [[ -d "${STATE_DIR}" ]]; then + docker run --rm \ + -v "${STATE_DIR}:/data" \ + --entrypoint /bin/sh \ + "${IMAGE}" \ + -c "chown -R $(id -u):$(id -g) /data" >/dev/null 2>&1 || true + rm -rf "${STATE_DIR}" || { + if command -v sudo >/dev/null 2>&1; then + sudo rm -rf "${STATE_DIR}" + else + chmod -R u+w "${STATE_DIR}" 2>/dev/null || true + rm -rf "${STATE_DIR}" + fi + } + fi } trap cleanup EXIT diff --git a/scripts/railway_start.sh b/scripts/railway_start.sh index ad57aeb..8b39b94 100755 --- a/scripts/railway_start.sh +++ b/scripts/railway_start.sh @@ -33,7 +33,7 @@ esac if [ "$(id -u)" = "0" ]; then chown -R zero:zero "$(dirname "${ZERO_JOURNAL_PATH}")" "${ZERO_FOUNDATION_STATE_DIR}" - exec su zero -s /bin/sh -c 'exec "$@"' -- "${args[@]}" + exec su zero -s /bin/sh -c 'exec "$@"' -- zero-runtime "${args[@]}" fi exec "${args[@]}" diff --git a/scripts/railway_template_packet.py b/scripts/railway_template_packet.py index 0c7eb58..a3e7b17 100755 --- a/scripts/railway_template_packet.py +++ b/scripts/railway_template_packet.py @@ -83,6 +83,10 @@ def build_packet() -> dict[str, Any]: in start_script and "exec su zero -s /bin/sh" in start_script ), + "railway_start_preserves_argv_after_su": ( + 'exec su zero -s /bin/sh -c \'exec "$@"\' -- zero-runtime "${args[@]}"' + in start_script + ), } return {