LOG 010.1: Architecting a Headless AI Bridge — Standardizing OpenClaw under Systemd
This is Part 1 of the Nine / 9OS series. This entry builds the infrastructure foundation: standardizing OpenClaw as a headless systemd service on a minimized Ubuntu Server — OS installation choices, dual-service topology, a PTY workaround for Crestodian's terminal check, persistent gateway lingering, and cross-device bridging. The complete path from bare metal to a production AI Bridge running in the dark.
Series hub: LOG 010: Building Nine — A Headless AI Agent Operating Layer for Paragon9
01. THE SYSTEMS SOURCE: THE SPLIT TOPOLOGY
Every sophisticated system eventually has to leave the comfortable, interactive world and run in the dark — no terminal, no handholding, no human watching the screen. That's the moment you find out whether what you built is real infrastructure or just a demo.
OpenClaw is our internal AI Bridge: a high-performance, monorepo-based orchestration layer designed for fuzzy planning, context routing, and multi-agent management on private infrastructure. Getting it running locally is straightforward. Standardizing it under Linux systemd as a production-grade, auto-healing background service is a different discipline entirely. This log documents that full path — from OS installation choices through cross-device access — so the gap doesn't catch the next person off guard.
OpenClaw doesn't run as a single monolithic block. It splits cleanly into two components that communicate over local loopback:
| Core Daemon Loop | Gateway Server | |
|---|---|---|
| Role | Loads agents, processes plans via model APIs, handles workspace logic. Runs openclaw.mjs directly via Node.js. |
Manages streaming proxy channels, API endpoints, and raw file asset uploads. |
| Interface | System-wide /etc/systemd/system service under an unprivileged user. |
User-space systemctl --user unit bound to ws://127.0.0.1:18789 (local loopback). |
| Systemd Scope | Always-on, auto-heals on failure. | Isolated network sandbox, independently restartable. |
Keeping the gateway in user-space isn't an accident. It maintains clean separation between the core application engine and the public-facing socket layer — which matters when you're running this on bare-metal or a physically constrained machine with no room for architectural slop.
The Insight: If your system can't survive being stripped of its interactive shell, it isn't production infrastructure. It's a prototype wearing a suit. The discipline of systemd deployment forces you to confront every environmental assumption you silently baked into your build.
02. THE BUILD SOURCE: THE DEPLOYMENT ROADMAP
This is a sequential blueprint. Each phase builds on the last. Skip one and the next phase's failure will seem random until you trace it back.
Phase 1: Base Ubuntu Server Preparation
Before OpenClaw, before Node, before any package — the OS-level decisions you make during installation determine whether this server will hold under pressure or drift into instability over time.
Three choices matter at install time:
- UEFI Mode: Configure your BIOS to boot exclusively via UEFI rather than Legacy/CSM. On physical hardware — an old laptop repurposed as a headless server, for example — this keeps the firmware stack clean and avoids boot-sequence ambiguity.
- Static IP: Assign a static IP to your primary network interface during the Ubuntu installer's network configuration phase. DHCP lease changes silently break SSH mappings and internal routing tables. A static address means the bridge always knows where home is.
- Minimal Install: Choose the lean installation profile and resist the default package pulls. Less background drift, fewer attack vectors, a cleaner baseline to build from. Just know what this costs you — which leads directly into Phase 2.
Optional but Recommended — Console Blanking for Headless Servers:
If this server has a physical monitor attached, it will stay lit indefinitely without input — a minor issue on hardware that will run unattended for months in a corner. A one-time systemd service fixes this at the OS level, blanking the screen after one minute of keyboard inactivity, even before login.
Create the unit file:
sudo nano /etc/systemd/system/enable-console-blanking.service
Paste the following:
[Unit]
Description=Enable virtual console blanking
[Service]
Type=oneshot
Environment=TERM=linux
StandardOutput=tty
TTYPath=/dev/console
ExecStart=/usr/bin/setterm -blank 1
[Install]
WantedBy=multi-user.target
Set permissions and enable:
sudo chmod 664 /etc/systemd/system/enable-console-blanking.service
sudo systemctl enable enable-console-blanking.service
Reboot the server. The console will now blank after one minute of inactivity — including the login prompt. Quiet hardware, no burned-in display, one service doing exactly one job.
Phase 2: Installing Runtimes and Global Dependencies
Log into your new server via SSH and provision the core runtime stack:
# Update the system package indexes
sudo apt update && sudo apt upgrade -y
# Install Node.js (v23+ recommended), npm, and git
sudo apt install -y nodejs npm git
Critical callout —
ubuntu-server-minimalis missing more than you think.If you selected the minimized Ubuntu server profile during installation, you are working on a stripped OS image that omits dozens of utilities that every developer assumes exist:
lsof,curl,netstat,ifconfig,nano, and others. These are not optional extras — they are tools that background processes and application boot scripts quietly depend on. OpenClaw specifically requireslsof(List Open Files) to scan network ports and clear stale process IDs before opening its WebSocket gateway. Without it, the gateway boot sequence fails silently with an unhelpfulENOENTerror. Install it explicitly, now, before it surfaces as a mystery failure later:
# Provision lsof — do not skip this on minimal installs
sudo apt install -y lsof
The habit this enforces is sound: on a minimized OS, never assume a utility exists. Provision explicitly. Audit before deploying.
Phase 3: Cloning and Initializing the Codebase
With the runtime layer in place, pull down the project and initialize the dependency tree:
# Create and move into your AI deployment workspace
mkdir -p ~/ai-bridge && cd ~/ai-bridge
# Clone the repository
git clone https://github.com/your-repo/openclaw.git
cd openclaw
# Install package dependencies
npm install
At this point you have a functional codebase on disk. What you don't yet have is a daemon — something that survives reboots, restarts on failure, and runs without you. The next two phases build that.
Phase 4: Stabilizing the Core Daemon Service
This is where most headless deployments break down. If you try to run OpenClaw directly inside a background systemd service using a package manager wrapper like pnpm start, the execution will fail with a generic status=1/FAILURE and a restart loop that tells you nothing useful.
The failures come from two sources:
Path Disconnection. When you log into an SSH session, your shell silently constructs a $PATH that locates your runtimes and global packages. Systemd executes inside a stripped environment where none of those paths exist. High-level wrappers fail to find their own dependencies.
Missing TTY. OpenClaw ships with a built-in management engine called Crestodian. On startup, Crestodian checks whether a human is attached to an active terminal by testing process.stdout.isTTY. Inside a standard systemd service, that check returns false. No terminal detected — process halts immediately to avoid printing interactive menus into a headless void.
Standard systemd TTY directives (StandardInput=tty, TTYPath=/dev/tty63) don't resolve this. Modern runtimes check the underlying file descriptor type, and those streams still register as non-interactive pipelines regardless of the directive.
The Fix: Bypass the package manager entirely and call the Node.js binary directly. To satisfy the TTY check, wrap the execution inside the Linux script utility. script opens an isolated, fully functional pseudo-terminal (PTY) on the fly — presenting the application with a synthetic terminal environment that causes the isTTY check to pass cleanly while the service runs headless.
Create the system-wide unit file:
sudo nano /etc/systemd/system/openclaw.service
Paste the following production configuration:
[Unit]
Description=OpenClaw AI Bridge Daemon
After=network.target
[Service]
Type=simple
User=paragon9
WorkingDirectory=/home/paragon9/ai-bridge/openclaw
# Wrap execution inside a fake PTY to satisfy Crestodian's isTTY environment check
ExecStart=/usr/bin/script -q -c "/usr/bin/node openclaw.mjs" /dev/null
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
The script -q -c "..." /dev/null pattern runs the command silently inside a PTY and discards the session log. Quiet, clean, satisfies the check.
Reload systemd, enable the service at boot, and start it:
sudo systemctl daemon-reload
sudo systemctl enable openclaw.service
sudo systemctl start openclaw.service
Phase 5: Provisioning the Persistent Gateway Service
With the core daemon running, provision the companion WebSocket engine and ensure it survives reboots unattended. There are two valid paths here — understand both.
Step 1: Generate the gateway unit file using OpenClaw's own toolchain.
This matters. Don't hand-write the gateway unit — let the application configure its own service boundary:
node openclaw.mjs gateway install
This generates a correctly scoped user-space unit at ~/.config/systemd/user/openclaw-gateway.service.
Step 2: Enable user lingering.
This is the step most guides skip, and it's the one that causes the gateway to silently disappear after your SSH session ends. By default, systemd --user services stop when your login session closes. Enabling lingering forces user-space daemons to launch at boot time and persist independently of any active session:
sudo loginctl enable-linger paragon9
Alternative approach — if
systemctl --useris still unavailable:
On some headless environments (EC2, bare-metal VPS, or minimal installs without a proper D-Bus session),systemctl --usercommands fail withFailed to connect to bus: No medium foundeven after enabling linger. This meansXDG_RUNTIME_DIRisn't set. Add the following to your~/.bashrcand source it:export XDG_RUNTIME_DIR=/run/user/$(id -u)Then retry
loginctl enable-lingerand the gateway install. This is the environment variable that tells user-space systemd where to find its runtime socket.
Step 3: Enable and start the gateway service:
systemctl --user enable openclaw-gateway.service
systemctl --user start openclaw-gateway.service
Verify both components are communicating across the loopback:
node openclaw.mjs crestodian -m "status" --yes
Look for: Gateway: reachable (ws://127.0.0.1:18789, local loopback)
Phase 6: Secure Cross-Device Bridging
Because the Gateway is bound to the server's local loopback (127.0.0.1), external client requests from your primary engineering workstation are blocked by default. The right solution isn't to open a public firewall port. It's to build an SSH tunnel.
Step 1: Open the port-forward pipe on your workstation.
On your primary workstation, open a terminal and run:
ssh -L 18789:127.0.0.1:18789 <your-linux-user>@<your-linux-server-ip>
Keep this terminal open in the background while you work. The tunnel closes when the session closes.
Step 2: Extract your gateway access token from the server.
On the Linux server SSH session, pull the active token:
cat ~/.openclaw/openclaw.json | grep -i token
Copy the gwy_tok_xxxxxx string value.
Then generate the dashboard entry path:
node openclaw.mjs dashboard --no-open
Step 3: Complete the browser handshake on your workstation.
In your primary workstation browser, navigate to:
http://127.0.0.1:18789/#token=YOUR_EXTRACTED_GATEWAY_TOKEN
The browser will display a Device Pairing Required notification containing a device approval UUID. Copy that ID, return to the Linux Server terminal, and run:
node openclaw.mjs devices approve <pasted-device-uuid-string>
Refresh the browser. The transport handshake will fully synchronize and the Control UI dashboard will unlock.
Phase 7: Injecting Isolated Agent API Credentials
When your core agents first try to execute plans, they may halt with this authentication error:
⚠️ Agent failed before reply: No API key found for provider "openai-codex".
Agent execution contexts are completely isolated from your global system profile. They maintain an independent credential vault inside their specific subdirectories — they don't inherit your environment variables or system-level API keys.
Two approaches to resolve this:
The CLI Approach — initialize the setup wizard directly within the target agent directory:
node openclaw.mjs agents add main
The Static Copy Approach — if your root configuration already holds verified credentials, clone them directly into the agent profile:
mkdir -p ~/.openclaw/agents/main/agent/
cp ~/.openclaw/auth-profiles.json ~/.openclaw/agents/main/agent/auth-profiles.json
The static copy approach is faster when you're provisioning multiple agents from a known-good credential file. The CLI approach is safer when you're configuring fresh.
03. THE LEGACY SOURCE: WHAT STABILITY LOOKS LIKE
After working through all seven phases, the system state confirms what the process built:
paragon9@aios:~/ai-bridge/openclaw$ node openclaw.mjs crestodian -m "status" --yes
Crestodian online. Little claws, typed tools.
Config: valid
Path: /home/paragon9/.openclaw/openclaw.json
Default agent: main
Gateway: reachable (ws://127.0.0.1:18789, local loopback)
Status: local ready | idle
Both services are up. The core daemon runs under the system manager, auto-healing on failure with a five-second restart window. The gateway runs in user-space, isolated and independently restartable. The AI Bridge handles high-volume background tasks without a terminal, without a human watching, without any of the scaffolding that made it easy to run locally.
Monitor the health of each layer at any time:
# Inspect the core planning engine
sudo systemctl status openclaw.service
# Inspect the networking layer
systemctl --user status openclaw-gateway.service
# Stream active agent processes
node openclaw.mjs logs --follow
This is what I mean when I talk about building infrastructure that holds. The interactive version of OpenClaw was a proof of concept. The systemd version is a foundation. There is a meaningful difference between something that runs and something that stays running — between a tool and a service, between a local experiment and a production system you can walk away from.
At Paragon9, we don't ship proofs of concept to production and call them stable. We work through the layers — from OS installation choices all the way to cross-device handshakes — until the system behaves the same in a headless environment as it does in a terminal window. The discipline of doing that work thoroughly is what earns the right to call something infrastructure.
The Insight: A system that only works when you're watching isn't production-ready. It's a performance. Real infrastructure runs in the dark, restarts on failure, and doesn't need you to hold its hand. Build to that standard — from the first BIOS setting to the last device approval.
[SYSTEM NOTES]
- Platform: P9 Private Infrastructure — bare-metal physical hardware, minimized Ubuntu Server.
- OS Gotcha:
ubuntu-server-minimalomitslsof,curl,netstat, and other assumed utilities — provision them explicitly before deploying any application that relies on them. - Runtime: Node.js v23+ via direct binary invocation. No package manager wrapper in the
systemdcontext. - PTY Workaround:
script -q -c "..." /dev/nullwraps the Node process in a synthetic PTY to satisfy Crestodian'sisTTYcheck. - Gateway Persistence:
sudo loginctl enable-linger <user>is required for user-space services to survive SSH session closure. - Fallback: If
systemctl --userfails withNo medium found, setexport XDG_RUNTIME_DIR=/run/user/$(id -u)in~/.bashrc. - Cross-Device Access: SSH tunnel (
-L 18789:127.0.0.1:18789) + token extraction + device approval UUID — no open firewall ports. - Agent Credentials: Isolated per-agent credential vaults; does not inherit system environment variables.
- Status: OpenClaw AI Bridge — dual-service topology stable and running.
Stay Resilient.
— Greg Scott Kirk (Tek)
Part 1 of the Nine / 9OS series.
→ Next: LOG 010.2: Connecting OpenClaw to Microsoft Teams on a Headless Linux Server
This is the architecture Paragon9 installs for mid-market firms — modernized, documented, and built to run without its operator. If you want it running in your business, the conversation is free.