|
| 1 | +#!/bin/bash |
| 2 | +# EvoNexus — Install as systemd service with dedicated user |
| 3 | +# Usage: sudo bash install-service.sh [install_dir] |
| 4 | +# |
| 5 | +# Creates an 'evonexus' system user, copies/chowns the installation, |
| 6 | +# installs uv + claude-code for that user, and sets up a systemd service. |
| 7 | +# Safe to re-run — skips steps that are already done. |
| 8 | + |
| 9 | +set -euo pipefail |
| 10 | + |
| 11 | +GREEN='\033[0;32m' |
| 12 | +YELLOW='\033[0;33m' |
| 13 | +RED='\033[0;31m' |
| 14 | +DIM='\033[0;90m' |
| 15 | +RESET='\033[0m' |
| 16 | + |
| 17 | +# ── Preflight ── |
| 18 | + |
| 19 | +if [ "$(id -u)" -ne 0 ]; then |
| 20 | + echo -e "${RED}✗ Must run as root (sudo bash install-service.sh)${RESET}" |
| 21 | + exit 1 |
| 22 | +fi |
| 23 | + |
| 24 | +INSTALL_DIR="${1:-$(pwd)}" |
| 25 | +if [ ! -f "$INSTALL_DIR/pyproject.toml" ]; then |
| 26 | + echo -e "${RED}✗ Not an EvoNexus installation: $INSTALL_DIR${RESET}" |
| 27 | + echo " Run from the evo-nexus directory, or pass the path: sudo bash install-service.sh /path/to/evo-nexus" |
| 28 | + exit 1 |
| 29 | +fi |
| 30 | + |
| 31 | +SERVICE_USER="evonexus" |
| 32 | +SERVICE_HOME="/home/$SERVICE_USER" |
| 33 | +SERVICE_DIR="$SERVICE_HOME/evo-nexus" |
| 34 | +SERVICE_NAME="evo-nexus" |
| 35 | + |
| 36 | +echo -e "\n${GREEN}EvoNexus — Service Installer${RESET}\n" |
| 37 | + |
| 38 | +# ── Step 1: Create user ── |
| 39 | + |
| 40 | +if id "$SERVICE_USER" &>/dev/null; then |
| 41 | + echo -e " ${DIM}✓ User '$SERVICE_USER' already exists${RESET}" |
| 42 | +else |
| 43 | + echo -e " Creating user '$SERVICE_USER'..." |
| 44 | + useradd -m -s /bin/bash "$SERVICE_USER" |
| 45 | + echo -e " ${GREEN}✓${RESET} User '$SERVICE_USER' created" |
| 46 | +fi |
| 47 | + |
| 48 | +# ── Step 2: Copy installation to user home (if not already there) ── |
| 49 | + |
| 50 | +INSTALL_DIR_REAL=$(realpath "$INSTALL_DIR") |
| 51 | +SERVICE_DIR_REAL=$(realpath "$SERVICE_DIR" 2>/dev/null || echo "$SERVICE_DIR") |
| 52 | + |
| 53 | +if [ "$INSTALL_DIR_REAL" = "$SERVICE_DIR_REAL" ]; then |
| 54 | + echo -e " ${DIM}✓ Already installed at $SERVICE_DIR${RESET}" |
| 55 | +else |
| 56 | + echo -e " Copying installation to $SERVICE_DIR..." |
| 57 | + # Remove old copy if exists |
| 58 | + rm -rf "$SERVICE_DIR" |
| 59 | + cp -a "$INSTALL_DIR_REAL" "$SERVICE_DIR" |
| 60 | + echo -e " ${GREEN}✓${RESET} Copied to $SERVICE_DIR" |
| 61 | +fi |
| 62 | + |
| 63 | +chown -R "$SERVICE_USER:$SERVICE_USER" "$SERVICE_DIR" |
| 64 | +chown -R "$SERVICE_USER:$SERVICE_USER" "$SERVICE_HOME" |
| 65 | + |
| 66 | +# ── Step 3: Install uv for the user ── |
| 67 | + |
| 68 | +if su - "$SERVICE_USER" -c "command -v uv" &>/dev/null; then |
| 69 | + echo -e " ${DIM}✓ uv already installed${RESET}" |
| 70 | +else |
| 71 | + echo -e " Installing uv..." |
| 72 | + su - "$SERVICE_USER" -c "curl -LsSf https://astral.sh/uv/install.sh | sh" >/dev/null 2>&1 |
| 73 | + echo -e " ${GREEN}✓${RESET} uv installed" |
| 74 | +fi |
| 75 | + |
| 76 | +# ── Step 4: Install Claude Code for the user ── |
| 77 | + |
| 78 | +if su - "$SERVICE_USER" -c "export PATH=\$HOME/.local/bin:\$PATH && command -v claude" &>/dev/null; then |
| 79 | + echo -e " ${DIM}✓ Claude Code already installed${RESET}" |
| 80 | +else |
| 81 | + echo -e " Installing Claude Code..." |
| 82 | + su - "$SERVICE_USER" -c "npm install -g @anthropic-ai/claude-code --prefix ~/.local" >/dev/null 2>&1 |
| 83 | + echo -e " ${GREEN}✓${RESET} Claude Code installed" |
| 84 | +fi |
| 85 | + |
| 86 | +# ── Step 5: Sync Python dependencies ── |
| 87 | + |
| 88 | +echo -e " Syncing Python dependencies..." |
| 89 | +su - "$SERVICE_USER" -c "export PATH=\$HOME/.local/bin:\$PATH && cd $SERVICE_DIR && uv sync -q" 2>/dev/null |
| 90 | +echo -e " ${GREEN}✓${RESET} Dependencies synced" |
| 91 | + |
| 92 | +# ── Step 6: Create systemd service ── |
| 93 | + |
| 94 | +echo -e " Creating systemd service..." |
| 95 | + |
| 96 | +cat > /etc/systemd/system/${SERVICE_NAME}.service << SERVICEEOF |
| 97 | +[Unit] |
| 98 | +Description=EvoNexus Dashboard + Scheduler + Terminal Server |
| 99 | +After=network.target |
| 100 | +Documentation=https://github.com/EvolutionAPI/evo-nexus |
| 101 | +
|
| 102 | +[Service] |
| 103 | +Type=forking |
| 104 | +User=$SERVICE_USER |
| 105 | +Group=$SERVICE_USER |
| 106 | +WorkingDirectory=$SERVICE_DIR |
| 107 | +Environment=PATH=$SERVICE_HOME/.local/bin:/usr/local/bin:/usr/bin:/bin |
| 108 | +Environment=HOME=$SERVICE_HOME |
| 109 | +ExecStart=/bin/bash $SERVICE_DIR/start-services.sh |
| 110 | +ExecStop=/bin/bash -c 'pkill -f "terminal-server/bin/server.js" 2>/dev/null; pkill -f "dashboard/backend.*app.py" 2>/dev/null' |
| 111 | +PIDFile=$SERVICE_DIR/logs/dashboard.pid |
| 112 | +Restart=on-failure |
| 113 | +RestartSec=10 |
| 114 | +StandardOutput=append:$SERVICE_DIR/logs/service.log |
| 115 | +StandardError=append:$SERVICE_DIR/logs/service.log |
| 116 | +
|
| 117 | +[Install] |
| 118 | +WantedBy=multi-user.target |
| 119 | +SERVICEEOF |
| 120 | + |
| 121 | +# Ensure logs dir exists |
| 122 | +su - "$SERVICE_USER" -c "mkdir -p $SERVICE_DIR/logs" |
| 123 | + |
| 124 | +systemctl daemon-reload |
| 125 | +systemctl enable "$SERVICE_NAME" >/dev/null 2>&1 |
| 126 | + |
| 127 | +echo -e " ${GREEN}✓${RESET} Systemd service created and enabled" |
| 128 | + |
| 129 | +# ── Step 7: Stop old services running as root ── |
| 130 | + |
| 131 | +echo -e " Stopping old root services..." |
| 132 | +pkill -f 'terminal-server/bin/server.js' 2>/dev/null || true |
| 133 | +pkill -f 'dashboard/backend.*app.py' 2>/dev/null || true |
| 134 | +sleep 1 |
| 135 | + |
| 136 | +# ── Step 8: Start the service ── |
| 137 | + |
| 138 | +echo -e " Starting $SERVICE_NAME service..." |
| 139 | +systemctl start "$SERVICE_NAME" |
| 140 | +sleep 3 |
| 141 | + |
| 142 | +# Verify |
| 143 | +if systemctl is-active --quiet "$SERVICE_NAME"; then |
| 144 | + echo -e " ${GREEN}✓${RESET} Service is running" |
| 145 | +else |
| 146 | + echo -e " ${YELLOW}!${RESET} Service may not have started — check: journalctl -u $SERVICE_NAME -n 30" |
| 147 | +fi |
| 148 | + |
| 149 | +# ── Done ── |
| 150 | + |
| 151 | +echo -e "\n${GREEN}Done!${RESET} EvoNexus is running as '$SERVICE_USER' via systemd.\n" |
| 152 | +echo -e " Useful commands:" |
| 153 | +echo -e " ${DIM}systemctl status $SERVICE_NAME${RESET} — check status" |
| 154 | +echo -e " ${DIM}systemctl restart $SERVICE_NAME${RESET} — restart" |
| 155 | +echo -e " ${DIM}journalctl -u $SERVICE_NAME -f${RESET} — follow logs" |
| 156 | +echo -e " ${DIM}su - $SERVICE_USER${RESET} — switch to service user" |
| 157 | +echo -e " ${DIM}su - $SERVICE_USER -c 'cd ~/evo-nexus && make run R=morning'${RESET} — run routine manually" |
| 158 | +echo "" |
0 commit comments