fix: deployment corrections from independent review

Six fixes addressing operational correctness on a real Debian host:

1. Ownership after git operations: chown -R zopu:zopu on the checkout
   after clone/update/rollback before running bun install/build as the
   service user. .env kept at 0600 with explicit chmod after each
   operation.

2. sudo replaced with runuser: minimal Debian does not include sudo.
   runuser is part of util-linux (essential) and always available.
   All three scripts (bootstrap, update, rollback) now use runuser -u.

3. cron.d entry fixed: /etc/cron.d format requires a username field.
   Added ${SERVICE_USER} between the time fields and the command path.

4. Firewall: added explicit `ufw allow in on tailscale0` rule so the
   deny-incoming default does not block Tailscale private-overlay
   reachability. Removed inaccurate claim that direct private IP works
   by default; documented that an explicit per-interface rule is needed.

5. SupplementaryGroups=docker added to zopu-agent.service: the Orb
   sandbox runtime lives in the agent process, not just the daemon.

6. zopu-health.service: added Environment=ENV_FILE=__INSTALL_DIR__/.env
   so health-check.sh sources the correct .env at custom install paths.

Verified Flue build output: bun run build:agents produces
packages/agents/dist/server.mjs (confirmed by running the build).
Agent unit ExecStart path is correct.

Validated: bash -n on all 6 shell scripts; mocked health-check smoke
against live RivetKit engine on port 6420; systemd unit structural
checks. No JS/TS/agent files changed.
This commit is contained in:
-Puter
2026-07-24 20:49:54 +05:30
parent 8f0b915f4b
commit 113c755e23
6 changed files with 90 additions and 25 deletions

View File

@@ -4,7 +4,7 @@
#
# Run as root on a fresh Debian 12 host:
#
# sudo bash bootstrap.sh
# bash bootstrap.sh
#
# Environment overrides (set before running):
# ZOPU_REPO_URL — SSH clone URL (default: ssh://git@git.openputer.com:2222/puter/zopu-code.git)
@@ -38,6 +38,12 @@ log() { echo -e "${GREEN}[bootstrap]${NC} $*"; }
warn() { echo -e "${YELLOW}[bootstrap]${NC} $*"; }
err() { echo -e "${RED}[bootstrap]${NC} $*" >&2; }
# runuser is part of util-linux (essential on Debian) and always available.
# sudo is NOT assumed on minimal Debian installs.
run_as_service() {
runuser -u "$SERVICE_USER" -- "$@"
}
# ---------------------------------------------------------------------------
# Pre-flight
# ---------------------------------------------------------------------------
@@ -64,9 +70,9 @@ apt-get install -y \
ufw \
git \
jq \
netcat-openbsd \
openssh-client \
unattended-upgrades \
netcat-openbsd \
rsyslog
# ---------------------------------------------------------------------------
@@ -137,17 +143,23 @@ else
cd "$INSTALL_DIR"
fi
# ---------------------------------------------------------------------------
# 5b. Hand ownership of the checkout to the service user
# ---------------------------------------------------------------------------
log "Setting ownership of $INSTALL_DIR to $SERVICE_USER..."
chown -R "$SERVICE_USER":"$SERVICE_USER" "$INSTALL_DIR"
# ---------------------------------------------------------------------------
# 6. Install dependencies and build
# ---------------------------------------------------------------------------
log "Running bun install..."
sudo -u "$SERVICE_USER" bun install
run_as_service bun install
log "Building daemon binary..."
sudo -u "$SERVICE_USER" bun run build:daemon
run_as_service bun run build:daemon
log "Building agent service..."
sudo -u "$SERVICE_USER" bun run build:agents
run_as_service bun run build:agents
# ---------------------------------------------------------------------------
# 7. Environment file
@@ -161,6 +173,9 @@ if [[ ! -f "$ENV_FILE" ]]; then
warn "Edit $ENV_FILE with real values before starting services."
else
log ".env already exists at $ENV_FILE"
# Ensure correct ownership and permissions on existing .env
chown "$SERVICE_USER":"$SERVICE_USER" "$ENV_FILE"
chmod 600 "$ENV_FILE"
fi
# ---------------------------------------------------------------------------
@@ -190,19 +205,25 @@ done
systemctl daemon-reload
# ---------------------------------------------------------------------------
# 10. Firewall (deny-by-default posture)
# 10. Firewall (deny-by-default, explicit allow for Tailscale)
# ---------------------------------------------------------------------------
log "Configuring firewall..."
if ! ufw status 2>/dev/null | grep -q "Status: active"; then
ufw allow 22/tcp
ufw default deny incoming
ufw default allow outgoing
# Allow all traffic on the Tailscale interface (if present)
# This lets the agent and engine ports be reached over the private overlay.
ufw allow in on tailscale0 || warn "tailscale0 not present yet; rule will activate when interface appears"
ufw --force enable
log "Firewall enabled: SSH (22) allowed, all other incoming denied."
warn "Agent and RivetKit ports are NOT exposed publicly."
warn "Access is via Tailscale or direct private IP only."
log "Firewall enabled: SSH (22) allowed, tailscale0 allowed."
warn "Agent and RivetKit ports are NOT exposed on public interfaces."
warn "Reachability is via Tailscale (tailscale0) only."
else
log "Firewall already active."
log "Firewall already active. Ensuring tailscale0 rule..."
ufw allow in on tailscale0 2>/dev/null || true
fi
# ---------------------------------------------------------------------------
@@ -217,15 +238,22 @@ if [[ -n "${TAILSCALE_AUTHKEY:-}" ]]; then
--hostname "${TAILSCALE_HOSTNAME:-zopu-runtime}" \
--accept-routes
log "Tailscale configured: $(tailscale ip -4 2>/dev/null || echo 'waiting for IP')"
# Re-apply the tailscale0 firewall rule now that the interface exists
ufw allow in on tailscale0 2>/dev/null || true
else
warn "TAILSCALE_AUTHKEY not set — skipping Tailscale setup."
warn "Without Tailscale, services are reachable only via localhost."
warn "To use a private network interface, add an explicit UFW rule:"
warn " ufw allow in on <interface>"
fi
# ---------------------------------------------------------------------------
# 12. Disk-space monitoring cron
# /etc/cron.d format REQUIRES a username field.
# ---------------------------------------------------------------------------
log "Installing disk-space monitor (daily at 06:00)..."
CRON_LINE="0 6 * * * $DEPLOY_DIR/scripts/disk-monitor.sh --warn-percent 80 >> /var/log/zopu/disk-monitor.log 2>&1"
CRON_LINE="0 6 * * * ${SERVICE_USER} ${DEPLOY_DIR}/scripts/disk-monitor.sh --warn-percent 80 >> /var/log/zopu/disk-monitor.log 2>&1"
echo "$CRON_LINE" > /etc/cron.d/zopu-disk-monitor
chmod 644 /etc/cron.d/zopu-disk-monitor