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..727a5b8 100644 --- a/contracts/distribution/railway-template.json +++ b/contracts/distribution/railway-template.json @@ -9,7 +9,9 @@ "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_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/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_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 36f2916..8b39b94 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 "$@"' -- zero-runtime "${args[@]}" +fi + exec "${args[@]}" diff --git a/scripts/railway_template_packet.py b/scripts/railway_template_packet.py index 46b2643..a3e7b17 100755 --- a/scripts/railway_template_packet.py +++ b/scripts/railway_template_packet.py @@ -78,6 +78,15 @@ 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 + ), + "railway_start_preserves_argv_after_su": ( + 'exec su zero -s /bin/sh -c \'exec "$@"\' -- zero-runtime "${args[@]}"' + in start_script + ), } return {