◀── Back to Logs
MAY 2026 [SYSTEMS] 15 min read #DEVOPS#AI ENGINEERING#SYSTEMD#OPENCLAW#INFRASTRUCTURE#LINUX#BARE METAL AI

LOG 010.1: Architecting a Headless AI Bridge — Standardizing OpenClaw under Systemd

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:

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-minimal is 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 requires lsof (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 unhelpful ENOENT error. 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 --user is still unavailable:
On some headless environments (EC2, bare-metal VPS, or minimal installs without a proper D-Bus session), systemctl --user commands fail with Failed to connect to bus: No medium found even after enabling linger. This means XDG_RUNTIME_DIR isn't set. Add the following to your ~/.bashrc and source it:

export XDG_RUNTIME_DIR=/run/user/$(id -u)

Then retry loginctl enable-linger and 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]

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

// NEXT ACTION

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.