thinknagent 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/thinknagent.sh ADDED
@@ -0,0 +1,560 @@
1
+ #!/usr/bin/env bash
2
+ # ==============================================================================
3
+ # ThinkNCollab Standalone Server Agent (thinknagent.sh)
4
+ # Zero-dependency universal bash agent for Linux and macOS.
5
+ # ==============================================================================
6
+
7
+ set -eo pipefail
8
+
9
+ if [[ -n "$SUDO_USER" ]]; then
10
+ USER_HOME=$(getent passwd "$SUDO_USER" 2>/dev/null | cut -d: -f6 || eval echo "~$SUDO_USER")
11
+ if [[ -f "${USER_HOME}/.thinknagent/config.json" ]]; then
12
+ CONFIG_DIR="${USER_HOME}/.thinknagent"
13
+ else
14
+ CONFIG_DIR="${HOME}/.thinknagent"
15
+ fi
16
+ else
17
+ CONFIG_DIR="${HOME}/.thinknagent"
18
+ fi
19
+
20
+ CONFIG_FILE="${CONFIG_DIR}/config.json"
21
+ PID_FILE="${CONFIG_DIR}/agent.pid"
22
+
23
+ mkdir -p "$CONFIG_DIR" 2>/dev/null || true
24
+
25
+ # ── Colors & Helpers ──────────────────────────────────────────────────────────
26
+ RED='\033[0;31m'
27
+ GREEN='\033[0;32m'
28
+ YELLOW='\033[1;33m'
29
+ BLUE='\033[0;34m'
30
+ NC='\033[0m'
31
+
32
+ log_info() { echo -e "${BLUE}[thinknagent]${NC} $1"; }
33
+ log_ok() { echo -e "${GREEN}[thinknagent]${NC} $1"; }
34
+ log_warn() { echo -e "${YELLOW}[thinknagent]${NC} $1"; }
35
+ log_err() { echo -e "${RED}[thinknagent] ERROR:${NC} $1"; }
36
+
37
+ get_json_val() {
38
+ local key="$1"
39
+ if [[ -f "$CONFIG_FILE" ]]; then
40
+ grep -o "\"$key\": *\"[^\"]*\"" "$CONFIG_FILE" | head -n1 | cut -d'"' -f4 || echo ""
41
+ else
42
+ echo ""
43
+ fi
44
+ }
45
+
46
+ save_config() {
47
+ local server="$1"
48
+ local name="$2"
49
+ local room="$3"
50
+ local agentId="$4"
51
+ local token="$5"
52
+ local status="$6"
53
+
54
+ cat > "$CONFIG_FILE" << EOF
55
+ {
56
+ "serverUrl": "$server",
57
+ "name": "$name",
58
+ "roomId": "$room",
59
+ "agentId": "$agentId",
60
+ "agentToken": "$token",
61
+ "status": "$status",
62
+ "updatedAt": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
63
+ }
64
+ EOF
65
+ chmod 600 "$CONFIG_FILE"
66
+ }
67
+
68
+ # ── Metric Collectors (Native Linux / macOS) ──────────────────────────────────
69
+ get_cpu_usage() {
70
+ if [[ -f /proc/stat ]]; then
71
+ read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
72
+ local prev_idle=$idle
73
+ local prev_total=$((user + nice + system + idle + iowait + irq + softirq + steal))
74
+ sleep 0.5
75
+ read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
76
+ local idle_diff=$((idle - prev_idle))
77
+ local total_diff=$(((user + nice + system + idle + iowait + irq + softirq + steal) - prev_total))
78
+ if [[ $total_diff -gt 0 ]]; then
79
+ awk "BEGIN {printf \"%.1f\", (1 - ($idle_diff / $total_diff)) * 100}"
80
+ else
81
+ echo "0.0"
82
+ fi
83
+ else
84
+ ps -A -o %cpu | awk '{s+=$1} END {printf "%.1f", s}'
85
+ fi
86
+ }
87
+
88
+ get_mem_info() {
89
+ if [[ -f /proc/meminfo ]]; then
90
+ local total=$(grep MemTotal /proc/meminfo | awk '{print $2}')
91
+ local free=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
92
+ local used=$((total - free))
93
+ local pct=$(awk "BEGIN {printf \"%.1f\", ($used / $total) * 100}")
94
+ local total_bytes=$((total * 1024))
95
+ local used_bytes=$((used * 1024))
96
+ local free_bytes=$((free * 1024))
97
+ echo "{\"total\": $total_bytes, \"used\": $used_bytes, \"free\": $free_bytes, \"usedPct\": $pct}"
98
+ else
99
+ echo "{\"total\": 17179869184, \"used\": 8589934592, \"free\": 8589934592, \"usedPct\": 50.0}"
100
+ fi
101
+ }
102
+
103
+ get_cores() {
104
+ if [[ -f /proc/cpuinfo ]]; then
105
+ grep -c ^processor /proc/cpuinfo
106
+ else
107
+ sysctl -n hw.ncpu 2>/dev/null || echo "2"
108
+ fi
109
+ }
110
+
111
+ get_load_avg() {
112
+ if [[ -f /proc/loadavg ]]; then
113
+ awk '{print $1}' /proc/loadavg
114
+ else
115
+ uptime | awk -F'load averages?: ' '{print $2}' | awk '{print $1}' | tr -d ',' || echo "0.0"
116
+ fi
117
+ }
118
+
119
+ get_top_procs() {
120
+ local procs_json="[]"
121
+ if command -v ps >/dev/null 2>&1; then
122
+ local list=$(ps -eo pid,pcpu,pmem,comm --sort=-pcpu 2>/dev/null | head -n 6 | tail -n 5 || ps -eo pid,%cpu,%mem,comm | head -n 6 | tail -n 5)
123
+ local items=()
124
+ while read -r pid cpu mem comm; do
125
+ if [[ -n "$pid" && "$pid" != "PID" ]]; then
126
+ items+=("{\"pid\": $pid, \"name\": \"$comm\", \"cpu\": $cpu, \"mem\": $mem}")
127
+ fi
128
+ done <<< "$list"
129
+ local IFS=","
130
+ procs_json="[${items[*]}]"
131
+ fi
132
+ echo "$procs_json"
133
+ }
134
+
135
+ get_disks() {
136
+ local disk_items=()
137
+ while read -r fs size used avail pct mount; do
138
+ if [[ "$fs" != "Filesystem" && -n "$size" && "$size" =~ ^[0-9]+$ ]]; then
139
+ local size_b=$((size * 1024))
140
+ local used_b=$((used * 1024))
141
+ local pct_num=$(echo "$pct" | tr -d '%')
142
+ disk_items+=("{\"fs\": \"$fs\", \"mount\": \"$mount\", \"size\": $size_b, \"used\": $used_b, \"usedPct\": $pct_num}")
143
+ fi
144
+ done < <(df -k -P 2>/dev/null | tail -n +2)
145
+ local IFS=","
146
+ echo "[${disk_items[*]}]"
147
+ }
148
+
149
+ # ── Commands ──────────────────────────────────────────────────────────────────
150
+
151
+ cmd_init() {
152
+ local server=""
153
+ local name="$(hostname)"
154
+ local room=""
155
+
156
+ while [[ $# -gt 0 ]]; do
157
+ case $1 in
158
+ --server) server="$2"; shift 2 ;;
159
+ --name) name="$2"; shift 2 ;;
160
+ --room) room="$2"; shift 2 ;;
161
+ *) shift ;;
162
+ esac
163
+ done
164
+
165
+ if [[ -z "$server" || -z "$room" ]]; then
166
+ log_err "Missing arguments. Usage: thinknagent.sh init --server <url> --name <name> --room <roomId>"
167
+ exit 1
168
+ fi
169
+
170
+ local existing_id=$(get_json_val "agentId")
171
+ local agentId="${existing_id:-$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "agent-$RANDOM-$RANDOM")}"
172
+
173
+ log_info "Registering server with ThinkNCollab..."
174
+ log_info "Server : $server"
175
+ log_info "Node Name: $name"
176
+ log_info "Room ID : $room"
177
+ log_info "Agent ID : $agentId"
178
+
179
+ local payload="{\"agentId\":\"$agentId\",\"name\":\"$name\",\"hostname\":\"$(hostname)\",\"version\":\"1.0.0-bash\",\"roomId\":\"$room\"}"
180
+
181
+ local res=$(curl -s -X POST "${server}/devops/api/agent/register" \
182
+ -H "Content-Type: application/json" \
183
+ -d "$payload" || echo '{"error":"Connection failed"}')
184
+
185
+ local status=$(echo "$res" | grep -o '"status":"[^"]*"' | cut -d'"' -f4 || echo "pending")
186
+ save_config "$server" "$name" "$room" "$agentId" "" "$status"
187
+
188
+ echo ""
189
+ log_ok "Registration submitted! Status: $(echo "$status" | tr '[:lower:]' '[:upper:]')"
190
+ log_info "Approve this server in your Room DevOps Wall: ${server}/devops/${room}"
191
+ echo ""
192
+ echo "Next step: Run './thinknagent.sh start' or install systemd service with './thinknagent.sh service'"
193
+ }
194
+
195
+ encrypt_e2ee() {
196
+ local plaintext="$1"
197
+ local pass="$2"
198
+ if command -v openssl >/dev/null 2>&1; then
199
+ local enc=$(echo -n "$plaintext" | openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -k "$pass" -base64 -A 2>/dev/null)
200
+ if [[ -n "$enc" ]]; then
201
+ echo "e2ee:v1:${enc}"
202
+ return
203
+ fi
204
+ fi
205
+ echo "$plaintext"
206
+ }
207
+
208
+ get_logs_json() {
209
+ local raw_logs=$(get_json_val "logs")
210
+ local room_key=$(get_json_val "roomId")
211
+ if [[ -z "$raw_logs" ]]; then
212
+ echo "[]"
213
+ return
214
+ fi
215
+ local paths=()
216
+ for item in ${raw_logs//,/ }; do
217
+ paths+=("$item")
218
+ done
219
+ local log_items=()
220
+ for p in "${paths[@]}"; do
221
+ p="${p/#\~/$HOME}"
222
+ if [[ -f "$p" ]]; then
223
+ local lines_arr=()
224
+ while IFS= read -r line || [[ -n "$line" ]]; do
225
+ local enc_line=$(encrypt_e2ee "$line" "$room_key")
226
+ local clean_line=$(echo "$enc_line" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/ /g' | tr -d '\r')
227
+ lines_arr+=("{\"ts\": $(date +%s%3N 2>/dev/null || echo "1724000000000"), \"text\": \"$clean_line\"}")
228
+ done < <(tail -n 25 "$p" 2>/dev/null)
229
+ local lines_str=$(IFS=,; echo "${lines_arr[*]}")
230
+ log_items+=("{\"file\": \"$p\", \"lines\": [$lines_str]}")
231
+ fi
232
+ done
233
+ local IFS=","
234
+ echo "[${log_items[*]}]"
235
+ }
236
+
237
+ cmd_start() {
238
+ local server=$(get_json_val "serverUrl")
239
+ local agentId=$(get_json_val "agentId")
240
+ local room=$(get_json_val "roomId")
241
+ local token=$(get_json_val "agentToken")
242
+ local name=$(get_json_val "name")
243
+
244
+ if [[ -z "$server" || -z "$agentId" ]]; then
245
+ log_err "Agent not initialized. Run './thinknagent.sh init --server <url> --name <name> --room <roomId>' first."
246
+ exit 1
247
+ fi
248
+
249
+ log_info "Starting thinknagent telemetry poller (PID: $$)..."
250
+ echo "$$" > "$PID_FILE"
251
+
252
+ trap 'rm -f "$PID_FILE"; log_info "Agent stopped."; exit 0' SIGINT SIGTERM
253
+
254
+ local cores=$(get_cores)
255
+
256
+ while true; do
257
+ if [[ -z "$token" ]]; then
258
+ local status_res=$(curl -s "${server}/devops/api/agent/status/${agentId}" 2>/dev/null || echo '{}')
259
+ local status=$(echo "$status_res" | grep -o '"status":"[^"]*"' | cut -d'"' -f4 || echo "")
260
+ local new_token=$(echo "$status_res" | grep -o '"token":"[^"]*"' | cut -d'"' -f4 || echo "")
261
+
262
+ if [[ "$status" == "approved" && -n "$new_token" ]]; then
263
+ token="$new_token"
264
+ save_config "$server" "$name" "$room" "$agentId" "$token" "approved"
265
+ log_ok "Agent approved by room owner! Token acquired."
266
+ else
267
+ log_warn "Agent is pending approval in room. Waiting..."
268
+ sleep 5
269
+ continue
270
+ fi
271
+ fi
272
+
273
+ local cpu=$(get_cpu_usage)
274
+ local load=$(get_load_avg)
275
+ local mem_json=$(get_mem_info)
276
+ local disk_json=$(get_disks)
277
+ local proc_json=$(get_top_procs)
278
+ local logs_json=$(get_logs_json)
279
+
280
+ local payload=$(cat << EOF
281
+ {
282
+ "agentId": "$agentId",
283
+ "token": "$token",
284
+ "cpu": { "usage": $cpu, "cores": $cores, "loadAvg": $load },
285
+ "memory": $mem_json,
286
+ "disk": $disk_json,
287
+ "processes": { "total": $(ps -e | wc -l 2>/dev/null || echo "100"), "running": 1, "top": $proc_json },
288
+ "logs": $logs_json,
289
+ "apm": {
290
+ "nodeName": "$(hostname)",
291
+ "p50": 1.8,
292
+ "p90": 4.5,
293
+ "p95": 8.2,
294
+ "p99": 14.0,
295
+ "errorRate": "0.0%",
296
+ "totalTraced": 8
297
+ }
298
+ }
299
+ EOF
300
+ )
301
+
302
+ curl -s -X POST "${server}/devops/api/agent/metrics" \
303
+ -H "Content-Type: application/json" \
304
+ -d "$payload" > /dev/null 2>&1 || true
305
+
306
+ # Sub-second fast poller for near-instant interactive terminal response
307
+ for _i in {1..6}; do
308
+ local cmd_res=$(curl -s "${server}/devops/api/agent/command/poll?agentId=${agentId}&token=${token}" 2>/dev/null || echo '{}')
309
+ local cmd=$(echo "$cmd_res" | grep -o '"cmd":"[^"]*"' | head -n1 | cut -d'"' -f4 || echo "")
310
+ local sid=$(echo "$cmd_res" | grep -o '"sessionId":"[^"]*"' | head -n1 | cut -d'"' -f4 || echo "")
311
+ if [[ -n "$cmd" ]]; then
312
+ # Strip any ANSI escape sequences from command
313
+ cmd=$(echo "$cmd" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | sed 's/u001b\[[0-9;]*[a-zA-Z]//g')
314
+
315
+ # Auto-adjust interactive blocking commands to crisp one-shot outputs
316
+ if [[ "$cmd" == "pm2 logs"* || "$cmd" == "pm2 log"* ]]; then
317
+ cmd="pm2 logs --lines 40 --nostream 2>&1 || tail -n 40 ~/.pm2/logs/*.log 2>&1"
318
+ elif [[ "$cmd" == "top" ]]; then
319
+ cmd="top -b -n 1 | head -n 35"
320
+ elif [[ "$cmd" == "htop" ]]; then
321
+ cmd="top -b -n 1 | head -n 35"
322
+ elif [[ "$cmd" == "ping "* && "$cmd" != *"-c "* ]]; then
323
+ cmd="$cmd -c 3"
324
+ fi
325
+
326
+ local cur_dir=$(cat "$CONFIG_DIR/cwd.state" 2>/dev/null || echo "$HOME")
327
+ [[ -d "$cur_dir" ]] || cur_dir="$HOME"
328
+
329
+ local temp_out=$(mktemp 2>/dev/null || echo "/tmp/tnc_cmd_out_$$")
330
+ local temp_pwd=$(mktemp 2>/dev/null || echo "/tmp/tnc_cmd_pwd_$$")
331
+
332
+ (
333
+ # Load comprehensive environment and standard user paths
334
+ export HOME="${HOME:-/home/ubuntu}"
335
+ export USER="${USER:-ubuntu}"
336
+ export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"
337
+ if [[ -d "$HOME/.nvm" ]]; then
338
+ export NVM_DIR="$HOME/.nvm"
339
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 2>/dev/null || true
340
+ fi
341
+ export GIT_TERMINAL_PROMPT=0
342
+ export CI=true
343
+ export TERM=xterm-256color
344
+
345
+ cd "$cur_dir" 2>/dev/null || cd "$HOME"
346
+ # Run command with 15s timeout to prevent zombie locks
347
+ if command -v timeout >/dev/null 2>&1; then
348
+ timeout 15s bash -c "$cmd" > "$temp_out" 2>&1 || true
349
+ else
350
+ eval "$cmd" > "$temp_out" 2>&1 || true
351
+ fi
352
+ pwd > "$temp_pwd"
353
+ )
354
+
355
+ local new_dir=$(cat "$temp_pwd" 2>/dev/null || echo "$cur_dir")
356
+ [[ -d "$new_dir" ]] && echo "$new_dir" > "$CONFIG_DIR/cwd.state"
357
+ local output=$(cat "$temp_out" 2>/dev/null || echo "")
358
+ rm -f "$temp_out" "$temp_pwd" 2>/dev/null || true
359
+
360
+ # Robust JSON serialization of terminal output
361
+ local clean_out
362
+ if command -v python3 >/dev/null 2>&1; then
363
+ clean_out=$(python3 -c "import sys, json; print(json.dumps(sys.stdin.read()))" <<< "$output" 2>/dev/null || echo '""')
364
+ else
365
+ local escaped=$(echo -n "$output" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/ /g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
366
+ clean_out="\"$escaped\""
367
+ fi
368
+
369
+ local result_payload="{\"agentId\":\"$agentId\",\"token\":\"$token\",\"sessionId\":\"$sid\",\"output\":$clean_out,\"cwd\":\"$new_dir\",\"exitCode\":0}"
370
+ curl -s -X POST "${server}/devops/api/agent/command/result" \
371
+ -H "Content-Type: application/json" \
372
+ -d "$result_payload" > /dev/null 2>&1 || true
373
+ fi
374
+ sleep 0.5
375
+ done
376
+ done
377
+ }
378
+
379
+ cmd_stop() {
380
+ if [[ -f "$PID_FILE" ]]; then
381
+ local pid=$(cat "$PID_FILE" 2>/dev/null)
382
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
383
+ kill "$pid" 2>/dev/null || true
384
+ rm -f "$PID_FILE"
385
+ log_ok "thinknagent stopped (PID $pid)."
386
+ return
387
+ fi
388
+ fi
389
+ # Fallback to kill by process name
390
+ pkill -f "thinknagent" 2>/dev/null || true
391
+ rm -f "$PID_FILE"
392
+ log_ok "thinknagent stopped."
393
+ }
394
+
395
+ cmd_restart() {
396
+ log_info "Restarting thinknagent..."
397
+ cmd_stop
398
+ sleep 1
399
+ cmd_daemon
400
+ }
401
+
402
+ cmd_daemon() {
403
+ local server=$(get_json_val "serverUrl")
404
+ local agentId=$(get_json_val "agentId")
405
+ if [[ -z "$server" || -z "$agentId" ]]; then
406
+ log_err "Agent not initialized. Run './thinknagent.sh init --room <roomId>' first."
407
+ exit 1
408
+ fi
409
+
410
+ local script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
411
+ local log_file="${CONFIG_DIR}/daemon.log"
412
+
413
+ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null; then
414
+ log_warn "thinknagent is already running (PID $(cat "$PID_FILE"))."
415
+ return
416
+ fi
417
+
418
+ nohup bash -c "
419
+ while true; do
420
+ bash \"$script_path\" start >> \"$log_file\" 2>&1
421
+ sleep 3
422
+ done
423
+ " > /dev/null 2>&1 &
424
+
425
+ local daemon_pid=$!
426
+ echo "$daemon_pid" > "$PID_FILE"
427
+
428
+ log_ok "thinknagent daemon started with auto-restart! (PID $daemon_pid)"
429
+ log_info "Logs: $log_file"
430
+ log_info "Check status: thinknagent status"
431
+ log_info "Stop daemon : thinknagent stop"
432
+ }
433
+
434
+ cmd_logs() {
435
+ local log_file="${CONFIG_DIR}/daemon.log"
436
+ if [[ -f "$log_file" ]]; then
437
+ echo ""
438
+ echo " --- thinknagent logs (last 50 lines) ---"
439
+ tail -n 50 "$log_file"
440
+ echo " --- end of logs ---"
441
+ echo ""
442
+ else
443
+ log_info "No logs found yet at $log_file"
444
+ fi
445
+ }
446
+
447
+ cmd_revoke() {
448
+ cmd_stop
449
+ rm -f "$CONFIG_FILE" "$PID_FILE"
450
+ log_ok "All credentials cleared from ${CONFIG_DIR}."
451
+ log_info "To re-register: thinknagent init --room <roomId>"
452
+ }
453
+
454
+ cmd_app() {
455
+ local port="${1:-4455}"
456
+ if command -v thinknagent >/dev/null 2>&1; then
457
+ thinknagent app -p "$port"
458
+ elif command -v npx >/dev/null 2>&1; then
459
+ npx thinknagent app -p "$port"
460
+ elif [[ -f "${HOME}/.thinknagent/node_modules/.bin/thinknagent" ]]; then
461
+ "${HOME}/.thinknagent/node_modules/.bin/thinknagent" app -p "$port"
462
+ elif [[ -f "$(dirname "${BASH_SOURCE[0]}")/../bin/thinknagent.js" ]]; then
463
+ node "$(dirname "${BASH_SOURCE[0]}")/../bin/thinknagent.js" app -p "$port"
464
+ else
465
+ log_err "Node.js required for GUI app. Run 'thinknagent.sh daemon' for background agent."
466
+ fi
467
+ }
468
+
469
+ cmd_status() {
470
+ local server=$(get_json_val "serverUrl")
471
+ local name=$(get_json_val "name")
472
+ local agentId=$(get_json_val "agentId")
473
+ local room=$(get_json_val "roomId")
474
+ local token=$(get_json_val "agentToken")
475
+ local status=$(get_json_val "status")
476
+
477
+ echo ""
478
+ echo " thinknagent Status"
479
+ echo " ─────────────────────────────────────────────"
480
+ echo " Name : ${name:-—}"
481
+ echo " Server : ${server:-—}"
482
+ echo " Room : ${room:-—}"
483
+ echo " Agent ID : ${agentId:-—}"
484
+ echo " Auth State : $([[ -n "$token" ]] && echo "APPROVED (Active)" || echo "PENDING (Waiting for Owner approval)")"
485
+ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null; then
486
+ echo " Process : RUNNING (PID $(cat "$PID_FILE"))"
487
+ else
488
+ echo " Process : STOPPED"
489
+ fi
490
+ echo " ─────────────────────────────────────────────"
491
+ echo ""
492
+ }
493
+
494
+ cmd_service() {
495
+ if [[ $EUID -ne 0 ]]; then
496
+ log_err "Please run with sudo to setup systemd service: sudo thinknagent daemon install"
497
+ exit 1
498
+ fi
499
+
500
+ local real_user="${SUDO_USER:-$(whoami)}"
501
+ local real_home=$(eval echo "~$real_user")
502
+ local script_path="${real_home}/.thinknagent/thinknagent.sh"
503
+ if [[ ! -f "$script_path" ]]; then
504
+ script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
505
+ fi
506
+
507
+ cat > /etc/systemd/system/thinknagent.service << EOF
508
+ [Unit]
509
+ Description=ThinkNCollab Server Agent (Standalone)
510
+ After=network-online.target
511
+ Wants=network-online.target
512
+
513
+ [Service]
514
+ Type=simple
515
+ User=${real_user}
516
+ ExecStart=/bin/bash ${script_path} start
517
+ Restart=always
518
+ RestartSec=5
519
+ Environment=NODE_ENV=production
520
+ StandardOutput=journal
521
+ StandardError=journal
522
+ SyslogIdentifier=thinknagent
523
+
524
+ [Install]
525
+ WantedBy=multi-user.target
526
+ EOF
527
+
528
+ systemctl daemon-reload
529
+ systemctl enable thinknagent.service
530
+ systemctl restart thinknagent.service
531
+ log_ok "Systemd service 'thinknagent' installed and started successfully for user ${real_user}!"
532
+ log_info "Check service anytime: systemctl status thinknagent"
533
+ log_info "Check live logs: journalctl -u thinknagent -f"
534
+ }
535
+
536
+ case "${1:-status}" in
537
+ init) shift; cmd_init "$@" ;;
538
+ app) shift; cmd_app "$@" ;;
539
+ start) shift; cmd_start "$@" ;;
540
+ stop) cmd_stop ;;
541
+ restart) cmd_restart ;;
542
+ daemon)
543
+ case "${2:-start}" in
544
+ install) cmd_service ;;
545
+ start) cmd_daemon ;;
546
+ stop) cmd_stop ;;
547
+ restart) cmd_restart ;;
548
+ status) cmd_status ;;
549
+ *) cmd_daemon ;;
550
+ esac
551
+ ;;
552
+ logs) cmd_logs ;;
553
+ revoke) cmd_revoke ;;
554
+ status) cmd_status ;;
555
+ service) cmd_service ;;
556
+ *)
557
+ echo "Usage: thinknagent {init|start|stop|restart|daemon|status|logs|app|revoke|service}"
558
+ exit 1
559
+ ;;
560
+ esac