/ scripts / deploy-radicle-systemd.sh
deploy-radicle-systemd.sh
  1  #!/bin/bash
  2  # Radicle Node Systemd Service Deployment Script
  3  # Version: 1.0.0
  4  # Date: 2026-01-26
  5  #
  6  # Purpose: Deploy Radicle node as a production systemd service
  7  # Usage: ./deploy-radicle-systemd.sh [--user USERNAME] [--port PORT]
  8  
  9  set -euo pipefail
 10  
 11  # Default configuration
 12  RAD_USER="${RAD_USER:-devops}"
 13  RAD_PORT="${RAD_PORT:-8776}"
 14  RAD_BINARY="/usr/local/bin/radicle-node"
 15  SERVICE_FILE="/etc/systemd/system/radicle-node.service"
 16  
 17  # Colors for output
 18  RED='\033[0;31m'
 19  GREEN='\033[0;32m'
 20  YELLOW='\033[1;33m'
 21  NC='\033[0m' # No Color
 22  
 23  # Parse command line arguments
 24  while [[ $# -gt 0 ]]; do
 25    case $1 in
 26      --user)
 27        RAD_USER="$2"
 28        shift 2
 29        ;;
 30      --port)
 31        RAD_PORT="$2"
 32        shift 2
 33        ;;
 34      --help)
 35        echo "Usage: $0 [OPTIONS]"
 36        echo ""
 37        echo "Options:"
 38        echo "  --user USERNAME    User to run Radicle node (default: devops)"
 39        echo "  --port PORT        Port to listen on (default: 8776)"
 40        echo "  --help             Show this help message"
 41        exit 0
 42        ;;
 43      *)
 44        echo "Unknown option: $1"
 45        exit 1
 46        ;;
 47    esac
 48  done
 49  
 50  # Functions
 51  log_info() {
 52    echo -e "${GREEN}[INFO]${NC} $1"
 53  }
 54  
 55  log_warn() {
 56    echo -e "${YELLOW}[WARN]${NC} $1"
 57  }
 58  
 59  log_error() {
 60    echo -e "${RED}[ERROR]${NC} $1"
 61  }
 62  
 63  check_root() {
 64    if [ "$EUID" -ne 0 ]; then
 65      log_error "This script must be run as root (use sudo)"
 66      exit 1
 67    fi
 68  }
 69  
 70  check_prerequisites() {
 71    log_info "Checking prerequisites..."
 72  
 73    # Check if radicle-node binary exists
 74    if [ ! -f "$RAD_BINARY" ]; then
 75      log_error "Radicle binary not found at $RAD_BINARY"
 76      log_error "Please install Radicle first: https://radicle.xyz/install"
 77      exit 1
 78    fi
 79  
 80    # Check if user exists
 81    if ! id "$RAD_USER" &>/dev/null; then
 82      log_error "User '$RAD_USER' does not exist"
 83      exit 1
 84    fi
 85  
 86    # Check if Radicle is initialized
 87    RAD_HOME=$(eval echo "~$RAD_USER")
 88    if [ ! -d "$RAD_HOME/.radicle" ]; then
 89      log_error "Radicle not initialized for user '$RAD_USER'"
 90      log_error "Run as $RAD_USER: rad auth"
 91      exit 1
 92    fi
 93  
 94    # Check if port is available
 95    if ss -tulpn | grep -q ":$RAD_PORT "; then
 96      log_warn "Port $RAD_PORT is already in use"
 97      log_warn "Existing radicle-node processes will be stopped"
 98    fi
 99  
100    log_info "Prerequisites check passed"
101  }
102  
103  stop_existing_processes() {
104    log_info "Stopping existing radicle-node processes..."
105  
106    # Find and stop radicle-node processes
107    PIDS=$(pgrep -f radicle-node || true)
108    if [ -n "$PIDS" ]; then
109      log_info "Found running processes: $PIDS"
110      pkill -f radicle-node || true
111      sleep 2
112  
113      # Force kill if still running
114      PIDS=$(pgrep -f radicle-node || true)
115      if [ -n "$PIDS" ]; then
116        log_warn "Force killing remaining processes"
117        pkill -9 -f radicle-node || true
118        sleep 1
119      fi
120  
121      log_info "Existing processes stopped"
122    else
123      log_info "No existing processes found"
124    fi
125  }
126  
127  create_service_file() {
128    log_info "Creating systemd service file..."
129  
130    RAD_HOME=$(eval echo "~$RAD_USER")
131    RAD_GROUP=$(id -gn "$RAD_USER")
132  
133    cat > "$SERVICE_FILE" <<EOF
134  [Unit]
135  Description=Radicle Node - Peer-to-peer code collaboration
136  Documentation=https://radicle.xyz/
137  After=network-online.target
138  Wants=network-online.target
139  
140  [Service]
141  Type=simple
142  User=$RAD_USER
143  Group=$RAD_GROUP
144  WorkingDirectory=$RAD_HOME
145  Environment="HOME=$RAD_HOME"
146  Environment="PATH=/usr/local/bin:/usr/bin:/bin"
147  ExecStart=$RAD_BINARY --listen 0.0.0.0:$RAD_PORT
148  Restart=on-failure
149  RestartSec=10
150  TimeoutStopSec=30
151  
152  # Security hardening
153  NoNewPrivileges=true
154  PrivateTmp=true
155  ProtectSystem=strict
156  ProtectHome=read-only
157  ReadWritePaths=$RAD_HOME/.radicle
158  
159  # Logging
160  StandardOutput=journal
161  StandardError=journal
162  SyslogIdentifier=radicle-node
163  
164  [Install]
165  WantedBy=multi-user.target
166  EOF
167  
168    log_info "Service file created at $SERVICE_FILE"
169  }
170  
171  enable_and_start_service() {
172    log_info "Reloading systemd daemon..."
173    systemctl daemon-reload
174  
175    log_info "Enabling service to start at boot..."
176    systemctl enable radicle-node.service
177  
178    log_info "Starting Radicle node service..."
179    systemctl start radicle-node.service
180  
181    sleep 3
182  
183    # Check if service started successfully
184    if systemctl is-active --quiet radicle-node.service; then
185      log_info "Service started successfully"
186    else
187      log_error "Service failed to start"
188      systemctl status radicle-node.service --no-pager -l
189      exit 1
190    fi
191  }
192  
193  verify_installation() {
194    log_info "Verifying installation..."
195  
196    # Check service status
197    if ! systemctl is-active --quiet radicle-node.service; then
198      log_error "Service is not running"
199      return 1
200    fi
201  
202    # Check if socket exists
203    RAD_HOME=$(eval echo "~$RAD_USER")
204    SOCKET_PATH="$RAD_HOME/.radicle/node/control.sock"
205  
206    sleep 2  # Give socket time to be created
207  
208    if [ ! -S "$SOCKET_PATH" ]; then
209      log_error "Socket not found at $SOCKET_PATH"
210      return 1
211    fi
212  
213    # Try to connect as the user
214    if ! su - "$RAD_USER" -c "rad node status >/dev/null 2>&1"; then
215      log_warn "Cannot connect to node (may need time to initialize)"
216    fi
217  
218    log_info "Installation verified"
219    log_info ""
220    log_info "Socket: $SOCKET_PATH"
221    log_info "Permissions: $(ls -l $SOCKET_PATH 2>/dev/null || echo 'pending')"
222  }
223  
224  print_summary() {
225    RAD_HOME=$(eval echo "~$RAD_USER")
226  
227    echo ""
228    echo "═══════════════════════════════════════════════════════════"
229    echo "  Radicle Node Systemd Service - Deployment Complete"
230    echo "═══════════════════════════════════════════════════════════"
231    echo ""
232    echo "Service Status:"
233    systemctl status radicle-node.service --no-pager -l | head -10
234    echo ""
235    echo "Quick Commands:"
236    echo "  Status:   sudo systemctl status radicle-node"
237    echo "  Restart:  sudo systemctl restart radicle-node"
238    echo "  Logs:     journalctl -u radicle-node -f"
239    echo "  Health:   rad node status (as $RAD_USER)"
240    echo ""
241    echo "Configuration:"
242    echo "  User:     $RAD_USER"
243    echo "  Port:     $RAD_PORT"
244    echo "  Socket:   $RAD_HOME/.radicle/node/control.sock"
245    echo "  Service:  $SERVICE_FILE"
246    echo ""
247    echo "Documentation:"
248    echo "  Management guide: $RAD_HOME/radicle-node-management.md"
249    echo "  Spec: alpha-delta-context/infra/machine/radicle-systemd-service.cspec"
250    echo ""
251  }
252  
253  # Main execution
254  main() {
255    log_info "Starting Radicle Node systemd service deployment"
256    log_info "User: $RAD_USER, Port: $RAD_PORT"
257    echo ""
258  
259    check_root
260    check_prerequisites
261    stop_existing_processes
262    create_service_file
263    enable_and_start_service
264    verify_installation
265    print_summary
266  
267    log_info "Deployment complete! ✅"
268  }
269  
270  # Run main function
271  main "$@"