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 "$@"