|
9 | 9 | # 4. Generates a private key and passphrase |
10 | 10 | # 5. Creates .env from template with generated values |
11 | 11 | # 6. Builds workspace packages |
12 | | -# 7. Prints next steps |
| 12 | +# 7. Installs and starts TelAgent as a system service |
| 13 | +# - Linux: systemd user service (~/.config/systemd/user/telagent.service) |
| 14 | +# - macOS: launchd agent (~/Library/LaunchAgents/org.telagent.node.plist) |
| 15 | +# - Windows: NSSM Windows service (auto-downloads nssm.exe if needed) |
13 | 16 |
|
14 | 17 | set -euo pipefail |
15 | 18 |
|
@@ -126,15 +129,268 @@ pnpm --filter @telagent/protocol build |
126 | 129 | pnpm --filter @telagent/sdk build |
127 | 130 | ok "Workspace packages built" |
128 | 131 |
|
129 | | -# ── Done ────────────────────────────────────────────────────────────── |
130 | | -echo "" |
131 | | -printf "${GREEN}${BOLD}TelAgent is ready!${RESET}\n" |
132 | | -echo "" |
133 | | -echo " Start the node:" |
134 | | -echo " cd ${INSTALL_DIR} && pnpm dev" |
| 132 | +# ── Step 6: Detect OS and install service ───────────────────────────── |
| 133 | +OS="$(uname -s)" |
| 134 | +PNPM_PATH="$(command -v pnpm)" |
| 135 | +NODE_PATH="$(command -v node)" |
| 136 | + |
| 137 | +install_linux_service() { |
| 138 | + info "Setting up systemd service..." |
| 139 | + SYSTEMD_DIR="$HOME/.config/systemd/user" |
| 140 | + mkdir -p "$SYSTEMD_DIR" |
| 141 | + |
| 142 | + cat > "$SYSTEMD_DIR/telagent.service" << EOF |
| 143 | +[Unit] |
| 144 | +Description=TelAgent Node |
| 145 | +After=network-online.target |
| 146 | +Wants=network-online.target |
| 147 | +
|
| 148 | +[Service] |
| 149 | +Type=simple |
| 150 | +WorkingDirectory=${INSTALL_DIR} |
| 151 | +ExecStart=${PNPM_PATH} --filter @telagent/node start |
| 152 | +Restart=always |
| 153 | +RestartSec=3 |
| 154 | +Environment=PATH=${NODE_PATH%/*}:/usr/local/bin:/usr/bin:/bin |
| 155 | +
|
| 156 | +[Install] |
| 157 | +WantedBy=default.target |
| 158 | +EOF |
| 159 | + |
| 160 | + systemctl --user daemon-reload |
| 161 | + systemctl --user enable telagent.service |
| 162 | + systemctl --user start telagent.service |
| 163 | + |
| 164 | + # Enable lingering so the user service runs without an active login session |
| 165 | + if command -v loginctl &>/dev/null; then |
| 166 | + loginctl enable-linger "$(whoami)" 2>/dev/null || true |
| 167 | + fi |
| 168 | + |
| 169 | + ok "systemd user service installed and started" |
| 170 | + echo "" |
| 171 | + echo " Manage the service:" |
| 172 | + echo " systemctl --user status telagent" |
| 173 | + echo " systemctl --user stop telagent" |
| 174 | + echo " systemctl --user restart telagent" |
| 175 | + echo " journalctl --user -u telagent -f" |
| 176 | +} |
| 177 | + |
| 178 | +install_macos_service() { |
| 179 | + info "Setting up launchd agent..." |
| 180 | + LAUNCH_DIR="$HOME/Library/LaunchAgents" |
| 181 | + PLIST="$LAUNCH_DIR/org.telagent.node.plist" |
| 182 | + LOG_DIR="$HOME/.telagent/logs" |
| 183 | + mkdir -p "$LAUNCH_DIR" "$LOG_DIR" |
| 184 | + |
| 185 | + cat > "$PLIST" << EOF |
| 186 | +<?xml version="1.0" encoding="UTF-8"?> |
| 187 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 188 | +<plist version="1.0"> |
| 189 | +<dict> |
| 190 | + <key>Label</key> |
| 191 | + <string>org.telagent.node</string> |
| 192 | + <key>WorkingDirectory</key> |
| 193 | + <string>${INSTALL_DIR}</string> |
| 194 | + <key>ProgramArguments</key> |
| 195 | + <array> |
| 196 | + <string>${PNPM_PATH}</string> |
| 197 | + <string>--filter</string> |
| 198 | + <string>@telagent/node</string> |
| 199 | + <string>start</string> |
| 200 | + </array> |
| 201 | + <key>EnvironmentVariables</key> |
| 202 | + <dict> |
| 203 | + <key>PATH</key> |
| 204 | + <string>${NODE_PATH%/*}:/usr/local/bin:/usr/bin:/bin</string> |
| 205 | + </dict> |
| 206 | + <key>RunAtLoad</key> |
| 207 | + <true/> |
| 208 | + <key>KeepAlive</key> |
| 209 | + <true/> |
| 210 | + <key>StandardOutPath</key> |
| 211 | + <string>${LOG_DIR}/telagent-stdout.log</string> |
| 212 | + <key>StandardErrorPath</key> |
| 213 | + <string>${LOG_DIR}/telagent-stderr.log</string> |
| 214 | +</dict> |
| 215 | +</plist> |
| 216 | +EOF |
| 217 | + |
| 218 | + # Unload first if already loaded (ignore errors) |
| 219 | + launchctl unload "$PLIST" 2>/dev/null || true |
| 220 | + launchctl load "$PLIST" |
| 221 | + |
| 222 | + ok "launchd agent installed and started" |
| 223 | + echo "" |
| 224 | + echo " Manage the service:" |
| 225 | + echo " launchctl list | grep telagent" |
| 226 | + echo " launchctl unload ~/Library/LaunchAgents/org.telagent.node.plist # stop" |
| 227 | + echo " launchctl load ~/Library/LaunchAgents/org.telagent.node.plist # start" |
| 228 | + echo " tail -f ~/.telagent/logs/telagent-stderr.log # logs" |
| 229 | +} |
| 230 | + |
| 231 | +start_foreground() { |
| 232 | + warn "Unsupported OS for service install: ${OS}" |
| 233 | + warn "Starting TelAgent in the foreground instead..." |
| 234 | + echo "" |
| 235 | + echo " To start manually later:" |
| 236 | + echo " cd ${INSTALL_DIR} && pnpm dev" |
| 237 | + echo "" |
| 238 | + cd "$INSTALL_DIR" |
| 239 | + exec pnpm dev |
| 240 | +} |
| 241 | + |
| 242 | +install_windows_service() { |
| 243 | + info "Setting up Windows service via NSSM..." |
| 244 | + LOG_DIR="$HOME/.telagent/logs" |
| 245 | + mkdir -p "$LOG_DIR" |
| 246 | + |
| 247 | + # Convert Git Bash paths to Windows paths |
| 248 | + WIN_INSTALL_DIR=$(cygpath -w "$INSTALL_DIR") |
| 249 | + WIN_PNPM_PATH=$(cygpath -w "$PNPM_PATH" 2>/dev/null || echo "$PNPM_PATH") |
| 250 | + WIN_NODE_DIR=$(cygpath -w "${NODE_PATH%/*}" 2>/dev/null || echo "${NODE_PATH%/*}") |
| 251 | + WIN_LOG_DIR=$(cygpath -w "$LOG_DIR") |
| 252 | + |
| 253 | + # Use pnpm.cmd on Windows |
| 254 | + PNPM_CMD="${WIN_PNPM_PATH%.exe}" |
| 255 | + if [ -f "$(cygpath "${WIN_NODE_DIR}/pnpm.cmd" 2>/dev/null)" ]; then |
| 256 | + PNPM_CMD="${WIN_NODE_DIR}\\pnpm.cmd" |
| 257 | + fi |
| 258 | + |
| 259 | + # Check if NSSM is available |
| 260 | + NSSM_PATH="" |
| 261 | + if command -v nssm &>/dev/null; then |
| 262 | + NSSM_PATH="nssm" |
| 263 | + elif [ -f "$INSTALL_DIR/tools/nssm.exe" ]; then |
| 264 | + NSSM_PATH="$INSTALL_DIR/tools/nssm.exe" |
| 265 | + fi |
| 266 | + |
| 267 | + # Download NSSM if not found |
| 268 | + if [ -z "$NSSM_PATH" ]; then |
| 269 | + info "Downloading NSSM..." |
| 270 | + NSSM_DIR="$INSTALL_DIR/tools" |
| 271 | + mkdir -p "$NSSM_DIR" |
| 272 | + NSSM_ZIP="$NSSM_DIR/nssm.zip" |
| 273 | + |
| 274 | + powershell.exe -NoProfile -Command \ |
| 275 | + "Invoke-WebRequest -Uri 'https://nssm.cc/release/nssm-2.24.zip' -OutFile '$(cygpath -w "$NSSM_ZIP")'" \ |
| 276 | + || fail "Failed to download NSSM. Download manually from https://nssm.cc and place nssm.exe in $NSSM_DIR" |
| 277 | + |
| 278 | + # Extract the correct architecture binary |
| 279 | + ARCH=$(uname -m) |
| 280 | + if [ "$ARCH" = "x86_64" ]; then |
| 281 | + NSSM_SUBDIR="nssm-2.24/win64" |
| 282 | + else |
| 283 | + NSSM_SUBDIR="nssm-2.24/win32" |
| 284 | + fi |
| 285 | + |
| 286 | + powershell.exe -NoProfile -Command \ |
| 287 | + "Expand-Archive -Path '$(cygpath -w "$NSSM_ZIP")' -DestinationPath '$(cygpath -w "$NSSM_DIR")' -Force" |
| 288 | + |
| 289 | + cp "$NSSM_DIR/$NSSM_SUBDIR/nssm.exe" "$NSSM_DIR/nssm.exe" |
| 290 | + rm -rf "$NSSM_DIR/nssm-2.24" "$NSSM_ZIP" |
| 291 | + NSSM_PATH="$NSSM_DIR/nssm.exe" |
| 292 | + ok "NSSM downloaded to $NSSM_DIR/nssm.exe" |
| 293 | + fi |
| 294 | + |
| 295 | + # Remove existing service if present (ignore errors) |
| 296 | + "$NSSM_PATH" stop TelAgent 2>/dev/null || true |
| 297 | + "$NSSM_PATH" remove TelAgent confirm 2>/dev/null || true |
| 298 | + |
| 299 | + # Install the service |
| 300 | + "$NSSM_PATH" install TelAgent "$PNPM_CMD" "--filter @telagent/node start" |
| 301 | + "$NSSM_PATH" set TelAgent AppDirectory "$WIN_INSTALL_DIR" |
| 302 | + "$NSSM_PATH" set TelAgent DisplayName "TelAgent Node" |
| 303 | + "$NSSM_PATH" set TelAgent Description "TelAgent decentralized messaging node" |
| 304 | + "$NSSM_PATH" set TelAgent Start SERVICE_AUTO_START |
| 305 | + "$NSSM_PATH" set TelAgent AppStdout "$WIN_LOG_DIR\\telagent-stdout.log" |
| 306 | + "$NSSM_PATH" set TelAgent AppStderr "$WIN_LOG_DIR\\telagent-stderr.log" |
| 307 | + "$NSSM_PATH" set TelAgent AppStdoutCreationDisposition 4 |
| 308 | + "$NSSM_PATH" set TelAgent AppStderrCreationDisposition 4 |
| 309 | + "$NSSM_PATH" set TelAgent AppRotateFiles 1 |
| 310 | + "$NSSM_PATH" set TelAgent AppRotateBytes 10485760 |
| 311 | + "$NSSM_PATH" set TelAgent AppExit Default Restart |
| 312 | + "$NSSM_PATH" set TelAgent AppRestartDelay 3000 |
| 313 | + |
| 314 | + # Start the service |
| 315 | + "$NSSM_PATH" start TelAgent |
| 316 | + |
| 317 | + ok "Windows service 'TelAgent' installed and started" |
| 318 | + echo "" |
| 319 | + echo " Manage the service:" |
| 320 | + echo " nssm status TelAgent" |
| 321 | + echo " nssm stop TelAgent" |
| 322 | + echo " nssm start TelAgent" |
| 323 | + echo " nssm restart TelAgent" |
| 324 | + echo " nssm edit TelAgent # GUI editor" |
| 325 | + echo " type %USERPROFILE%\\.telagent\\logs\\telagent-stderr.log # logs" |
| 326 | +} |
| 327 | + |
| 328 | +# ── Step 7: Start the service ───────────────────────────────────────── |
135 | 329 | echo "" |
136 | | -echo " Start the WebApp (in another terminal):" |
137 | | -echo " cd ${INSTALL_DIR} && pnpm --filter @telagent/webapp dev" |
| 330 | +case "$OS" in |
| 331 | + Linux*) |
| 332 | + if command -v systemctl &>/dev/null; then |
| 333 | + install_linux_service |
| 334 | + else |
| 335 | + start_foreground |
| 336 | + fi |
| 337 | + ;; |
| 338 | + Darwin*) |
| 339 | + install_macos_service |
| 340 | + ;; |
| 341 | + MINGW*|MSYS*|CYGWIN*) |
| 342 | + install_windows_service |
| 343 | + ;; |
| 344 | + *) |
| 345 | + start_foreground |
| 346 | + ;; |
| 347 | +esac |
| 348 | + |
| 349 | +# ── Wait for node to be ready ───────────────────────────────────────── |
| 350 | +info "Waiting for TelAgent node to start..." |
| 351 | +READY=false |
| 352 | +for i in $(seq 1 15); do |
| 353 | + if curl -fs http://127.0.0.1:9529/api/v1/node/ &>/dev/null; then |
| 354 | + READY=true |
| 355 | + break |
| 356 | + fi |
| 357 | + sleep 2 |
| 358 | +done |
| 359 | + |
138 | 360 | echo "" |
139 | | -echo " Then open http://localhost:5173 and enter your passphrase to connect." |
| 361 | +if [ "$READY" = true ]; then |
| 362 | + printf "${GREEN}${BOLD}TelAgent is running!${RESET}\n" |
| 363 | + echo "" |
| 364 | + NODE_INFO=$(curl -fs http://127.0.0.1:9529/api/v1/identities/self 2>/dev/null || echo '{}') |
| 365 | + DID=$(echo "$NODE_INFO" | jq -r '.data.did // empty' 2>/dev/null || true) |
| 366 | + if [ -n "$DID" ]; then |
| 367 | + printf " ${BOLD}Your DID:${RESET} %s\n" "$DID" |
| 368 | + fi |
| 369 | + echo "" |
| 370 | + echo " Node API: http://127.0.0.1:9529" |
| 371 | + echo "" |
| 372 | + echo " Start the WebApp (optional):" |
| 373 | + echo " cd ${INSTALL_DIR} && pnpm --filter @telagent/webapp dev" |
| 374 | + echo " Then open http://localhost:5173 and enter your passphrase to connect." |
| 375 | +else |
| 376 | + printf "${YELLOW}${BOLD}TelAgent installed but node may still be starting.${RESET}\n" |
| 377 | + echo "" |
| 378 | + echo " Check status:" |
| 379 | + case "$OS" in |
| 380 | + Darwin*) |
| 381 | + echo " launchctl list | grep telagent" |
| 382 | + echo " tail -f ~/.telagent/logs/telagent-stderr.log" |
| 383 | + ;; |
| 384 | + MINGW*|MSYS*|CYGWIN*) |
| 385 | + echo " nssm status TelAgent" |
| 386 | + echo " type %USERPROFILE%\\.telagent\\logs\\telagent-stderr.log" |
| 387 | + ;; |
| 388 | + *) |
| 389 | + echo " systemctl --user status telagent" |
| 390 | + echo " journalctl --user -u telagent -f" |
| 391 | + ;; |
| 392 | + esac |
| 393 | + echo "" |
| 394 | + echo " Once running, the API is at http://127.0.0.1:9529" |
| 395 | +fi |
140 | 396 | echo "" |
0 commit comments