-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall.sh
More file actions
565 lines (499 loc) · 19.8 KB
/
Copy pathinstall.sh
File metadata and controls
565 lines (499 loc) · 19.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
#!/bin/bash
# Remote Agent Installation Script
# Run with: curl -fsSL https://raw.githubusercontent.com/mrtcmn/remote-agent/main/install.sh | bash
set -e
# Configuration
INSTALL_DIR="${INSTALL_DIR:-/opt/remote-agent}"
REGISTRY="ghcr.io"
IMAGE_NAME="${IMAGE_NAME:-mrtcmn/remote-agent}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
GITHUB_REPO="${GITHUB_REPO:-mrtcmn/remote-agent}"
REPO_URL="https://raw.githubusercontent.com/${GITHUB_REPO}/main"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
echo ""
echo "========================================"
echo " Remote Agent Installation"
echo "========================================"
echo ""
# Fall back to home directory when not root (no sudo required)
if [ "$INSTALL_DIR" = "/opt/remote-agent" ] && [ "$EUID" -ne 0 ]; then
INSTALL_DIR="$HOME/remote-agent"
log_warn "Not running as root — installing to $INSTALL_DIR"
fi
# Prompt helper: works under `curl | bash` (reads /dev/tty), silently
# falls back to empty (=defaults) when no terminal is attached.
prompt() {
local ans=""
if [ -z "${NONINTERACTIVE:-}" ] && [ -r /dev/tty ]; then
read -r -p "$1" ans < /dev/tty || true
fi
echo "$ans"
}
# --uninstall / -u / UNINSTALL=1: stop and remove containers, optionally
# wipe data volumes and the install directory. Exits before touching
# anything else.
if [ "${1:-}" = "--uninstall" ] || [ "${1:-}" = "-u" ] || [ -n "${UNINSTALL:-}" ]; then
if [ ! -d "$INSTALL_DIR" ] || [ ! -f "$INSTALL_DIR/docker-compose.yml" ]; then
log_error "No installation found at $INSTALL_DIR (set INSTALL_DIR=... if it's elsewhere)"
exit 1
fi
cd "$INSTALL_DIR"
log_warn "This stops and removes the Remote Agent containers at $INSTALL_DIR."
if systemctl is-enabled remote-agent &> /dev/null; then
systemctl disable --now remote-agent 2>/dev/null || true
rm -f /etc/systemd/system/remote-agent.service
systemctl daemon-reload
log_warn "Stopped and removed remote-agent.service (host mode)."
fi
RM_VOLUMES="$(prompt 'Also delete data (database, isolated-mode workspaces)? [y/N]: ')"
RM_DIR="$(prompt "Also delete the install directory ($INSTALL_DIR)? [y/N]: ")"
case "$RM_VOLUMES" in
y|Y|yes) docker compose down -v --remove-orphans; log_warn "Containers and volumes removed." ;;
*) docker compose down --remove-orphans; log_success "Containers removed (data volumes kept)." ;;
esac
case "$RM_DIR" in
y|Y|yes) cd /; rm -rf "$INSTALL_DIR"; log_success "Removed $INSTALL_DIR" ;;
esac
log_success "Uninstall complete."
exit 0
fi
rand_hex() {
head -c "$1" /dev/urandom | od -An -tx1 | tr -d ' \n'
}
# Port pre-flight: detect a busy host port and offer the next free one
# instead of letting `docker compose up` fail after everything else ran.
port_in_use() {
if command -v ss &> /dev/null; then
ss -ltn 2>/dev/null | awk '{print $4}' | grep -q ":$1\$"
elif command -v lsof &> /dev/null; then
lsof -iTCP:"$1" -sTCP:LISTEN &> /dev/null
else
(exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null && { exec 3<&-; exec 3>&-; return 0; } || return 1
fi
}
next_free_port() {
local p=$1
while port_in_use "$p"; do p=$((p + 1)); done
echo "$p"
}
# $1 = port var name, $2 = human label. Reads/writes the named var in place.
ensure_port_free() {
local var="$1" label="$2" cur="${!1}"
while port_in_use "$cur"; do
local suggestion ans
suggestion=$(next_free_port "$cur")
log_warn "Port $cur ($label) is already in use."
ans="$(prompt "Use port $suggestion instead? [Y/n, or type a port]: ")"
case "$ans" in
n|N|no) log_warn "Continuing with $cur anyway — container may fail to start."; break ;;
""|y|Y|yes) cur="$suggestion" ;;
*) cur="$ans" ;;
esac
done
eval "$var=\"\$cur\""
}
# Machine role: a master hosts sessions and is driven by paired machines; a
# connector pairs to an existing master and drives it from its own UI.
# Auth on a connector comes from the master via the pairing token — no
# credentials are asked here.
ROLE="${ROLE:-}"
if [ -z "$ROLE" ]; then
echo ""
echo "How will this machine be used?"
echo " 1) master — hosts sessions, other machines pair to it (needs a reachable URL)"
echo " 2) connector — pairs to an existing master and controls it from here"
ROLE_CHOICE="$(prompt 'Select role [1]: ')"
case "$ROLE_CHOICE" in
2|connector) ROLE="connector" ;;
*) ROLE="master" ;;
esac
fi
MASTER_URL="${MASTER_URL:-}"
PAIRING_TOKEN="${PAIRING_TOKEN:-}"
if [ "$ROLE" = "connector" ]; then
if [ -z "$MASTER_URL" ]; then
MASTER_URL="$(prompt 'Master URL (e.g. https://master.example.com): ')"
fi
if [ -z "$PAIRING_TOKEN" ]; then
PAIRING_TOKEN="$(prompt 'Pairing token (rapt_..., from master Settings > Paired secondaries): ')"
fi
if [ -z "$MASTER_URL" ] || [ -z "$PAIRING_TOKEN" ]; then
log_warn "No master URL/token given — install continues, pair later in Settings > Paired masters."
fi
fi
# Deploy mode: "docker" runs the whole app (incl. Claude sessions) sandboxed
# in the remote-agent container. "host" keeps only Postgres in Docker and
# runs the API — and therefore every Claude session it spawns — as a plain
# host process, with real access to the host filesystem, binaries and
# network (e.g. an existing host `claude` install). Both modes still need
# Docker for the database.
DEPLOY_MODE="${DEPLOY_MODE:-}"
if [ -z "$DEPLOY_MODE" ]; then
echo ""
echo "How should the app itself run?"
echo " 1) Docker — fully sandboxed, Claude sessions run inside the container (default)"
echo " 2) Host — Postgres in Docker, API + Claude sessions run natively on this host"
DEPLOY_CHOICE="$(prompt 'Select [1]: ')"
case "$DEPLOY_CHOICE" in
2|host) DEPLOY_MODE="host" ;;
*) DEPLOY_MODE="docker" ;;
esac
fi
# Check Docker
log_info "Checking Docker installation..."
if ! command -v docker &> /dev/null; then
log_warn "Docker not found. Installing..."
curl -fsSL https://get.docker.com | sh
if [ "$EUID" -ne 0 ]; then
sudo usermod -aG docker "$USER"
log_warn "Please log out and back in, then run this script again."
exit 0
fi
fi
log_success "Docker is installed"
# Check Docker Compose
log_info "Checking Docker Compose..."
if ! docker compose version &> /dev/null; then
log_warn "Docker Compose not found. Installing..."
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y docker-compose-plugin
else
log_error "Please install Docker Compose manually"
exit 1
fi
fi
log_success "Docker Compose is installed"
# Create installation directories
log_info "Creating installation directory: $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"/{workspaces,ssh-keys,backups,config/skills,config/hooks}
cd "$INSTALL_DIR"
# Download docker-compose.yml
log_info "Downloading configuration files..."
curl -fsSL "${REPO_URL}/docker/docker-compose.prod.yml" -o docker-compose.yml
# Download upgrade script
curl -fsSL "${REPO_URL}/scripts/upgrade.sh" -o upgrade.sh
chmod +x upgrade.sh
# Save this installer too, so `./install.sh --uninstall` works later
# without re-curling
curl -fsSL "${REPO_URL}/install.sh" -o install.sh
chmod +x install.sh
log_success "Configuration files downloaded"
# Setup environment file
if [ ! -f .env ]; then
echo ""
log_info "Setting up environment configuration..."
echo ""
# Generated secrets
POSTGRES_PASSWORD=$(rand_hex 16)
JWT_SECRET=$(rand_hex 32)
# GitHub OAuth is optional — email/password login works out of the box.
# Connectors skip these prompts entirely: they sit behind NAT and get
# their identity from the master via the pairing token.
if [ "$ROLE" = "master" ]; then
GITHUB_CLIENT_ID="${GITHUB_CLIENT_ID:-$(prompt 'GITHUB_CLIENT_ID (optional, Enter to skip): ')}"
if [ -n "$GITHUB_CLIENT_ID" ] && [ -z "${GITHUB_CLIENT_SECRET:-}" ]; then
GITHUB_CLIENT_SECRET="$(prompt 'GITHUB_CLIENT_SECRET: ')"
fi
# Get external IP or use localhost
EXTERNAL_IP=$(curl -s ifconfig.me 2>/dev/null || echo "localhost")
if [ -z "${APP_URL:-}" ]; then
APP_URL="$(prompt "APP_URL [http://${EXTERNAL_IP}:5100]: ")"
APP_URL=${APP_URL:-"http://${EXTERNAL_IP}:5100"}
fi
else
APP_URL="${APP_URL:-http://localhost:5100}"
fi
# Firebase push notifications: optional, env-only (FIREBASE_* vars)
# Where Claude's session files (workspaces, ssh keys) live — only
# meaningful in docker mode; host mode uses the host filesystem directly.
if [ "$DEPLOY_MODE" = "docker" ] && [ -z "${WORKSPACES_SRC:-}" ]; then
echo ""
echo "Where should Claude's workspace/project files live?"
echo " 1) Host directory — ${INSTALL_DIR}/workspaces, directly browsable/editable from the host (default)"
echo " 2) Isolated Docker volume — sandboxed, not directly accessible on the host"
STORAGE_CHOICE="$(prompt 'Select [1]: ')"
case "$STORAGE_CHOICE" in
2|volume|isolated)
WORKSPACES_SRC="workspaces"
SSH_KEYS_SRC="ssh-keys"
;;
*)
WORKSPACES_SRC="./workspaces"
SSH_KEYS_SRC="./ssh-keys"
;;
esac
fi
# Which interface the app port (5100) binds to on the host — host mode
# always binds all interfaces (it's a plain Bun process, not a container
# port mapping), so this only applies to docker mode.
if [ "$DEPLOY_MODE" = "docker" ] && [ -z "${BIND_HOST:-}" ]; then
echo ""
echo "Bind the app port (5100) to which address?"
echo " 1) 127.0.0.1 — localhost only, put a reverse proxy in front (default)"
echo " 2) 0.0.0.0 — all interfaces, reachable directly from the network"
echo " 3) custom IP"
BIND_CHOICE="$(prompt 'Select [1]: ')"
case "$BIND_CHOICE" in
2|0.0.0.0) BIND_HOST="0.0.0.0" ;;
3|custom) BIND_HOST="$(prompt 'Bind IP: ')" ;;
*) BIND_HOST="127.0.0.1" ;;
esac
fi
cat > .env << EOF
# Remote Agent Configuration
# Generated on $(date)
# Docker Image
IMAGE_TAG=${IMAGE_TAG}
# Database
POSTGRES_USER=agent
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=remote_agent
# GitHub OAuth (optional — email/password login works without it)
GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
# Security
JWT_SECRET=${JWT_SECRET}
# Firebase Push Notifications (optional)
FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID:-}
FIREBASE_PRIVATE_KEY=${FIREBASE_PRIVATE_KEY:-}
FIREBASE_CLIENT_EMAIL=${FIREBASE_CLIENT_EMAIL:-}
# Application
PORT=5100
BIND_HOST=${BIND_HOST:-}
APP_URL=${APP_URL}
NODE_ENV=production
# Storage
WORKSPACES_SRC=${WORKSPACES_SRC:-}
SSH_KEYS_SRC=${SSH_KEYS_SRC:-}
# Deploy mode
DEPLOY_MODE=${DEPLOY_MODE}
DB_PORT=5432
EOF
chmod 600 .env
log_success "Environment configuration created"
else
log_info "Using existing .env file"
DEPLOY_MODE="$(grep -m1 '^DEPLOY_MODE=' .env 2>/dev/null | cut -d= -f2)"
DEPLOY_MODE="${DEPLOY_MODE:-docker}"
fi
PORT="$(grep -m1 '^PORT=' .env 2>/dev/null | cut -d= -f2)"
PORT="${PORT:-5100}"
if [ "$DEPLOY_MODE" = "docker" ]; then
# Pull Docker image
echo ""
log_info "Pulling Docker image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
docker pull "${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
log_success "Docker image pulled"
# Tear down any existing/stale containers first — both so the port check
# below isn't fooled by our own old containers still holding the ports, and
# so a broken container from a prior failed run gets fully recreated.
docker compose down --remove-orphans 2>/dev/null || true
# Check the ports we're about to bind — every run, not just first install,
# since another container (e.g. something already using 8080) can show up later.
log_info "Checking required ports are free..."
CODE_SERVER_PORT="$(grep -m1 '^CODE_SERVER_PORT=' .env 2>/dev/null | cut -d= -f2)"
CODE_SERVER_PORT="${CODE_SERVER_PORT:-8080}"
ensure_port_free PORT "app UI"
ensure_port_free CODE_SERVER_PORT "code-server"
grep -v '^PORT=\|^CODE_SERVER_PORT=' .env > .env.tmp || true
{
cat .env.tmp
echo "PORT=${PORT}"
echo "CODE_SERVER_PORT=${CODE_SERVER_PORT}"
} > .env
rm -f .env.tmp
chmod 600 .env
log_success "Ports OK (app: ${PORT}, code-server: ${CODE_SERVER_PORT})"
# Start services
echo ""
log_info "Starting Remote Agent..."
docker compose up -d --remove-orphans
else
# Host mode: only Postgres runs in Docker. The API — and every Claude
# session it spawns — runs as a plain systemd-managed host process.
ensure_port_free PORT "app UI"
grep -v '^PORT=' .env > .env.tmp || true
{ cat .env.tmp; echo "PORT=${PORT}"; } > .env
rm -f .env.tmp
chmod 600 .env
echo ""
log_info "Starting Postgres (Docker)..."
docker compose up -d --remove-orphans db
log_success "Postgres running"
if ! command -v bun &> /dev/null; then
log_warn "Bun not found. Installing..."
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
fi
log_success "Bun is installed"
if ! command -v claude &> /dev/null; then
log_warn "Claude Code CLI not found on host. Sessions will fail until it's installed (npm install -g @anthropic-ai/claude-code)."
fi
CLAUDE_BIN_PATH="$(command -v claude || echo "$HOME/.local/bin/claude")"
APP_SRC_DIR="${INSTALL_DIR}/app"
if [ ! -d "$APP_SRC_DIR/.git" ]; then
log_info "Cloning source..."
command -v git &> /dev/null || (command -v apt-get &> /dev/null && apt-get update && apt-get install -y git)
git clone "https://github.com/${GITHUB_REPO}.git" "$APP_SRC_DIR"
else
log_info "Updating source..."
git -C "$APP_SRC_DIR" pull --ff-only
fi
# ponytail: UI only. systemd runs `bun run start` (= bun src/index.ts) from
# this source tree, so the --compile step and its embedded-ui manifest are
# never used in host mode; the API scans packages/ui/dist at runtime.
log_info "Installing dependencies and building UI..."
(cd "$APP_SRC_DIR" && bun install && bun run build:ui)
log_success "Build complete"
POSTGRES_PASSWORD="$(grep -m1 '^POSTGRES_PASSWORD=' .env | cut -d= -f2)"
JWT_SECRET="$(grep -m1 '^JWT_SECRET=' .env | cut -d= -f2)"
DB_PORT="$(grep -m1 '^DB_PORT=' .env 2>/dev/null | cut -d= -f2)"
DB_PORT="${DB_PORT:-5432}"
HOST_DATABASE_URL="postgres://agent:${POSTGRES_PASSWORD}@localhost:${DB_PORT}/remote_agent"
log_info "Running database migrations..."
(cd "$APP_SRC_DIR" && DATABASE_URL="$HOST_DATABASE_URL" bun run db:migrate)
log_info "Installing systemd service..."
cat > /etc/systemd/system/remote-agent.service << EOF
[Unit]
Description=Remote Agent API (host mode)
After=network.target docker.service
[Service]
Type=simple
WorkingDirectory=${APP_SRC_DIR}
Environment=DATABASE_URL=${HOST_DATABASE_URL}
Environment=JWT_SECRET=${JWT_SECRET}
Environment=CLAUDE_BIN_PATH=${CLAUDE_BIN_PATH}
Environment=PORT=${PORT}
Environment=NODE_ENV=production
Environment=PATH=${HOME}/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=$(command -v bun) run start
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now remote-agent
log_success "remote-agent.service started"
fi
# Wait for health check
log_info "Waiting for service to be ready..."
MAX_ATTEMPTS=60
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
if curl -sf "http://localhost:${PORT}/health" > /dev/null 2>&1; then
break
fi
sleep 2
((ATTEMPT++))
done
if [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then
log_error "Service failed to start. Check logs with: docker compose logs"
exit 1
fi
# Auto-pair connector with its master using the seeded local account —
# identity on the master side comes from the pairing token, so no
# credentials are ever asked.
PAIRED=""
if [ "$ROLE" = "connector" ] && [ -n "$MASTER_URL" ] && [ -n "$PAIRING_TOKEN" ]; then
log_info "Pairing with master ${MASTER_URL}..."
COOKIE_JAR=$(mktemp)
SIGNED_IN=""
# Seed user is created shortly after boot — retry sign-in briefly
for _ in 1 2 3 4 5; do
if curl -sf -c "$COOKIE_JAR" -X POST "http://localhost:${PORT}/api/auth/sign-in/email" \
-H 'Content-Type: application/json' \
-d '{"email":"test@t.com","password":"123456"}' > /dev/null 2>&1; then
SIGNED_IN=1
break
fi
sleep 2
done
if [ -n "$SIGNED_IN" ]; then
PAIR_RESP=$(curl -s -b "$COOKIE_JAR" -X POST "http://localhost:${PORT}/api/paired-masters" \
-H 'Content-Type: application/json' \
-d "{\"url\":\"${MASTER_URL}\",\"token\":\"${PAIRING_TOKEN}\",\"name\":\"$(hostname 2>/dev/null || echo connector)\"}")
if echo "$PAIR_RESP" | grep -q '"master"'; then
PAIRED=1
log_success "Paired with master ${MASTER_URL}"
else
log_warn "Auto-pair failed: ${PAIR_RESP}"
fi
else
log_warn "Could not sign in to local instance for pairing."
fi
rm -f "$COOKIE_JAR"
if [ -z "$PAIRED" ]; then
log_info "Pair manually: Settings > Paired masters (token is single-use — generate a fresh one on the master if needed)"
fi
fi
# Get current version
if [ "$DEPLOY_MODE" = "docker" ]; then
CURRENT_VERSION=$(docker exec remote-agent printenv APP_VERSION 2>/dev/null || echo "latest")
else
CURRENT_VERSION=$(git -C "${INSTALL_DIR}/app" rev-parse --short HEAD 2>/dev/null || echo "latest")
fi
echo ""
echo "========================================"
log_success "Remote Agent installed successfully!"
echo "========================================"
echo ""
echo " Version: ${CURRENT_VERSION}"
echo " Location: ${INSTALL_DIR}"
echo " URL: http://localhost:${PORT}"
echo ""
echo "Quick Start:"
echo " 1. Open http://localhost:${PORT} in your browser"
if [ -n "${GITHUB_CLIENT_ID:-}" ]; then
echo " 2. Login with GitHub"
else
echo " 2. Login with test@t.com / 123456 (change it after first login)"
fi
if [ "$ROLE" = "connector" ]; then
if [ -n "$PAIRED" ]; then
echo " 3. Your master's sessions appear in the sidebar (machine switcher)"
else
echo " 3. Finish pairing: Settings > Paired masters"
fi
else
echo " 3. Set up a PIN for extra security"
echo " 4. To control this machine from another PC: Settings > Paired secondaries"
echo " > Generate pairing token, then install with role 'connector' there."
fi
echo ""
if [ "$DEPLOY_MODE" = "docker" ]; then
echo "Management Commands:"
echo " cd ${INSTALL_DIR}"
echo " ./upgrade.sh # Check for and install updates"
echo " ./upgrade.sh --check # Check for updates only"
echo " docker compose logs -f # View logs"
echo " docker compose restart # Restart services"
echo " docker compose down # Stop services"
echo " ./install.sh --uninstall # Remove Remote Agent"
else
echo "Host mode: API runs natively via systemd (Postgres still in Docker)."
echo "Management Commands:"
echo " journalctl -u remote-agent -f # View logs"
echo " systemctl restart remote-agent # Restart the API"
echo " systemctl stop remote-agent # Stop the API"
echo " cd ${INSTALL_DIR} && docker compose stop db # Stop Postgres"
echo " ./install.sh --uninstall # Remove Remote Agent"
fi
echo ""