feat(deploy): add host-process systemd unit alongside the Docker one#3617
Conversation
Agents spawn host CLIs (gh, codex, opencode, infisical, claude) via child_process. In containers those tools either aren't installed or can't see the host's auth files (~/.codex/auth.json, ~/.config/gh/hosts.yml, etc), so operator workflows that depend on host-local auth need a non-Docker deploy path. ## Changes - Rename `automaker.service` -> `automaker-docker.service`. Header rewritten to call out that this is the containerized variant and point at the host counterpart. Adds `After=network-online.target` + `Wants=network-online.target` so docker doesn't race the network. - New `automaker-host.service`: runs `npm start` (which executes `start-automaker.mjs --production`) under a dedicated deploy user. Sets NODE_ENV=production, AUTO_MODE=true, journald logging, graceful SIGTERM with 30s drain, restart-on-failure with burst limit. Filesystem hardening via ProtectSystem=full + ReadWritePaths so the agent can still write worktrees + the deploy user's HOME (for tool credentials). - `docs/self-hosting/deployment.md`: systemd section rewritten to document the two-unit choice. Table comparing the two, install instructions for each, "pick exactly one per host" guidance. Operators pick the variant that fits their deploy: sudo systemctl enable --now automaker-host.service # bare-metal sudo systemctl enable --now automaker-docker.service # containerized ## What's not in this PR - No changes to deploy-main.yml or any CI workflow. Deploy plumbing decides which unit to install separately. - No changes to start-automaker.mjs itself. It already supports --production; the host unit just wires it into systemd.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR refactors the systemd deployment model by replacing a single generic ChangesSystemd Deployment Unit Refactor
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code Review — ? finding(s)
protoLabs Code Review Report
No findings recorded. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@automaker-host.service`:
- Around line 55-56: The StartLimitIntervalSec and StartLimitBurst directives
are currently placed under the [Service] section but must live in the [Unit]
section to apply start-rate limiting correctly; move the two lines
StartLimitIntervalSec=300 and StartLimitBurst=5 from the [Service] block into
the [Unit] block in the automaker-host.service unit file so systemd applies the
restart throttling semantics as intended.
In `@docs/self-hosting/deployment.md`:
- Around line 179-218: Reorder the "Install the host-process variant" and
"Install the Docker variant" sections so each begins with the runnable commands
(the cp, edit, systemctl, and journalctl lines) followed by the explanatory
narrative; specifically move the command blocks that reference
automaker-host.service and automaker-docker.service, and the notes about
WorkingDirectory and Environment=COMPOSE_FILE, to appear before the descriptive
paragraphs about when to use automaker-host.service vs automaker-docker.service
and the rationale about child_process/host CLIs; keep the same commands and
config hints (WorkingDirectory, User/Group, Environment=HOME,
Environment=COMPOSE_FILE) and preserve the example journalctl -u automaker-host
-f line.
- Around line 177-230: The systemd installation content (the two units
automaker-docker.service and automaker-host.service and their install
instructions) mixes a How-to into the multi-purpose deployment page; move that
block into a new dedicated How-to titled something like "Systemd service
install" (create a standalone page containing the service descriptions, the
install steps for automaker-host.service and automaker-docker.service, and the
enable/start commands), then replace the removed block in the original
deployment page with a short one-line cross-reference link to the new How-to;
ensure the new page follows Diataxis How-to structure and update any internal
TOC or links accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f63d46a8-bd40-4b18-9c62-f390f56b8861
📒 Files selected for processing (4)
automaker-docker.serviceautomaker-host.serviceautomaker.servicedocs/self-hosting/deployment.md
💤 Files with no reviewable changes (1)
- automaker.service
| StartLimitIntervalSec=300 | ||
| StartLimitBurst=5 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify unit directives are in valid sections
systemd-analyze verify automaker-host.service 2>&1 | rg -n "StartLimit|Unknown lvalue|warning|error" -C2 || trueRepository: protoLabsAI/protoMaker
Length of output: 48
🏁 Script executed:
systemd-analyze verify automaker-host.service 2>&1 | rg -n "StartLimit|Unknown lvalue|warning|error" -C2 || trueRepository: protoLabsAI/protoMaker
Length of output: 48
🏁 Script executed:
systemd-analyze verify automaker-host.service 2>&1 | rg -n "StartLimit|Unknown lvalue|warning|error" -C2 || trueRepository: protoLabsAI/protoMaker
Length of output: 48
Move StartLimitIntervalSec and StartLimitBurst to the [Unit] section in automaker-host.service
Start-rate limiting directives should be in [Unit]; keeping them under [Service] risks restart throttling not being applied as intended (no errors were reported by systemd-analyze verify, but that doesn’t guarantee the intended semantics).
Suggested fix
[Unit]
Description=Automaker AI Development Studio (host process)
Documentation=https://github.com/protoLabsAI/protoMaker
After=network-online.target
Wants=network-online.target
+StartLimitIntervalSec=300
+StartLimitBurst=5
[Service]
@@
Restart=on-failure
RestartSec=10
-StartLimitIntervalSec=300
-StartLimitBurst=5📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| StartLimitIntervalSec=300 | |
| StartLimitBurst=5 | |
| [Unit] | |
| Description=Automaker AI Development Studio (host process) | |
| Documentation=https://github.com/protoLabsAI/protoMaker | |
| After=network-online.target | |
| Wants=network-online.target | |
| StartLimitIntervalSec=300 | |
| StartLimitBurst=5 | |
| [Service] | |
| Restart=on-failure | |
| RestartSec=10 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@automaker-host.service` around lines 55 - 56, The StartLimitIntervalSec and
StartLimitBurst directives are currently placed under the [Service] section but
must live in the [Unit] section to apply start-rate limiting correctly; move the
two lines StartLimitIntervalSec=300 and StartLimitBurst=5 from the [Service]
block into the [Unit] block in the automaker-host.service unit file so systemd
applies the restart throttling semantics as intended.
| ## systemd | ||
|
|
||
| For persistent server deployments, use systemd to manage Docker Compose. | ||
| Two systemd units ship in the repo. Pick exactly one per host: | ||
|
|
||
| ### 1. Install Service File | ||
| | Unit | Runs | When to use | | ||
| | -------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | ||
| | `automaker-docker.service` | `docker compose up -d` | Containerized prod, full filesystem isolation, no host CLI access from agents | | ||
| | `automaker-host.service` | `npm start` (= `start-automaker.mjs --production`) | Bare-metal prod. Agents can shell out to host CLIs (`gh`, `codex`, `opencode`, `infisical`, `claude`). | | ||
|
|
||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker.service /etc/systemd/system/ | ||
| The host-process variant exists because agents spawn external tools via `child_process`; in containers those tools either aren't installed or can't see the host's auth files. If your operator workflow depends on `gh auth login` or `codex login` happening on the host, use `automaker-host.service`. | ||
|
|
||
| # Edit for your environment | ||
| sudo nano /etc/systemd/system/automaker.service | ||
| ``` | ||
| ### Install the host-process variant | ||
|
|
||
| ### 2. Configure Service | ||
|
|
||
| Edit the service file: | ||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker-host.service /etc/systemd/system/ | ||
|
|
||
| ```ini | ||
| [Unit] | ||
| Description=protoLabs AI Development Studio | ||
| After=docker.service | ||
| Requires=docker.service | ||
| # Edit for your environment if defaults don't match | ||
| sudo nano /etc/systemd/system/automaker-host.service | ||
| # WorkingDirectory = clone path (default: /opt/protomaker) | ||
| # User / Group = deploy user (default: automaker) | ||
| # Environment=HOME = $HOME for that user (default: /home/automaker) | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| RemainAfterExit=yes | ||
| WorkingDirectory=/home/youruser/automaker | ||
| ExecStart=/usr/bin/docker compose up -d | ||
| ExecStop=/usr/bin/docker compose down | ||
| ExecReload=/usr/bin/docker compose restart | ||
| TimeoutStartSec=120 | ||
| TimeoutStopSec=60 | ||
| Restart=on-failure | ||
| RestartSec=10 | ||
| sudo systemctl daemon-reload | ||
| sudo systemctl enable --now automaker-host.service | ||
| journalctl -u automaker-host -f | ||
| ``` | ||
|
|
||
| # Run as your user | ||
| User=youruser | ||
| Group=youruser | ||
| ### Install the Docker variant | ||
|
|
||
| Environment=COMPOSE_PROJECT_NAME=automaker | ||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker-docker.service /etc/systemd/system/ | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target | ||
| # Edit for your environment | ||
| sudo nano /etc/systemd/system/automaker-docker.service | ||
| # WorkingDirectory = path containing the compose file | ||
| # User / Group = deploy user | ||
| ``` | ||
|
|
||
| ### 3. Enable and Start | ||
| The shipped file uses the default `docker-compose.yml`. For a prod deploy, set `WorkingDirectory` to the directory containing `docker-compose.prod.yml` and add `Environment=COMPOSE_FILE=docker-compose.prod.yml` to the `[Service]` block. | ||
|
|
||
| ### Enable and start | ||
|
|
||
| ```bash | ||
| # Reload systemd | ||
| sudo systemctl daemon-reload | ||
|
|
||
| # Enable on boot | ||
| sudo systemctl enable automaker | ||
|
|
||
| # Start now | ||
| sudo systemctl start automaker | ||
| # Pick exactly one: | ||
| sudo systemctl enable --now automaker-host.service | ||
| # OR | ||
| sudo systemctl enable --now automaker-docker.service | ||
|
|
||
| # Check status | ||
| sudo systemctl status automaker | ||
| # Check status (substitute the unit you enabled): | ||
| sudo systemctl status automaker-host |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
This addition continues mixing documentation types on one page.
The new systemd how-to content is added into an already multi-purpose page; please split this into a dedicated Diataxis page type (e.g., a focused How-to) and link it from here.
As per coding guidelines, "Documentation must follow the Diataxis framework; each page serves one of four types (Tutorial, How-to Guide, Reference, Explanation); never mix types on a single page".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/self-hosting/deployment.md` around lines 177 - 230, The systemd
installation content (the two units automaker-docker.service and
automaker-host.service and their install instructions) mixes a How-to into the
multi-purpose deployment page; move that block into a new dedicated How-to
titled something like "Systemd service install" (create a standalone page
containing the service descriptions, the install steps for
automaker-host.service and automaker-docker.service, and the enable/start
commands), then replace the removed block in the original deployment page with a
short one-line cross-reference link to the new How-to; ensure the new page
follows Diataxis How-to structure and update any internal TOC or links
accordingly.
| Two systemd units ship in the repo. Pick exactly one per host: | ||
|
|
||
| ### 1. Install Service File | ||
| | Unit | Runs | When to use | | ||
| | -------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | ||
| | `automaker-docker.service` | `docker compose up -d` | Containerized prod, full filesystem isolation, no host CLI access from agents | | ||
| | `automaker-host.service` | `npm start` (= `start-automaker.mjs --production`) | Bare-metal prod. Agents can shell out to host CLIs (`gh`, `codex`, `opencode`, `infisical`, `claude`). | | ||
|
|
||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker.service /etc/systemd/system/ | ||
| The host-process variant exists because agents spawn external tools via `child_process`; in containers those tools either aren't installed or can't see the host's auth files. If your operator workflow depends on `gh auth login` or `codex login` happening on the host, use `automaker-host.service`. | ||
|
|
||
| # Edit for your environment | ||
| sudo nano /etc/systemd/system/automaker.service | ||
| ``` | ||
| ### Install the host-process variant | ||
|
|
||
| ### 2. Configure Service | ||
|
|
||
| Edit the service file: | ||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker-host.service /etc/systemd/system/ | ||
|
|
||
| ```ini | ||
| [Unit] | ||
| Description=protoLabs AI Development Studio | ||
| After=docker.service | ||
| Requires=docker.service | ||
| # Edit for your environment if defaults don't match | ||
| sudo nano /etc/systemd/system/automaker-host.service | ||
| # WorkingDirectory = clone path (default: /opt/protomaker) | ||
| # User / Group = deploy user (default: automaker) | ||
| # Environment=HOME = $HOME for that user (default: /home/automaker) | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| RemainAfterExit=yes | ||
| WorkingDirectory=/home/youruser/automaker | ||
| ExecStart=/usr/bin/docker compose up -d | ||
| ExecStop=/usr/bin/docker compose down | ||
| ExecReload=/usr/bin/docker compose restart | ||
| TimeoutStartSec=120 | ||
| TimeoutStopSec=60 | ||
| Restart=on-failure | ||
| RestartSec=10 | ||
| sudo systemctl daemon-reload | ||
| sudo systemctl enable --now automaker-host.service | ||
| journalctl -u automaker-host -f | ||
| ``` | ||
|
|
||
| # Run as your user | ||
| User=youruser | ||
| Group=youruser | ||
| ### Install the Docker variant | ||
|
|
||
| Environment=COMPOSE_PROJECT_NAME=automaker | ||
| ```bash | ||
| # Copy the service file | ||
| sudo cp automaker-docker.service /etc/systemd/system/ | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target | ||
| # Edit for your environment | ||
| sudo nano /etc/systemd/system/automaker-docker.service | ||
| # WorkingDirectory = path containing the compose file | ||
| # User / Group = deploy user | ||
| ``` | ||
|
|
||
| ### 3. Enable and Start | ||
| The shipped file uses the default `docker-compose.yml`. For a prod deploy, set `WorkingDirectory` to the directory containing `docker-compose.prod.yml` and add `Environment=COMPOSE_FILE=docker-compose.prod.yml` to the `[Service]` block. | ||
|
|
There was a problem hiding this comment.
Put runnable commands before narrative in the new systemd section.
This section explains first and shows commands later; reverse that order to match the docs standard.
As per coding guidelines, "In documentation, show code snippets first, then explanation; use outcome-focused headings (e.g., 'Accept a payment' not 'PaymentIntent API'); write short sentences with active verbs and second person ('you')".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/self-hosting/deployment.md` around lines 179 - 218, Reorder the "Install
the host-process variant" and "Install the Docker variant" sections so each
begins with the runnable commands (the cp, edit, systemctl, and journalctl
lines) followed by the explanatory narrative; specifically move the command
blocks that reference automaker-host.service and automaker-docker.service, and
the notes about WorkingDirectory and Environment=COMPOSE_FILE, to appear before
the descriptive paragraphs about when to use automaker-host.service vs
automaker-docker.service and the rationale about child_process/host CLIs; keep
the same commands and config hints (WorkingDirectory, User/Group,
Environment=HOME, Environment=COMPOSE_FILE) and preserve the example journalctl
-u automaker-host -f line.
`apps/server/tsconfig.build.json` sets `rootDir: "../../"` so tsc preserves
the monorepo directory structure under `outDir`. The actual server entry
ends up at `dist/apps/server/src/index.js`, NOT `dist/index.js`. The
Dockerfile already uses the nested path (CMD line) — but
`apps/server/package.json`'s `main`, `start`, and `start:headless` all
pointed at the (non-existent) flat `dist/index.js`.
This had been broken for months. Nobody noticed because the Docker prod
path bypassed the start script. Surfaced during the host-process systemd
work: `npm start --production` built cleanly, then died at runtime trying
to load a months-old stale dist/services/discord-bot-service.js (the old
flat-layout artifact that predated the rootDir change). The "stale" file
was actually the only thing keeping the path resolvable for non-Docker
runs — clearing it exposes the structural mismatch.
## Changes
- `apps/server/package.json`:
- `main`: `dist/index.js` → `dist/apps/server/src/index.js`
- `start`: `node dist/index.js` → `node dist/apps/server/src/index.js`
- `start:headless`: same path fix
- `build` post-step (the .md copy script): substitutes `^src` with
`dist/apps/server/src` so the runtime can find prompt files like
`pm-prompt.md` (the second crash that surfaced during this debug).
## Validation
rm -rf apps/server/dist
cd apps/server && npm run build
PORT=3018 node dist/apps/server/src/index.js
# → server boots, /api/health returns 200, no missing-module crashes
This fix is a prerequisite for `automaker-host.service` to work
(this PR's main change). Folded in here rather than a separate PR
because the systemd unit is dead without it.
Update: real-machine test surfaced a prerequisite fix (added in
|
Code Review — ? finding(s)
protoLabs Code Review Report
No findings recorded. |
Agents spawn host CLIs (
gh,codex,opencode,infisical,claude) viachild_process. In containers those tools either aren't installed or can't see the host's auth files (~/.codex/auth.json,~/.config/gh/hosts.yml, etc.), so operator workflows that depend on host-local auth need a non-Docker deploy path.This PR doesn't force a choice — it makes both modes first-class systemd units. Pick one per host.
Changes
automaker.service→automaker-docker.service. Header rewritten to call out that this is the containerized variant and point at the host counterpart. AddsAfter=network-online.target+Wants=network-online.targetso Docker doesn't race the network.automaker-host.service. Runsnpm start(which executesstart-automaker.mjs --production) under a dedicated deploy user.NODE_ENV=production,AUTO_MODE=true, journald logginginterruptedin theirfeature.jsonso they resume on next start)StartLimitBurst=5in 5min)ProtectSystem=full+ explicitReadWritePathsso the agent can still write worktrees + the deploy user'sHOMEfor tool credentialsdocs/self-hosting/deployment.md: systemd section rewritten to document the two-unit choice. Table comparing the two, install instructions for each, "pick exactly one per host" guidance.Decision matrix (now in the docs)
automaker-docker.servicedocker compose up -dautomaker-host.servicenpm start(=start-automaker.mjs --production)Out of scope
deploy-main.ymlor any CI workflow. The deploy pipeline still produces a Docker image; switching the production deploy to installautomaker-host.serviceinstead of running compose is a separate change.start-automaker.mjs. It already supports--production; the host unit just wires that into systemd.docker-compose.ymlordocker-compose.prod.yml. The compose-consolidation work is in refactor(docker): consolidate prod compose, drop Swarm-only prod.yml #3616 (which renames staging.yml → prod.yml + drops the Swarm version).Test plan
.servicefiles parse as valid INI (configparser.ConfigParserreads both,ExecStartkeys resolve correctly).automaker-host.serviceto a staging-like host,systemctl enable --now, verify the server starts andnpm startsurvives a reboot. Happens after merge.Summary by CodeRabbit
New Features
Documentation