A practical, opinionated guide to running an autonomous AI assistant on a dedicated home lab server — with proper isolation, network control, and custom tooling.

Goals

You want a dedicated, always-on Linux machine that runs an AI agent (OpenClaw) with the following properties:

  1. Isolated execution environment — The agent runs under a dedicated Linux user (agent-openclaw) with strict permission boundaries. It cannot escalate to root, cannot access other users’ data, and operates within well-defined filesystem and process boundaries.

  2. Custom CLI tool integration — Your own GoLang tools (Sky, PowerCtl) are available to the agent, authenticated via a mix of environment variables and Bitwarden CLI-fetched secrets, injected at runtime without persisting tokens on disk in plaintext.

  3. Network-level control via Tailscale — The agent machine joins your tailnet but can only reach specific services (n8n, Bitwarden, Obsidian vault shares) and specific external API domains. All other network traffic is denied by default. You have full visibility and control over what the agent can access.

  4. Hardened OS — The Ubuntu Server 24.04 LTS installation follows security best practices: minimal attack surface, automatic security updates, audit logging, and strict firewall rules.

  5. OpenClaw as the runtime — The agent uses OpenClaw’s Gateway, channels, and skills platform. It runs as a systemd user service, restarts on failure, and exposes its Gateway only over Tailscale.


Steps Overview

  1. Base Ubuntu Server Installation & Hardening
  2. User Accounts & Isolation Model
  3. Tailscale Network Control
  4. Secrets Management
  5. Installing Custom CLI Tools
  6. Installing OpenClaw
  7. systemd Service Configuration
  8. AppArmor Profile (Optional but Recommended)
  9. Operational Checklist

Part 1: Base Ubuntu Server Installation & Hardening

1.1 — Minimal Installation

Start with a Ubuntu Server 24.04 LTS minimal installation. During install:

  • Choose “Ubuntu Server (minimized)” if offered
  • Set up LVM with encryption (LUKS) for the root partition — this protects data at rest if the physical machine is stolen
  • Create a single admin user (e.g., kristoffer) — this is your human operator account, not the agent
  • Enable OpenSSH server during install

After first boot, update everything:

sudo apt update && sudo apt upgrade -y
sudo apt install -y unattended-upgrades apt-listchanges

1.2 — Enable Automatic Security Updates

Configure unattended-upgrades to automatically apply security patches:

sudo dpkg-reconfigure -plow unattended-upgrades

Verify the configuration:

cat /etc/apt/apt.conf.d/20auto-upgrades

Should contain:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots if needed (for kernel updates):

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

1.3 — SSH Hardening

Edit /etc/ssh/sshd_config:

sudo tee /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Disable password auth — keys only
PasswordAuthentication no
ChallengeResponseAuthentication no

# Disable root login
PermitRootLogin no

# Limit to your admin user only
AllowUsers kristoffer

# Reduce attack surface
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
EOF

sudo systemctl restart sshd

Make sure your SSH key is in ~kristoffer/.ssh/authorized_keys before disabling password auth.

1.4 — Firewall with UFW

Set up a strict firewall. The agent machine should only be reachable via Tailscale, not on the LAN directly:

sudo ufw default deny incoming
sudo ufw default deny outgoing

# Allow SSH from LAN as fallback (restrict to your subnet)
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp

# Allow Tailscale interface (all traffic over tailnet is controlled by ACLs)
sudo ufw allow in on tailscale0
sudo ufw allow out on tailscale0

# Allow DNS resolution
sudo ufw allow out 53/udp
sudo ufw allow out 53/tcp

# Allow HTTPS out (needed for API calls, package management)
sudo ufw allow out 443/tcp

# Allow HTTP out (some package repos)
sudo ufw allow out 80/tcp

# Allow Tailscale's own UDP traffic (WireGuard)
sudo ufw allow out 41641/udp

sudo ufw enable

Note: We allow outgoing HTTPS/HTTP here at the OS level but will use Tailscale ACLs to control which hosts the agent can actually reach. UFW is the coarse filter; Tailscale ACLs are the fine-grained one.

1.5 — Install Essential Packages

sudo apt install -y \
  build-essential \
  curl \
  git \
  jq \
  tmux \
  htop \
  auditd \
  fail2ban \
  apparmor \
  apparmor-utils \
  acl \
  rsync

1.6 — Enable Audit Logging

The audit daemon records system calls and can track what the agent user does:

sudo systemctl enable auditd
sudo systemctl start auditd

# Add rules to monitor the agent user's activities
sudo tee /etc/audit/rules.d/agent-openclaw.rules << 'EOF'
# Monitor all commands executed by the agent user (UID will be set after user creation)
# We'll update this after creating the agent user
-a always,exit -F arch=b64 -S execve -F uid=2001 -k agent-exec
-a always,exit -F arch=b64 -S openat -F uid=2001 -F dir=/etc -k agent-etc-access
-a always,exit -F arch=b64 -S openat -F uid=2001 -F dir=/home/kristoffer -k agent-home-snoop
EOF

1.7 — Configure fail2ban

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Default config protects SSH. Good enough for a home lab.


Part 2: User Accounts & Isolation Model

The core principle: the agent runs as a dedicated, unprivileged Linux user with no sudo access and carefully controlled filesystem permissions.

2.1 — Create the Agent User

# Create a system group for agent users
sudo groupadd --gid 2000 agents

# Create the dedicated agent user
sudo useradd \
  --uid 2001 \
  --gid agents \
  --create-home \
  --home-dir /home/agent-openclaw \
  --shell /bin/bash \
  --comment "OpenClaw AI Agent" \
  agent-openclaw

# Lock the password — no direct login, only su/sudo from admin
sudo passwd -l agent-openclaw

Now update the audit rules with the correct UID:

sudo augenrules --load

2.2 — Directory Structure

Create a clean workspace layout for the agent:

# Agent home structure
sudo -u agent-openclaw mkdir -p /home/agent-openclaw/{.config,.local/bin,workspace,tools,secrets}

# The .openclaw directory (OpenClaw's data)
sudo -u agent-openclaw mkdir -p /home/agent-openclaw/.openclaw/{workspace/skills,credentials}

# Lock down permissions — agent owns their home, nobody else reads it
sudo chmod 750 /home/agent-openclaw
sudo chmod 700 /home/agent-openclaw/secrets

2.3 — Restrict Agent Filesystem Access

Use filesystem permissions and ACLs to ensure the agent user cannot wander:

# Agent cannot read other users' homes
sudo chmod 750 /home/kristoffer

# Agent cannot write to system directories (already the case by default, but verify)
# Agent cannot access /root
sudo chmod 700 /root

# Create a shared directory for files you want the agent to access
sudo mkdir -p /srv/agent-shared
sudo chown kristoffer:agents /srv/agent-shared
sudo chmod 2770 /srv/agent-shared  # setgid so new files inherit group

2.4 — Restrict Process Visibility

Prevent the agent user from seeing other users’ processes:

# Mount /proc with hidepid
echo "proc /proc proc defaults,hidepid=2,gid=$(getent group agents | cut -d: -f3) 0 0" | \
  sudo tee -a /etc/fstab

# Apply now
sudo mount -o remount,hidepid=2,gid=2000 /proc

With hidepid=2, the agent-openclaw user can only see its own processes, not yours or other services'.

2.5 — Resource Limits with systemd and cgroups

Prevent the agent from consuming all system resources. We’ll configure this later when we set up the systemd service, but the key limits will be:

  • Memory: Capped at e.g. 4GB (adjust for your hardware)
  • CPU: Limited to specific cores or percentage
  • Max open files: Reasonable limit
  • No ability to create new user namespaces (prevents container escapes)

Part 3: Tailscale Network Control

This is where you get surgical control over what the agent machine can talk to.

3.1 — Install Tailscale

curl -fsSL https://tailscale.com/install.sh | sh

# Start tailscale and authenticate
sudo tailscale up --ssh

3.2 — Tag the Agent Machine

In your Tailscale admin console (https://login.tailscale.com/admin/acls), you’ll tag this machine. Tags are the foundation of ACL control.

First, register the tag in your ACL policy:

{
  "tagOwners": {
    "tag:agent": ["autogroup:admin"],
    "tag:homelab": ["autogroup:admin"],
    "tag:services": ["autogroup:admin"],
  },
}

Apply the tag to the agent machine:

sudo tailscale up --advertise-tags=tag:agent

Tag your other machines appropriately:

  • Your n8n server → tag:services
  • Your Bitwarden/Vaultwarden instance → tag:services
  • Your NAS/Obsidian file server → tag:homelab
  • Your personal machines → no tag (they’re your user identity)

3.3 — Tailscale ACL Policy

This is the core of your network control. The ACL policy defines exactly what the agent machine can reach:

{
  "tagOwners": {
    "tag:agent": ["autogroup:admin"],
    "tag:homelab": ["autogroup:admin"],
    "tag:services": ["autogroup:admin"],
  },

  "acls": [
    // Your personal machines can reach everything
    {
      "action": "accept",
      "src": ["autogroup:member"],
      "dst": ["*:*"],
    },

    // Agent can reach specific services on your tailnet
    {
      "action": "accept",
      "src": ["tag:agent"],
      "dst": [
        "tag:services:8080", // n8n webhook port
        "tag:services:443", // Vaultwarden/Bitwarden HTTPS
        "tag:homelab:445", // SMB for Obsidian vault
        "tag:homelab:22", // SSH/rsync for file access
      ],
    },

    // Agent can reach the OpenClaw Gateway on itself (loopback via tailscale)
    {
      "action": "accept",
      "src": ["tag:agent"],
      "dst": ["tag:agent:18789"],
    },

    // Services can talk back to agent (for webhooks from n8n, etc.)
    {
      "action": "accept",
      "src": ["tag:services"],
      "dst": ["tag:agent:18789"],
    },
  ],

  // DNS configuration — control what domains the agent can resolve
  "dns": {
    "nameservers": ["100.100.100.100"], // Tailscale's MagicDNS
    "domains": ["your-tailnet.ts.net"],
    "magicDNS": true,
  },
}

3.4 — Controlling Internet Access (Exit Node Pattern)

Tailscale ACLs control tailnet traffic, but what about the agent reaching external APIs (Anthropic, OpenAI, GitHub, etc.)? There are two approaches:

Option A: Allow outbound HTTPS at the OS level (simpler)

You already have UFW allowing outbound 443. The agent process can reach external APIs directly. This is the simplest approach and works well if you trust the agent not to exfiltrate data to random domains.

Option B: Proxy through an exit node with DNS filtering (more control)

Set up a machine on your tailnet as an exit node with DNS-level filtering (e.g., Pi-hole or NextDNS):

# On your DNS/proxy machine
sudo tailscale up --advertise-exit-node --advertise-tags=tag:services

# On the agent machine — route all traffic through the exit node
sudo tailscale up --exit-node=<exit-node-ip> --advertise-tags=tag:agent

Then configure Pi-hole/NextDNS to only allow specific domains:

  • api.anthropic.com
  • api.openai.com
  • github.com
  • registry.npmjs.org
  • pypi.org
  • Your custom API domains

This guide assumes Option A as the starting point. You can layer on Option B later.

3.5 — Mount Obsidian Vault via Tailscale

Since the agent needs access to your Obsidian vault on another machine, set up an SMB or NFS mount over Tailscale:

# Install CIFS utilities
sudo apt install -y cifs-utils

# Create mount point
sudo mkdir -p /mnt/obsidian-vault

# Create a credentials file (readable only by root)
sudo tee /etc/samba/.obsidian-creds << 'EOF'
username=your-smb-user
password=your-smb-password
EOF
sudo chmod 600 /etc/samba/.obsidian-creds

# Add fstab entry — mounts over Tailscale IP, accessible by agent user
echo "//your-nas.your-tailnet.ts.net/obsidian /mnt/obsidian-vault cifs credentials=/etc/samba/.obsidian-creds,uid=agent-openclaw,gid=agents,file_mode=0640,dir_mode=0750,nofail 0 0" | \
  sudo tee -a /etc/fstab

sudo mount -a

Now the agent can read the vault at /mnt/obsidian-vault but the mount credentials are stored securely.


Part 4: Secrets Management

Your GoLang tools (Sky, PowerCtl) use a mix of environment variables and Bitwarden CLI for authentication. Here’s how to set this up securely for the agent.

4.1 — Install Bitwarden CLI

# Install as the agent user
sudo -u agent-openclaw bash << 'SETUP'
cd /home/agent-openclaw
curl -fsSL "https://vault.bitwarden.com/download/?app=cli&platform=linux" -o bw.zip
unzip bw.zip -d .local/bin/
rm bw.zip
chmod +x .local/bin/bw
SETUP

4.2 — Bitwarden Session Management

The agent should never store the Bitwarden master password on disk. Instead, use a systemd-managed session that unlocks at service start via a pre-stored API key or session token.

Create a wrapper script that the agent’s tools will use to fetch secrets:

sudo -u agent-openclaw tee /home/agent-openclaw/tools/bw-get-secret.sh << 'SCRIPT'
#!/usr/bin/env bash
# Fetch a secret from Bitwarden by item name and field
# Usage: bw-get-secret.sh "item-name" [field-name]
# If field-name is omitted, returns the password field

set -euo pipefail

ITEM_NAME="$1"
FIELD_NAME="${2:-password}"

if [ -z "${BW_SESSION:-}" ]; then
  echo "ERROR: BW_SESSION not set. Bitwarden is not unlocked." >&2
  exit 1
fi

if [ "$FIELD_NAME" = "password" ]; then
  bw get password "$ITEM_NAME" --session "$BW_SESSION"
elif [ "$FIELD_NAME" = "username" ]; then
  bw get username "$ITEM_NAME" --session "$BW_SESSION"
else
  bw get item "$ITEM_NAME" --session "$BW_SESSION" | jq -r ".fields[] | select(.name==\"$FIELD_NAME\") | .value"
fi
SCRIPT

sudo chmod 750 /home/agent-openclaw/tools/bw-get-secret.sh

4.3 — Environment File for Static Secrets

For environment variables that don’t change often, use a systemd EnvironmentFile that is readable only by root and the agent:

# Create the directory first
sudo mkdir -p /etc/openclaw

# Then create the environment file
sudo tee /etc/openclaw/agent.env << 'EOF'
# OpenClaw configuration
ANTHROPIC_API_KEY=sk-ant-xxxxx
OPENCLAW_GATEWAY_PORT=18789

# Static tool tokens (rotate periodically)
SKY_API_ENDPOINT=https://your-api.example.com
POWERCTL_CONFIG_PATH=/home/agent-openclaw/.config/powerctl/config.yaml

# Bitwarden API key for automatic unlock
BW_CLIENTID=user.xxxxx
BW_CLIENTSECRET=xxxxx
EOF

# Set secure permissions
sudo chmod 600 /etc/openclaw/agent.env
sudo chown root:agents /etc/openclaw/agent.env
sudo chmod 640 /etc/openclaw/agent.env

4.4 — Runtime Secret Injection Script

Create a startup script that unlocks Bitwarden and populates dynamic secrets before OpenClaw starts:

sudo -u agent-openclaw tee /home/agent-openclaw/tools/agent-startup.sh << 'SCRIPT'
#!/usr/bin/env bash
# Runs before OpenClaw Gateway starts
# Unlocks Bitwarden and exports dynamic secrets

set -euo pipefail

export PATH="/home/agent-openclaw/.local/bin:$PATH"

# Login to Bitwarden using API key (set via EnvironmentFile)
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
  bw login --apikey 2>/dev/null || true
  export BW_SESSION=$(bw unlock --passwordenv BW_MASTER_PASSWORD --raw 2>/dev/null || echo "")

  if [ -z "$BW_SESSION" ]; then
    echo "WARNING: Could not unlock Bitwarden vault. Dynamic secrets unavailable." >&2
  else
    echo "Bitwarden vault unlocked successfully."
    # Fetch dynamic secrets and export them
    export SKY_ACCESS_TOKEN=$(bw get password "Sky CLI Token" --session "$BW_SESSION" 2>/dev/null || echo "")
    export POWERCTL_TOKEN=$(bw get password "PowerCtl API Token" --session "$BW_SESSION" 2>/dev/null || echo "")
  fi
fi

# Execute the actual command (OpenClaw gateway)
exec "$@"
SCRIPT

sudo chmod 750 /home/agent-openclaw/tools/agent-startup.sh

Part 5: Installing Custom CLI Tools

5.1 — Install Your GoLang Tools

Since Sky and PowerCtl are your own GoLang binaries, install them into the agent’s local bin:

# Copy pre-built binaries (adjust paths to where you build/distribute them)
sudo cp /path/to/sky /home/agent-openclaw/.local/bin/sky
sudo cp /path/to/powerctl /home/agent-openclaw/.local/bin/powerctl

sudo chown agent-openclaw:agents /home/agent-openclaw/.local/bin/{sky,powerctl}
sudo chmod 750 /home/agent-openclaw/.local/bin/{sky,powerctl}

Alternatively, if you want the agent machine to pull them from a Git repo or your own release server:

# If you publish releases on GitHub
sudo -u agent-openclaw bash << 'INSTALL'
cd /home/agent-openclaw/.local/bin
curl -fsSL https://github.com/your-org/sky/releases/latest/download/sky-linux-amd64 -o sky
curl -fsSL https://github.com/your-org/powerctl/releases/latest/download/powerctl-linux-amd64 -o powerctl
chmod +x sky powerctl
INSTALL

5.2 — Configure Tool Access for OpenClaw

OpenClaw needs to know about these tools. Add them to the agent’s workspace:

sudo -u agent-openclaw tee /home/agent-openclaw/.openclaw/workspace/TOOLS.md << 'TOOLSDOC'
# Available CLI Tools

## Sky
Location: `~/.local/bin/sky`
Purpose: [Your description of what Sky does]
Usage: `sky [command] [flags]`
Authentication: Uses $SKY_ACCESS_TOKEN environment variable.

## PowerCtl
Location: `~/.local/bin/powerctl`
Purpose: [Your description of what PowerCtl does]
Usage: `powerctl [command] [flags]`
Authentication: Uses $POWERCTL_TOKEN environment variable and config at $POWERCTL_CONFIG_PATH.

## Bitwarden CLI
Location: `~/.local/bin/bw`
Purpose: Fetch secrets from your Bitwarden vault.
Usage: `bw get password "item-name"` (requires $BW_SESSION to be set)
Note: Session is established at agent startup. Use `bw-get-secret.sh` wrapper for convenience.
TOOLSDOC

5.3 — PATH Configuration

Ensure the agent’s PATH includes the local bin:

sudo -u agent-openclaw tee -a /home/agent-openclaw/.bashrc << 'BASHRC'

# Agent tool paths
export PATH="$HOME/.local/bin:$PATH"

# Node.js (installed in Part 6)
export PATH="$HOME/.local/share/nvm/versions/node/v22/bin:$PATH"
BASHRC

Part 6: Installing OpenClaw

6.1 — Install Node.js 22

OpenClaw requires Node.js >= 22:

# Install nvm as the agent user
sudo -u agent-openclaw bash << 'NVMSETUP'
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
nvm alias default 22

# Install pnpm globally
npm install -g pnpm
NVMSETUP

6.2 — Install OpenClaw

sudo -u agent-openclaw bash << 'CLAWSETUP'
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"

# Install OpenClaw globally
npm install -g openclaw@latest

# Or from source for more control:
# git clone https://github.com/openclaw/openclaw.git ~/openclaw-src
# cd ~/openclaw-src
# pnpm install
# pnpm ui:build
# pnpm build
CLAWSETUP

6.3 — Configure OpenClaw

Create the minimal configuration:

sudo -u agent-openclaw mkdir -p /home/agent-openclaw/.openclaw

sudo -u agent-openclaw tee /home/agent-openclaw/.openclaw/openclaw.json << 'CONFIG'
{
  "agent": {
    "model": "anthropic/claude-opus-4-5"
  },
  "gateway": {
    "port": 18789,
    "bind": "loopback",
    "tailscale": {
      "mode": "serve"
    },
    "auth": {
      "mode": "password",
      "password": "CHANGE_ME_TO_SOMETHING_STRONG"
    }
  },
  "channels": {
    "telegram": {
      "botToken": "YOUR_TELEGRAM_BOT_TOKEN"
    }
  },
  "browser": {
    "enabled": false
  }
}
CONFIG

Key decisions here:

  • bind: "loopback" — Gateway only listens on 127.0.0.1. External access is via Tailscale Serve only.
  • tailscale.mode: "serve" — Exposes the Gateway dashboard over your tailnet with HTTPS, but not to the public internet.
  • auth.mode: "password" — Adds a password requirement even for tailnet access.
  • browser.enabled: false — Disabled by default on a headless server. Enable if you install a headless Chrome later.

6.4 — Run the Onboarding Wizard

sudo -iu agent-openclaw openclaw onboard

Follow the wizard to set up your preferred channels (Telegram, Discord, etc.), identity, and workspace.


Part 7: systemd Service Configuration

7.1 — Create the Service Unit

sudo tee /etc/systemd/system/openclaw-agent.service << 'UNIT'
[Unit]
Description=OpenClaw AI Agent Gateway
After=network-online.target tailscaled.service
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
User=agent-openclaw
Group=agents

# Environment files
EnvironmentFile=/etc/openclaw/agent.env

# Use the startup script to inject dynamic secrets
ExecStart=/home/agent-openclaw/tools/agent-startup.sh \
  /home/agent-openclaw/.nvm/versions/node/v22/bin/node \
  /home/agent-openclaw/.nvm/versions/node/v22/bin/openclaw \
  gateway --port 18789 --verbose

# Restart policy
Restart=on-failure
RestartSec=10

# Working directory
WorkingDirectory=/home/agent-openclaw

# Resource limits (adjust for your hardware)
MemoryMax=4G
MemoryHigh=3G
CPUQuota=200%
TasksMax=256
LimitNOFILE=8192

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=tmpfs
BindPaths=/home/agent-openclaw
BindReadOnlyPaths=/mnt/obsidian-vault
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
SystemCallArchitectures=native

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw-agent

[Install]
WantedBy=multi-user.target
UNIT

This is a heavily hardened service configuration. Key security properties:

  • NoNewPrivileges=true — The agent process cannot gain additional privileges
  • ProtectSystem=strict — The entire filesystem is read-only except explicitly allowed paths
  • ProtectHome=tmpfs — All home directories are hidden, then we bind-mount only the agent’s home
  • BindPaths — Only the agent’s home is writable
  • BindReadOnlyPaths — Obsidian vault is mounted read-only
  • RestrictNamespaces=true — Cannot create user/network namespaces (prevents container escape patterns)
  • MemoryMax=4G — Hard memory cap

7.2 — Enable and Start

sudo systemctl daemon-reload
sudo systemctl enable openclaw-agent.service
sudo systemctl start openclaw-agent.service

# Check status
sudo systemctl status openclaw-agent.service
sudo journalctl -u openclaw-agent.service -f

7.3 — Log Monitoring

Set up structured log access:

# View agent logs
sudo journalctl -u openclaw-agent.service --since "1 hour ago"

# View audit logs for the agent user
sudo ausearch -ua 2001 --start today

# Follow live
sudo journalctl -u openclaw-agent.service -f

For an additional layer of mandatory access control, create an AppArmor profile for the OpenClaw process:

sudo tee /etc/apparmor.d/openclaw-agent << 'APPARMOR'
#include <tunables/global>

profile openclaw-agent /home/agent-openclaw/.nvm/versions/node/*/bin/node {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Node.js and OpenClaw
  /home/agent-openclaw/.nvm/** rix,
  /home/agent-openclaw/.local/bin/** rix,

  # Agent workspace (read/write)
  /home/agent-openclaw/ r,
  /home/agent-openclaw/** rw,
  /home/agent-openclaw/.openclaw/** rw,

  # Obsidian vault (read-only)
  /mnt/obsidian-vault/ r,
  /mnt/obsidian-vault/** r,

  # Shared directory
  /srv/agent-shared/** rw,

  # Deny sensitive paths
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /home/kristoffer/** rw,
  deny /root/** rw,

  # Network
  network inet stream,
  network inet dgram,
  network inet6 stream,
  network inet6 dgram,

  # Temp
  /tmp/** rw,
  /var/tmp/** rw,
}
APPARMOR

sudo apparmor_parser -r /etc/apparmor.d/openclaw-agent

Part 9: Operational Checklist

Verification Steps

After completing the setup, verify each layer:

# 1. User isolation — agent cannot read your home
sudo -u agent-openclaw ls /home/kristoffer
# Should: Permission denied

# 2. Process isolation — agent cannot see your processes
sudo -u agent-openclaw ps aux
# Should: Only see agent-openclaw's own processes

# 3. Tailscale connectivity — agent can reach allowed services
sudo -u agent-openclaw curl -s https://your-n8n.your-tailnet.ts.net/healthz
# Should: 200 OK

# 4. Tailscale isolation — agent cannot reach random tailnet machines
sudo -u agent-openclaw curl -s https://your-desktop.your-tailnet.ts.net:22
# Should: Timeout/refused (if not in ACL)

# 5. Tools work — Sky and PowerCtl are available
sudo -u agent-openclaw /home/agent-openclaw/.local/bin/sky --version
sudo -u agent-openclaw /home/agent-openclaw/.local/bin/powerctl --version

# 6. OpenClaw is running
sudo systemctl status openclaw-agent.service
curl -s http://127.0.0.1:18789/health

# 7. Audit trail is recording
sudo ausearch -ua 2001 --start today -i | head -20

Maintenance Tasks

TaskFrequencyCommand
OS security updatesDaily (auto)unattended-upgrades handles this
OpenClaw updatesWeekly/as neededsudo -iu agent-openclaw npm update -g openclaw
Rotate API keysMonthlyUpdate /etc/openclaw/agent.env and Bitwarden
Review audit logsWeeklysudo ausearch -ua 2001 --start recent -i
Check Tailscale ACLsAfter changesTailscale admin console
Backup agent workspaceWeeklyrsync to your NAS
Update Sky/PowerCtlAs releasedReplace binaries in .local/bin/

Backup the Agent State

# Cron job to backup the agent's workspace and config
sudo tee /etc/cron.d/openclaw-backup << 'CRON'
0 3 * * * root rsync -az --delete /home/agent-openclaw/.openclaw/ /srv/backups/openclaw/ 2>&1 | logger -t openclaw-backup
CRON

Architecture Summary

┌─────────────────────────────────────────────────────────────────┐
│                    Your Tailnet (WireGuard)                     │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────────────┐ │
│  │ Your Desktop │  │   NAS/Files  │  │  Services (n8n, BW)   │ │
│  │  (personal)  │  │ tag:homelab  │  │    tag:services        │ │
│  └──────┬───────┘  └──────┬───────┘  └───────────┬───────────┘ │
│         │                 │                       │             │
│         │    Tailscale ACLs control all traffic   │             │
│         │                 │                       │             │
│  ┌──────┴─────────────────┴───────────────────────┴──────────┐  │
│  │              Agent Machine (tag:agent)                     │  │
│  │              Ubuntu 24.04 LTS — Hardened                   │  │
│  │                                                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │  user: agent-openclaw (uid 2001)                     │  │  │
│  │  │                                                      │  │  │
│  │  │  ┌─────────────┐  ┌────────┐  ┌──────────────────┐  │  │  │
│  │  │  │  OpenClaw    │  │  Sky   │  │    PowerCtl      │  │  │  │
│  │  │  │  Gateway     │  │  CLI   │  │    CLI           │  │  │  │
│  │  │  │  :18789      │  │        │  │                  │  │  │  │
│  │  │  └──────┬───────┘  └───┬────┘  └────────┬─────────┘  │  │  │
│  │  │         │              │                 │            │  │  │
│  │  │         └──────────────┴─────────────────┘            │  │  │
│  │  │                   │                                   │  │  │
│  │  │    Secrets: env vars + Bitwarden CLI runtime fetch    │  │  │
│  │  │    Filesystem: home + /mnt/obsidian-vault (ro)        │  │  │
│  │  │    Resources: 4GB RAM, 200% CPU, 256 tasks max        │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  │                                                            │  │
│  │  Security layers:                                          │  │
│  │    ✓ UFW firewall (coarse)                                │  │
│  │    ✓ Tailscale ACLs (fine-grained)                        │  │
│  │    ✓ systemd sandboxing (ProtectSystem, NoNewPrivileges)  │  │
│  │    ✓ AppArmor MAC profile                                 │  │
│  │    ✓ hidepid=2 (process isolation)                        │  │
│  │    ✓ auditd (full command logging)                        │  │
│  │    ✓ LUKS disk encryption                                 │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                 │
│         External APIs (Anthropic, OpenAI, etc.)                 │
│         ← Allowed via UFW outbound HTTPS                        │
│         ← Optional: DNS filtering via exit node                 │
└─────────────────────────────────────────────────────────────────┘

What’s Next

Once this foundation is running, you can extend it:

  • Add more agent users — Create agent-codex, agent-research, etc., each with their own UID, Tailscale tag, and ACL rules
  • Enable OpenClaw browser control — Install headless Chromium and set browser.enabled: true for web automation
  • Set up OpenClaw skills — Build custom skills that wrap your Sky and PowerCtl tools with natural language interfaces
  • Add n8n webhooks — Use OpenClaw’s webhook surface to receive triggers from your n8n automations
  • DNS filtering — Deploy a Pi-hole exit node for granular domain-level control over the agent’s internet access
  • Prometheus/Grafana monitoring — Monitor the agent’s resource usage, API costs, and activity patterns

The key insight of this architecture is defense in depth: no single layer is responsible for security. The agent is constrained by Linux user permissions, systemd sandboxing, AppArmor mandatory access control, Tailscale network ACLs, and OS-level firewalling — all working together. If any one layer has a gap, the others compensate.