Skip to content

Latest commit

 

History

History
271 lines (196 loc) · 8.21 KB

File metadata and controls

271 lines (196 loc) · 8.21 KB

Installation Guide

This guide walks through deploying all three components: the approval server, the VPS client, and the n8n email workflow.

Prerequisites

Component Requirement
Approval server Node.js >= 18, npm
VPS client Python >= 3.8, pip
Email notifications n8n instance (self-hosted or cloud)
Networking The approval server must be reachable from both the VPS and from email link clicks (i.e., publicly accessible or via tunnel)

1. Server Setup

The approval server is an Express.js application that manages approval requests and serves the approve/deny token pages.

Clone the repository

git clone https://github.com/DatafyingTech/safe-rm.git
cd safe-rm

Install dependencies

cd server
npm install

Configure environment

cp ../examples/.env.example .env

Edit .env with your values:

PORT=3000
HOST=127.0.0.1
BASE_URL=https://safe-rm.example.com
TRUST_PROXY=true

# Generate a strong secret (shared with the client)
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
SAFE_RM_SECRET=your-generated-secret-here

DATA_DIR=./data
APPROVAL_TIMEOUT_SECONDS=600
CLAUDE_EVENT_TIMEOUT_SECONDS=600
RATE_LIMIT_RPM=100

# n8n webhook URLs (set after importing workflows)
N8N_DELETE_APPROVAL_WEBHOOK=https://your-n8n.example.com/webhook/delete-approval
N8N_CLAUDE_NOTIFICATION_WEBHOOK=https://your-n8n.example.com/webhook/claude-hook

Key points:

  • BASE_URL must be the public URL where users will click approve/deny links from their email.
  • SAFE_RM_SECRET must be identical on the server and client. Generate it with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  • TRUST_PROXY should be true if running behind nginx or another reverse proxy.

Start the server

# Production
node src/index.js

# Development (auto-restart on changes)
npm run dev

Run behind a reverse proxy (recommended)

The server listens on 127.0.0.1:3000 by default. Use nginx or Caddy to terminate TLS and proxy to it. An example nginx configuration is provided at examples/nginx.example.conf.

sudo cp ../examples/nginx.example.conf /etc/nginx/sites-available/safe-rm
# Edit the file: set your domain and certificate paths
sudo ln -s /etc/nginx/sites-available/safe-rm /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Run as a systemd service (optional)

Create /etc/systemd/system/safe-rm-server.service:

[Unit]
Description=safe-rm Approval Server
After=network.target

[Service]
Type=simple
User=saferm
WorkingDirectory=/opt/safe-rm/server
ExecStart=/usr/bin/node src/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now safe-rm-server

2. Client Setup

The client is a Python script that replaces rm on the VPS where your AI agent runs.

Automated installation

# Copy the client directory to the target VPS, then:
cd client
sudo ./install.sh

The installer will:

  1. Copy safe-rm to /usr/local/bin/safe-rm
  2. Install the requests Python library if missing
  3. Create a symlink /usr/local/bin/rm -> /usr/local/bin/safe-rm
  4. Prompt for your approval server URL and shared secret
  5. Write the config to /etc/safe-rm.conf (mode 600)
  6. Create /etc/profile.d/safe-rm.sh to ensure /usr/local/bin is first in $PATH

Manual installation

If you prefer to install manually:

# Copy the script
sudo cp client/safe-rm /usr/local/bin/safe-rm
sudo chmod +x /usr/local/bin/safe-rm

# Install Python dependency
pip3 install requests>=2.28.0

# Create symlink so 'rm' resolves to safe-rm
sudo ln -sf /usr/local/bin/safe-rm /usr/local/bin/rm

# Create config
sudo cp client/safe-rm.example.conf /etc/safe-rm.conf
sudo chmod 600 /etc/safe-rm.conf
# Edit /etc/safe-rm.conf with your API URL and secret

# Ensure /usr/local/bin is first in PATH
echo 'export PATH="/usr/local/bin:$PATH"' | sudo tee /etc/profile.d/safe-rm.sh
source /etc/profile.d/safe-rm.sh

Verify installation

# Check that 'rm' resolves to safe-rm
which rm
# Should output: /usr/local/bin/rm

# Test with dry-run (no API call, no actual deletion)
rm --dry-run -rf /var/www
# Should show: "RISKY DELETION DETECTED" and "dry-run: would request approval"

3. n8n Workflow Setup

n8n handles sending the approval emails. You need a running n8n instance.

Import a workflow

  1. Open your n8n instance.
  2. Go to Workflows > Add Workflow > Import from File.
  3. Choose one of the templates from n8n-workflows/:
File Use when...
delete-approval-smtp.json You have any SMTP email server
delete-approval-gmail.json You want to use Gmail via OAuth
claude-hook-notification.json You want Claude Code hook notifications

Configure email credentials

SMTP:

  1. In n8n, go to Settings > Credentials > Add Credential.
  2. Search for SMTP and enter your server details (host, port, user, password, SSL/TLS).
  3. In the workflow, click the email send node and select your SMTP credential.

Gmail OAuth:

  1. In n8n, go to Settings > Credentials > Add Credential.
  2. Search for Gmail OAuth2 and follow the OAuth authorization flow.
  3. In the workflow, click the email send node and select your Gmail credential.

Set the recipient email

  • SMTP workflows: The recipient defaults to the ADMIN_EMAIL n8n environment variable. Set it in your n8n environment, or edit the email node's sendTo field directly.
  • Gmail workflow: Edit the email node and replace admin@example.com with your address.

Activate and connect

  1. Toggle the workflow to Active.
  2. Copy the webhook URL from the Webhook Trigger node (e.g., https://your-n8n.example.com/webhook/delete-approval).
  3. Paste it into your approval server's .env as N8N_DELETE_APPROVAL_WEBHOOK (or N8N_CLAUDE_NOTIFICATION_WEBHOOK for the Claude hook workflow).
  4. Restart the approval server.

4. Testing the Full Flow

Once all three components are running:

# 1. SSH into your VPS (or set SAFE_RM_ACTIVE=1 to force guarded mode)
export SAFE_RM_ACTIVE=1

# 2. Run a risky command in dry-run mode first
rm --dry-run -rf /var/www/html

# 3. If dry-run output looks correct, run for real
rm -rf /var/www/html
# You should see "RISKY DELETION DETECTED" and "APPROVAL REQUESTED"

# 4. Check your email -- click Approve or Deny

# 5. The terminal should print "APPROVED - proceeding" or "DENIED - deletion blocked"

Quick server health check

curl https://safe-rm.example.com/health
# Expected: {"status":"ok","uptime":...}

5. Troubleshooting

"ERROR: Cannot reach API"

  • Verify the server is running: curl http://127.0.0.1:3000/health
  • Check that SAFE_RM_API in the client config matches the server's BASE_URL
  • If behind nginx, ensure the proxy is forwarding correctly

"ERROR: API returned 401"

  • The HMAC secret does not match. Ensure SAFE_RM_SECRET is identical on the server and client.
  • Check for trailing whitespace in the config file or .env.

safe-rm is not intercepting commands

  • Verify which rm returns /usr/local/bin/rm
  • Check that /usr/local/bin appears before /bin in your $PATH
  • Verify the current session is guarded: set SAFE_RM_ACTIVE=1 to test, or check that your SSH source IP matches ALLOWED_SOURCE_IPS

Email not arriving

  • Check n8n execution logs for errors
  • Verify the webhook URL in .env matches the URL shown in n8n
  • Test the n8n webhook manually: curl -X POST https://your-n8n.example.com/webhook/delete-approval -H 'Content-Type: application/json' -d '{"request_id":"test","command":"rm -rf /test"}'

Approve/Deny links not working

  • The BASE_URL in .env must be publicly accessible from wherever you read your email
  • Check that the URL in the email matches the server's BASE_URL
  • If running locally for development, use a tunnel (e.g., ngrok, cloudflared)

Uninstalling

To remove safe-rm from a VPS:

sudo ./client/uninstall.sh

This removes the binary, symlink, and profile script. You will be prompted about removing the config file. The system rm at /bin/rm is never modified and remains intact.