Skip to content

Commit 36e2230

Browse files
gcmsgclaude
andcommitted
feat: add systemd deployment support
Add systemd unit file with security hardening, production config template, env file for secrets, and install/uninstall scripts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b0339c1 commit 36e2230

File tree

6 files changed

+314
-1
lines changed

6 files changed

+314
-1
lines changed

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build test lint run clean proto fmt vet dashboard
1+
.PHONY: build test lint run clean proto fmt vet dashboard install uninstall
22

33
BINARY := peerclawd
44
BUILD_DIR := bin
@@ -33,6 +33,12 @@ vet:
3333
clean:
3434
rm -rf $(BUILD_DIR) coverage.out coverage.html *.db
3535

36+
install: build
37+
sudo deploy/systemd/install.sh $(BUILD_DIR)/$(BINARY)
38+
39+
uninstall:
40+
sudo deploy/systemd/uninstall.sh
41+
3642
docker-build:
3743
docker build -t peerclaw-server:latest .
3844

configs/peerclaw.production.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
server:
2+
http_addr: ":8080"
3+
cors_origins: [] # e.g. ["https://dashboard.example.com"]
4+
5+
auth:
6+
required: true
7+
8+
database:
9+
driver: "sqlite" # "sqlite" or "postgres"
10+
dsn: "/var/lib/peerclaw/peerclaw.db"
11+
12+
redis:
13+
addr: "localhost:6379"
14+
password: "${REDIS_PASSWORD}"
15+
db: 0
16+
17+
logging:
18+
level: "info"
19+
format: "json"
20+
21+
user_auth:
22+
enabled: true
23+
jwt_secret: "${JWT_SECRET}"
24+
access_ttl: "15m"
25+
refresh_ttl: "168h"
26+
bcrypt_cost: 12
27+
28+
bridge:
29+
a2a:
30+
enabled: true
31+
acp:
32+
enabled: true
33+
mcp:
34+
enabled: true
35+
36+
signaling:
37+
enabled: true
38+
turn:
39+
urls: [] # e.g. ["turn:turn.example.com:3478"]
40+
username: ""
41+
credential: "${TURN_CREDENTIAL}"
42+
43+
rate_limit:
44+
enabled: true
45+
requests_per_sec: 100
46+
burst_size: 200
47+
max_connections: 1000
48+
49+
observability:
50+
enabled: false
51+
otlp_endpoint: "localhost:4317"
52+
service_name: "peerclaw-gateway"
53+
traces_sampling: 0.1
54+
55+
audit_log:
56+
enabled: true
57+
output: "file:/var/log/peerclaw/audit.log"
58+
59+
federation:
60+
enabled: false
61+
node_name: "node-1"
62+
auth_token: "${FEDERATION_TOKEN}"
63+
peers: []

deploy/systemd/install.sh

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
#
3+
# PeerClaw systemd installation script.
4+
# Usage: sudo ./install.sh [path-to-binary]
5+
#
6+
# This script:
7+
# 1. Creates the peerclaw system user and group
8+
# 2. Installs the binary to /usr/local/bin/
9+
# 3. Sets up configuration and data directories
10+
# 4. Installs and enables the systemd unit
11+
#
12+
set -euo pipefail
13+
14+
BINARY="${1:-../../bin/peerclawd}"
15+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16+
17+
# Colors
18+
RED='\033[0;31m'
19+
GREEN='\033[0;32m'
20+
YELLOW='\033[1;33m'
21+
NC='\033[0m'
22+
23+
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
24+
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
25+
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
26+
27+
# Must run as root.
28+
if [[ $EUID -ne 0 ]]; then
29+
error "This script must be run as root (use sudo)"
30+
fi
31+
32+
# Check binary exists.
33+
if [[ ! -f "$BINARY" ]]; then
34+
error "Binary not found: $BINARY\n Build first with: make build"
35+
fi
36+
37+
info "Installing PeerClaw..."
38+
39+
# 1. Create system user.
40+
if ! id -u peerclaw &>/dev/null; then
41+
useradd --system --no-create-home --shell /usr/sbin/nologin --user-group peerclaw
42+
info "Created system user: peerclaw"
43+
else
44+
info "System user peerclaw already exists"
45+
fi
46+
47+
# 2. Install binary.
48+
install -m 0755 "$BINARY" /usr/local/bin/peerclawd
49+
info "Installed binary to /usr/local/bin/peerclawd"
50+
51+
# 3. Create directories.
52+
install -d -m 0755 -o peerclaw -g peerclaw /var/lib/peerclaw
53+
install -d -m 0755 -o peerclaw -g peerclaw /var/log/peerclaw
54+
install -d -m 0750 -o root -g peerclaw /etc/peerclaw
55+
info "Created directories: /var/lib/peerclaw, /var/log/peerclaw, /etc/peerclaw"
56+
57+
# 4. Install config (don't overwrite existing).
58+
if [[ ! -f /etc/peerclaw/config.yaml ]]; then
59+
install -m 0640 -o root -g peerclaw "$SCRIPT_DIR/../../configs/peerclaw.production.yaml" /etc/peerclaw/config.yaml
60+
info "Installed config to /etc/peerclaw/config.yaml"
61+
else
62+
warn "Config /etc/peerclaw/config.yaml already exists, skipping"
63+
fi
64+
65+
if [[ ! -f /etc/peerclaw/peerclaw.env ]]; then
66+
install -m 0640 -o root -g peerclaw "$SCRIPT_DIR/peerclaw.env" /etc/peerclaw/peerclaw.env
67+
info "Installed env file to /etc/peerclaw/peerclaw.env"
68+
warn "Edit /etc/peerclaw/peerclaw.env and set JWT_SECRET before starting"
69+
else
70+
warn "Env file /etc/peerclaw/peerclaw.env already exists, skipping"
71+
fi
72+
73+
# 5. Install systemd unit.
74+
install -m 0644 "$SCRIPT_DIR/peerclawd.service" /etc/systemd/system/peerclawd.service
75+
systemctl daemon-reload
76+
info "Installed systemd unit: peerclawd.service"
77+
78+
# 6. Enable (but don't start yet).
79+
systemctl enable peerclawd.service
80+
info "Enabled peerclawd.service"
81+
82+
echo ""
83+
info "Installation complete!"
84+
echo ""
85+
echo " Next steps:"
86+
echo " 1. Edit /etc/peerclaw/peerclaw.env and set JWT_SECRET:"
87+
echo " sudo nano /etc/peerclaw/peerclaw.env"
88+
echo ""
89+
echo " 2. Review /etc/peerclaw/config.yaml for your environment:"
90+
echo " sudo nano /etc/peerclaw/config.yaml"
91+
echo ""
92+
echo " 3. Start the service:"
93+
echo " sudo systemctl start peerclawd"
94+
echo ""
95+
echo " 4. Check status:"
96+
echo " sudo systemctl status peerclawd"
97+
echo " sudo journalctl -u peerclawd -f"
98+
echo ""

deploy/systemd/peerclaw.env

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# PeerClaw environment variables for systemd.
2+
# Copy to /etc/peerclaw/peerclaw.env and fill in production values.
3+
# This file is referenced by EnvironmentFile= in the unit file.
4+
5+
# JWT secret for user authentication (required, generate with: openssl rand -hex 32)
6+
JWT_SECRET=
7+
8+
# Redis password (leave empty if Redis has no auth)
9+
REDIS_PASSWORD=
10+
11+
# TURN server credential for WebRTC NAT traversal
12+
TURN_CREDENTIAL=
13+
14+
# Federation auth token (required if federation is enabled)
15+
FEDERATION_TOKEN=

deploy/systemd/peerclawd.service

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[Unit]
2+
Description=PeerClaw Agent Gateway
3+
Documentation=https://github.com/peerclaw/peerclaw-server
4+
After=network-online.target
5+
Wants=network-online.target
6+
# Uncomment if using PostgreSQL or Redis:
7+
# After=network-online.target postgresql.service redis.service
8+
9+
[Service]
10+
Type=simple
11+
User=peerclaw
12+
Group=peerclaw
13+
14+
ExecStart=/usr/local/bin/peerclawd -config /etc/peerclaw/config.yaml
15+
16+
WorkingDirectory=/var/lib/peerclaw
17+
RuntimeDirectory=peerclaw
18+
StateDirectory=peerclaw
19+
LogsDirectory=peerclaw
20+
ConfigurationDirectory=peerclaw
21+
22+
# Environment file for secrets (JWT_SECRET, REDIS_PASSWORD, etc.)
23+
EnvironmentFile=-/etc/peerclaw/peerclaw.env
24+
25+
# Restart policy
26+
Restart=on-failure
27+
RestartSec=5
28+
StartLimitIntervalSec=60
29+
StartLimitBurst=5
30+
31+
# Graceful shutdown
32+
KillMode=mixed
33+
KillSignal=SIGTERM
34+
TimeoutStopSec=30
35+
36+
# Security hardening
37+
NoNewPrivileges=yes
38+
ProtectSystem=strict
39+
ProtectHome=yes
40+
PrivateTmp=yes
41+
PrivateDevices=yes
42+
ProtectKernelTunables=yes
43+
ProtectKernelModules=yes
44+
ProtectControlGroups=yes
45+
RestrictSUIDSGID=yes
46+
RestrictNamespaces=yes
47+
RestrictRealtime=yes
48+
MemoryDenyWriteExecute=yes
49+
LockPersonality=yes
50+
51+
# Allow binding to port 8080 (non-privileged)
52+
AmbientCapabilities=
53+
CapabilityBoundingSet=
54+
55+
# File descriptor limits
56+
LimitNOFILE=65536
57+
LimitNPROC=4096
58+
59+
# Read-write paths for data and logs
60+
ReadWritePaths=/var/lib/peerclaw /var/log/peerclaw
61+
62+
[Install]
63+
WantedBy=multi-user.target

deploy/systemd/uninstall.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
#
3+
# PeerClaw systemd uninstall script.
4+
# Usage: sudo ./uninstall.sh [--purge]
5+
#
6+
# --purge Also removes config, data, logs, and the system user.
7+
#
8+
set -euo pipefail
9+
10+
PURGE=false
11+
if [[ "${1:-}" == "--purge" ]]; then
12+
PURGE=true
13+
fi
14+
15+
RED='\033[0;31m'
16+
GREEN='\033[0;32m'
17+
YELLOW='\033[1;33m'
18+
NC='\033[0m'
19+
20+
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
21+
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
22+
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
23+
24+
if [[ $EUID -ne 0 ]]; then
25+
error "This script must be run as root (use sudo)"
26+
fi
27+
28+
info "Uninstalling PeerClaw..."
29+
30+
# Stop and disable service.
31+
if systemctl is-active --quiet peerclawd 2>/dev/null; then
32+
systemctl stop peerclawd
33+
info "Stopped peerclawd service"
34+
fi
35+
36+
if systemctl is-enabled --quiet peerclawd 2>/dev/null; then
37+
systemctl disable peerclawd
38+
info "Disabled peerclawd service"
39+
fi
40+
41+
# Remove unit file.
42+
rm -f /etc/systemd/system/peerclawd.service
43+
systemctl daemon-reload
44+
info "Removed systemd unit"
45+
46+
# Remove binary.
47+
rm -f /usr/local/bin/peerclawd
48+
info "Removed /usr/local/bin/peerclawd"
49+
50+
if $PURGE; then
51+
warn "Purging all data, config, and logs..."
52+
rm -rf /var/lib/peerclaw
53+
rm -rf /var/log/peerclaw
54+
rm -rf /etc/peerclaw
55+
info "Removed /var/lib/peerclaw, /var/log/peerclaw, /etc/peerclaw"
56+
57+
if id -u peerclaw &>/dev/null; then
58+
userdel peerclaw 2>/dev/null || true
59+
info "Removed system user: peerclaw"
60+
fi
61+
fi
62+
63+
echo ""
64+
info "Uninstall complete."
65+
if ! $PURGE; then
66+
echo " Config, data, and logs were preserved."
67+
echo " Run with --purge to remove everything."
68+
fi

0 commit comments

Comments
 (0)