@geekbeer/minion 2.23.0 → 2.32.0
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/core/lib/platform.js +117 -0
- package/{routes → core/routes}/health.js +1 -1
- package/{routes → core/routes}/routines.js +44 -4
- package/{routes → core/routes}/skills.js +3 -3
- package/{routes → core/routes}/workflows.js +4 -4
- package/{chat-store.js → core/stores/chat-store.js} +1 -1
- package/{execution-store.js → core/stores/execution-store.js} +1 -1
- package/{routine-store.js → core/stores/routine-store.js} +1 -1
- package/{workflow-store.js → core/stores/workflow-store.js} +1 -1
- package/{minion-cli.sh → linux/minion-cli.sh} +245 -4
- package/{routes → linux/routes}/chat.js +3 -3
- package/{routes → linux/routes}/commands.js +1 -1
- package/{routes → linux/routes}/config.js +3 -3
- package/{routes → linux/routes}/directives.js +5 -5
- package/{routes → linux/routes}/files.js +2 -2
- package/{routes → linux/routes}/terminal.js +2 -2
- package/{routine-runner.js → linux/routine-runner.js} +4 -4
- package/{server.js → linux/server.js} +71 -36
- package/{workflow-runner.js → linux/workflow-runner.js} +4 -4
- package/package.json +16 -20
- package/win/bin/hq-win.js +18 -0
- package/win/bin/hq.ps1 +108 -0
- package/win/bin/minion-cli-win.js +20 -0
- package/win/lib/llm-checker.js +115 -0
- package/win/lib/log-manager.js +119 -0
- package/win/lib/process-manager.js +112 -0
- package/win/minion-cli.ps1 +869 -0
- package/win/routes/chat.js +280 -0
- package/win/routes/commands.js +101 -0
- package/win/routes/config.js +227 -0
- package/win/routes/directives.js +136 -0
- package/win/routes/files.js +283 -0
- package/win/routes/terminal.js +316 -0
- package/win/routine-runner.js +324 -0
- package/win/server.js +230 -0
- package/win/terminal-server.js +234 -0
- package/win/workflow-runner.js +380 -0
- package/routes/index.js +0 -106
- /package/{api.js → core/api.js} +0 -0
- /package/{config.js → core/config.js} +0 -0
- /package/{lib → core/lib}/auth.js +0 -0
- /package/{lib → core/lib}/llm-checker.js +0 -0
- /package/{lib → core/lib}/log-manager.js +0 -0
- /package/{routes → core/routes}/auth.js +0 -0
- /package/{bin → linux/bin}/hq +0 -0
- /package/{lib → linux/lib}/process-manager.js +0 -0
- /package/{terminal-proxy.js → linux/terminal-proxy.js} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform utility module
|
|
3
|
+
*
|
|
4
|
+
* Provides platform-aware paths, separators, and helpers.
|
|
5
|
+
* Used by win/ modules to avoid hardcoded Unix paths.
|
|
6
|
+
* This file does NOT modify any existing Linux module behavior.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const os = require('os')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
|
|
13
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
14
|
+
const PATH_SEPARATOR = path.delimiter // ';' on Windows, ':' on Unix
|
|
15
|
+
const TEMP_DIR = os.tmpdir()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the data directory for minion agent persistent files.
|
|
19
|
+
* Windows: %PROGRAMDATA%\minion-agent (or fallback to %USERPROFILE%\.minion-agent)
|
|
20
|
+
* Linux: /opt/minion-agent (existing behavior)
|
|
21
|
+
*/
|
|
22
|
+
function resolveDataDir() {
|
|
23
|
+
if (IS_WINDOWS) {
|
|
24
|
+
const programData = process.env.PROGRAMDATA || process.env.ALLUSERSPROFILE
|
|
25
|
+
if (programData) {
|
|
26
|
+
const dir = path.join(programData, 'minion-agent')
|
|
27
|
+
try {
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
29
|
+
return dir
|
|
30
|
+
} catch {
|
|
31
|
+
// Fall through to home-based path
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return path.join(os.homedir(), '.minion-agent')
|
|
35
|
+
}
|
|
36
|
+
return '/opt/minion-agent'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DATA_DIR = resolveDataDir()
|
|
40
|
+
const LOG_DIR = path.join(DATA_DIR, 'logs')
|
|
41
|
+
const MARKER_DIR = path.join(TEMP_DIR, 'minion-executions')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build extended PATH including common CLI installation locations.
|
|
45
|
+
* Uses the correct platform separator.
|
|
46
|
+
* @param {string} homeDir - User home directory
|
|
47
|
+
* @returns {string} Extended PATH string
|
|
48
|
+
*/
|
|
49
|
+
function buildExtendedPath(homeDir) {
|
|
50
|
+
const additionalPaths = IS_WINDOWS
|
|
51
|
+
? [
|
|
52
|
+
path.join(homeDir, '.local', 'bin'),
|
|
53
|
+
path.join(homeDir, '.npm-global'),
|
|
54
|
+
path.join(homeDir, '.claude', 'bin'),
|
|
55
|
+
path.join(homeDir, 'AppData', 'Roaming', 'npm'),
|
|
56
|
+
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'nodejs'),
|
|
57
|
+
]
|
|
58
|
+
: [
|
|
59
|
+
path.join(homeDir, 'bin'),
|
|
60
|
+
path.join(homeDir, '.npm-global', 'bin'),
|
|
61
|
+
path.join(homeDir, '.local', 'bin'),
|
|
62
|
+
path.join(homeDir, '.claude', 'bin'),
|
|
63
|
+
'/usr/local/bin',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const currentPath = process.env.PATH || ''
|
|
67
|
+
return [...additionalPaths, currentPath].join(PATH_SEPARATOR)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get exit code file path for a session.
|
|
72
|
+
* @param {string} sessionName - Session identifier
|
|
73
|
+
* @returns {string} Path to exit code file
|
|
74
|
+
*/
|
|
75
|
+
function getExitCodePath(sessionName) {
|
|
76
|
+
return path.join(TEMP_DIR, `minion-exit-${sessionName}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the default shell for spawning processes.
|
|
81
|
+
* @returns {string} Shell executable path
|
|
82
|
+
*/
|
|
83
|
+
function getDefaultShell() {
|
|
84
|
+
if (IS_WINDOWS) {
|
|
85
|
+
return process.env.COMSPEC || 'cmd.exe'
|
|
86
|
+
}
|
|
87
|
+
return process.env.SHELL || '/bin/sh'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve .env file path.
|
|
92
|
+
* Prefers DATA_DIR/.env, falls back to ~/minion.env.
|
|
93
|
+
* @param {string} homeDir - User home directory
|
|
94
|
+
* @returns {string} Path to .env file
|
|
95
|
+
*/
|
|
96
|
+
function resolveEnvFilePath(homeDir) {
|
|
97
|
+
const dataEnv = path.join(DATA_DIR, '.env')
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(path.dirname(dataEnv), fs.constants.W_OK)
|
|
100
|
+
return dataEnv
|
|
101
|
+
} catch {
|
|
102
|
+
return path.join(homeDir, 'minion.env')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
IS_WINDOWS,
|
|
108
|
+
PATH_SEPARATOR,
|
|
109
|
+
TEMP_DIR,
|
|
110
|
+
DATA_DIR,
|
|
111
|
+
LOG_DIR,
|
|
112
|
+
MARKER_DIR,
|
|
113
|
+
buildExtendedPath,
|
|
114
|
+
getExitCodePath,
|
|
115
|
+
getDefaultShell,
|
|
116
|
+
resolveEnvFilePath,
|
|
117
|
+
}
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Endpoints:
|
|
5
5
|
* - GET /api/routines - List all routines with status
|
|
6
|
-
* - POST /api/routines - Receive routines
|
|
6
|
+
* - POST /api/routines - Receive routines (upsert/additive)
|
|
7
7
|
* - POST /api/routines/sync - Pull routines from HQ and sync locally
|
|
8
8
|
* - PUT /api/routines/:id/schedule - Update a routine schedule (cron_expression, is_active)
|
|
9
9
|
* - DELETE /api/routines/:id - Remove a routine
|
|
10
|
+
* - POST /api/routines/bulk-toggle - Set is_active for all routines at once
|
|
10
11
|
* - POST /api/routines/trigger - Manual trigger for a routine
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const { verifyToken } = require('../lib/auth')
|
|
14
|
-
const
|
|
15
|
-
const routineStore = require('../routine-store')
|
|
15
|
+
const routineStore = require('../stores/routine-store')
|
|
16
16
|
const api = require('../api')
|
|
17
17
|
const { isHqConfigured } = require('../config')
|
|
18
18
|
|
|
@@ -20,7 +20,8 @@ const { isHqConfigured } = require('../config')
|
|
|
20
20
|
* Register routine routes as Fastify plugin
|
|
21
21
|
* @param {import('fastify').FastifyInstance} fastify
|
|
22
22
|
*/
|
|
23
|
-
async function routineRoutes(fastify) {
|
|
23
|
+
async function routineRoutes(fastify, opts) {
|
|
24
|
+
const { routineRunner } = opts
|
|
24
25
|
// Receive routines from HQ (upsert: add new, update existing)
|
|
25
26
|
fastify.post('/api/routines', async (request, reply) => {
|
|
26
27
|
if (!verifyToken(request)) {
|
|
@@ -218,6 +219,45 @@ async function routineRoutes(fastify) {
|
|
|
218
219
|
}
|
|
219
220
|
})
|
|
220
221
|
|
|
222
|
+
// Bulk toggle: set is_active for all routines at once
|
|
223
|
+
fastify.post('/api/routines/bulk-toggle', async (request, reply) => {
|
|
224
|
+
if (!verifyToken(request)) {
|
|
225
|
+
reply.code(401)
|
|
226
|
+
return { success: false, error: 'Unauthorized' }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { is_active } = request.body || {}
|
|
230
|
+
|
|
231
|
+
if (typeof is_active !== 'boolean') {
|
|
232
|
+
reply.code(400)
|
|
233
|
+
return { success: false, error: 'is_active (boolean) is required' }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`[Routines] Bulk toggle: setting all routines is_active=${is_active}`)
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const routines = await routineStore.load()
|
|
240
|
+
|
|
241
|
+
for (const routine of routines) {
|
|
242
|
+
routine.is_active = is_active
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await routineStore.save(routines)
|
|
246
|
+
routineRunner.loadRoutines(routines)
|
|
247
|
+
|
|
248
|
+
const count = routines.length
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
message: `${count} routines set to is_active=${is_active}`,
|
|
252
|
+
count,
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error(`[Routines] Failed to bulk toggle: ${error.message}`)
|
|
256
|
+
reply.code(500)
|
|
257
|
+
return { success: false, error: error.message }
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
221
261
|
// Manual trigger: run a routine immediately
|
|
222
262
|
fastify.post('/api/routines/trigger', async (request, reply) => {
|
|
223
263
|
if (!verifyToken(request)) {
|
|
@@ -17,8 +17,7 @@ const crypto = require('crypto')
|
|
|
17
17
|
const { verifyToken } = require('../lib/auth')
|
|
18
18
|
const api = require('../api')
|
|
19
19
|
const { config, isHqConfigured } = require('../config')
|
|
20
|
-
const
|
|
21
|
-
const executionStore = require('../execution-store')
|
|
20
|
+
const executionStore = require('../stores/execution-store')
|
|
22
21
|
const logManager = require('../lib/log-manager')
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -128,7 +127,8 @@ async function pushSkillToHQ(name) {
|
|
|
128
127
|
* Register skill routes as Fastify plugin
|
|
129
128
|
* @param {import('fastify').FastifyInstance} fastify
|
|
130
129
|
*/
|
|
131
|
-
async function skillRoutes(fastify) {
|
|
130
|
+
async function skillRoutes(fastify, opts) {
|
|
131
|
+
const { workflowRunner } = opts
|
|
132
132
|
// List deployed skills from local .claude/skills directory
|
|
133
133
|
fastify.get('/api/list-skills', async (request, reply) => {
|
|
134
134
|
if (!verifyToken(request)) {
|
|
@@ -20,9 +20,8 @@ const fs = require('fs').promises
|
|
|
20
20
|
const path = require('path')
|
|
21
21
|
|
|
22
22
|
const { verifyToken } = require('../lib/auth')
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const executionStore = require('../execution-store')
|
|
23
|
+
const workflowStore = require('../stores/workflow-store')
|
|
24
|
+
const executionStore = require('../stores/execution-store')
|
|
26
25
|
const logManager = require('../lib/log-manager')
|
|
27
26
|
const api = require('../api')
|
|
28
27
|
const { config, isHqConfigured } = require('../config')
|
|
@@ -32,7 +31,8 @@ const { writeSkillToLocal, pushSkillToHQ } = require('./skills')
|
|
|
32
31
|
* Register workflow routes as Fastify plugin
|
|
33
32
|
* @param {import('fastify').FastifyInstance} fastify
|
|
34
33
|
*/
|
|
35
|
-
async function workflowRoutes(fastify) {
|
|
34
|
+
async function workflowRoutes(fastify, opts) {
|
|
35
|
+
const { workflowRunner } = opts
|
|
36
36
|
// Receive workflows from HQ (upsert: add new, update existing)
|
|
37
37
|
fastify.post('/api/workflows', async (request, reply) => {
|
|
38
38
|
if (!verifyToken(request)) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs').promises
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
|
-
const { config } = require('
|
|
10
|
+
const { config } = require('../config')
|
|
11
11
|
|
|
12
12
|
// Routine file location: /opt/minion-agent/routines.json (systemd/supervisord)
|
|
13
13
|
// or ~/routines.json (standalone)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs').promises
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
|
-
const { config } = require('
|
|
10
|
+
const { config } = require('../config')
|
|
11
11
|
|
|
12
12
|
// Workflow file location: /opt/minion-agent/workflows.json (systemd/supervisord)
|
|
13
13
|
// or ~/workflows.json (standalone)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
7
|
# sudo minion-cli setup [options] # Set up minion agent service (root)
|
|
8
|
+
# sudo minion-cli reconfigure [options] # Re-register with new HQ credentials (root)
|
|
8
9
|
# sudo minion-cli start # Start agent service (root)
|
|
9
10
|
# sudo minion-cli stop # Stop agent service (root)
|
|
10
11
|
# sudo minion-cli restart # Restart agent service (root)
|
|
@@ -40,7 +41,7 @@ fi
|
|
|
40
41
|
|
|
41
42
|
# Resolve version from package.json (installed location)
|
|
42
43
|
CLI_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
|
43
|
-
CLI_VERSION="$(node -p "require('${CLI_DIR}
|
|
44
|
+
CLI_VERSION="$(node -p "require('${CLI_DIR}/../package.json').version")"
|
|
44
45
|
|
|
45
46
|
# Use sudo only when not running as root
|
|
46
47
|
SUDO=""
|
|
@@ -88,6 +89,28 @@ svc_control() {
|
|
|
88
89
|
|
|
89
90
|
AGENT_URL="${MINION_AGENT_URL:-http://localhost:8080}"
|
|
90
91
|
|
|
92
|
+
# Detect LAN IPv4 address (best-effort)
|
|
93
|
+
detect_lan_ip() {
|
|
94
|
+
local ip=""
|
|
95
|
+
# Method 1: Use ip route to find the source IP for default gateway
|
|
96
|
+
if command -v ip &>/dev/null; then
|
|
97
|
+
ip=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[\d.]+' | head -1)
|
|
98
|
+
fi
|
|
99
|
+
# Method 2: Parse ip addr for non-loopback, non-docker, non-APIPA addresses
|
|
100
|
+
if [ -z "$ip" ] && command -v ip &>/dev/null; then
|
|
101
|
+
ip=$(ip -4 addr show scope global 2>/dev/null \
|
|
102
|
+
| grep -oP 'inet \K[\d.]+' \
|
|
103
|
+
| grep -v '^172\.17\.' \
|
|
104
|
+
| grep -v '^169\.254\.' \
|
|
105
|
+
| head -1)
|
|
106
|
+
fi
|
|
107
|
+
# Method 3: hostname -I (available on most Linux distros)
|
|
108
|
+
if [ -z "$ip" ] && command -v hostname &>/dev/null; then
|
|
109
|
+
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
110
|
+
fi
|
|
111
|
+
echo "$ip"
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
# Auto-load .env so that API_TOKEN etc. are available in interactive shells
|
|
92
115
|
ENV_FILE="/opt/minion-agent/.env"
|
|
93
116
|
if [ -f "$ENV_FILE" ] && [ -r "$ENV_FILE" ]; then
|
|
@@ -318,7 +341,7 @@ do_setup() {
|
|
|
318
341
|
echo "[6/${TOTAL_STEPS}] Creating service configuration ($PROC_MGR)..."
|
|
319
342
|
local NPM_ROOT
|
|
320
343
|
NPM_ROOT="$(npm root -g)"
|
|
321
|
-
local SERVER_PATH="${NPM_ROOT}/@geekbeer/minion/server.js"
|
|
344
|
+
local SERVER_PATH="${NPM_ROOT}/@geekbeer/minion/linux/server.js"
|
|
322
345
|
|
|
323
346
|
if [ ! -f "$SERVER_PATH" ]; then
|
|
324
347
|
echo " ERROR: server.js not found at $SERVER_PATH"
|
|
@@ -731,14 +754,33 @@ CFEOF
|
|
|
731
754
|
echo "Notifying HQ of setup completion..."
|
|
732
755
|
local NOTIFY_RESPONSE
|
|
733
756
|
local HOSTNAME_VAL
|
|
757
|
+
local LAN_IP
|
|
734
758
|
HOSTNAME_VAL=$(hostname 2>/dev/null || echo "")
|
|
759
|
+
LAN_IP=$(detect_lan_ip)
|
|
760
|
+
|
|
761
|
+
# Build request body with LAN IP detection
|
|
762
|
+
# Docker: use hostname (container name) for internal_ip_address (resolved via Docker DNS)
|
|
763
|
+
# Self-hosted: use LAN IP for both fields (hostname is not resolvable on LAN)
|
|
764
|
+
local BODY
|
|
765
|
+
if [ -f /.dockerenv ]; then
|
|
766
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
767
|
+
elif [ -n "$LAN_IP" ]; then
|
|
768
|
+
BODY="{\"internal_ip_address\":\"${LAN_IP}\",\"ip_address\":\"${LAN_IP}\"}"
|
|
769
|
+
else
|
|
770
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
771
|
+
fi
|
|
772
|
+
|
|
735
773
|
NOTIFY_RESPONSE=$(curl -sfL -X POST "${HQ_URL}/api/minion/setup-complete" \
|
|
736
774
|
-H "Content-Type: application/json" \
|
|
737
775
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
|
738
|
-
-d "
|
|
776
|
+
-d "$BODY" 2>&1) || true
|
|
739
777
|
|
|
740
778
|
if echo "$NOTIFY_RESPONSE" | grep -q '"success":true' 2>/dev/null; then
|
|
741
|
-
|
|
779
|
+
if [ -n "$LAN_IP" ] && [ ! -f /.dockerenv ]; then
|
|
780
|
+
echo " -> HQ notified successfully (LAN IP: ${LAN_IP})"
|
|
781
|
+
else
|
|
782
|
+
echo " -> HQ notified successfully"
|
|
783
|
+
fi
|
|
742
784
|
else
|
|
743
785
|
echo " -> HQ notification skipped (HQ may not be reachable)"
|
|
744
786
|
fi
|
|
@@ -763,6 +805,193 @@ CFEOF
|
|
|
763
805
|
fi
|
|
764
806
|
}
|
|
765
807
|
|
|
808
|
+
# ============================================================
|
|
809
|
+
# reconfigure subcommand
|
|
810
|
+
# ============================================================
|
|
811
|
+
do_reconfigure() {
|
|
812
|
+
local HQ_URL=""
|
|
813
|
+
local MINION_ID=""
|
|
814
|
+
local API_TOKEN=""
|
|
815
|
+
|
|
816
|
+
# Parse arguments
|
|
817
|
+
while [[ $# -gt 0 ]]; do
|
|
818
|
+
case "$1" in
|
|
819
|
+
--hq-url)
|
|
820
|
+
HQ_URL="$2"
|
|
821
|
+
shift 2
|
|
822
|
+
;;
|
|
823
|
+
--minion-id)
|
|
824
|
+
MINION_ID="$2"
|
|
825
|
+
shift 2
|
|
826
|
+
;;
|
|
827
|
+
--api-token)
|
|
828
|
+
API_TOKEN="$2"
|
|
829
|
+
shift 2
|
|
830
|
+
;;
|
|
831
|
+
*)
|
|
832
|
+
echo "Unknown option: $1"
|
|
833
|
+
echo "Usage: sudo minion-cli reconfigure --hq-url <URL> --minion-id <UUID> --api-token <TOKEN>"
|
|
834
|
+
exit 1
|
|
835
|
+
;;
|
|
836
|
+
esac
|
|
837
|
+
done
|
|
838
|
+
|
|
839
|
+
# Validate required arguments
|
|
840
|
+
if [ -z "$HQ_URL" ] || [ -z "$MINION_ID" ] || [ -z "$API_TOKEN" ]; then
|
|
841
|
+
echo "ERROR: All three options are required: --hq-url, --minion-id, --api-token"
|
|
842
|
+
echo "Usage: sudo minion-cli reconfigure --hq-url <URL> --minion-id <UUID> --api-token <TOKEN>"
|
|
843
|
+
exit 1
|
|
844
|
+
fi
|
|
845
|
+
|
|
846
|
+
# Check that .env exists (setup must have been run before)
|
|
847
|
+
if [ ! -f /opt/minion-agent/.env ]; then
|
|
848
|
+
echo "ERROR: /opt/minion-agent/.env not found."
|
|
849
|
+
echo "It looks like minion-cli setup has not been run on this server."
|
|
850
|
+
echo "Please run 'sudo minion-cli setup' first for initial installation."
|
|
851
|
+
exit 1
|
|
852
|
+
fi
|
|
853
|
+
|
|
854
|
+
echo "========================================="
|
|
855
|
+
echo " @geekbeer/minion Reconfigure"
|
|
856
|
+
echo "========================================="
|
|
857
|
+
echo "HQ: $HQ_URL"
|
|
858
|
+
echo "Minion ID: $MINION_ID"
|
|
859
|
+
echo ""
|
|
860
|
+
|
|
861
|
+
# Step 1: Read existing .env and preserve non-credential keys
|
|
862
|
+
echo "[1/4] Updating .env credentials..."
|
|
863
|
+
local ENV_CONTENT=""
|
|
864
|
+
ENV_CONTENT+="# Minion Agent Configuration\n"
|
|
865
|
+
ENV_CONTENT+="# Reconfigured by minion-cli reconfigure\n\n"
|
|
866
|
+
ENV_CONTENT+="HQ_URL=${HQ_URL}\n"
|
|
867
|
+
ENV_CONTENT+="API_TOKEN=${API_TOKEN}\n"
|
|
868
|
+
ENV_CONTENT+="MINION_ID=${MINION_ID}\n"
|
|
869
|
+
|
|
870
|
+
# Preserve non-credential keys from existing .env
|
|
871
|
+
while IFS='=' read -r key value; do
|
|
872
|
+
[[ -z "$key" || "$key" == \#* ]] && continue
|
|
873
|
+
case "$key" in
|
|
874
|
+
HQ_URL|API_TOKEN|MINION_ID) ;; # Skip — already set above
|
|
875
|
+
*) ENV_CONTENT+="${key}=${value}\n" ;;
|
|
876
|
+
esac
|
|
877
|
+
done < /opt/minion-agent/.env
|
|
878
|
+
|
|
879
|
+
echo -e "$ENV_CONTENT" | $SUDO tee /opt/minion-agent/.env > /dev/null
|
|
880
|
+
echo " -> /opt/minion-agent/.env updated"
|
|
881
|
+
|
|
882
|
+
# Step 2: Update process manager config & restart service
|
|
883
|
+
echo "[2/4] Restarting minion-agent service..."
|
|
884
|
+
if [ -z "$PROC_MGR" ]; then
|
|
885
|
+
echo " WARNING: No supported process manager found"
|
|
886
|
+
echo " Please restart the minion-agent service manually"
|
|
887
|
+
elif [ "$PROC_MGR" = "supervisord" ]; then
|
|
888
|
+
# supervisord bakes env vars into conf — must regenerate
|
|
889
|
+
local CONF_FILE="/etc/supervisor/conf.d/minion-agent.conf"
|
|
890
|
+
if [ -f "$CONF_FILE" ]; then
|
|
891
|
+
# Read current conf to preserve command, user, and other settings
|
|
892
|
+
local CURRENT_COMMAND
|
|
893
|
+
CURRENT_COMMAND=$(grep '^command=' "$CONF_FILE" | head -1)
|
|
894
|
+
local CURRENT_DIRECTORY
|
|
895
|
+
CURRENT_DIRECTORY=$(grep '^directory=' "$CONF_FILE" | head -1)
|
|
896
|
+
local CURRENT_USER_LINE
|
|
897
|
+
CURRENT_USER_LINE=$(grep '^user=' "$CONF_FILE" || echo "")
|
|
898
|
+
|
|
899
|
+
# Rebuild environment line from updated .env
|
|
900
|
+
local ENV_LINE="environment="
|
|
901
|
+
# Detect home dir for the service user
|
|
902
|
+
local SVC_USER
|
|
903
|
+
SVC_USER=$(echo "$CURRENT_USER_LINE" | sed 's/user=//')
|
|
904
|
+
local SVC_HOME="$HOME"
|
|
905
|
+
if [ -n "$SVC_USER" ]; then
|
|
906
|
+
SVC_HOME=$(getent passwd "$SVC_USER" | cut -d: -f6 || echo "$HOME")
|
|
907
|
+
fi
|
|
908
|
+
local ENV_PAIRS=("HOME=\"${SVC_HOME}\"" "DISPLAY=\":99\"")
|
|
909
|
+
while IFS='=' read -r key value; do
|
|
910
|
+
[[ -z "$key" || "$key" == \#* ]] && continue
|
|
911
|
+
ENV_PAIRS+=("${key}=\"${value}\"")
|
|
912
|
+
done < /opt/minion-agent/.env
|
|
913
|
+
ENV_LINE+="$(IFS=,; echo "${ENV_PAIRS[*]}")"
|
|
914
|
+
|
|
915
|
+
$SUDO tee "$CONF_FILE" > /dev/null <<SUPEOF
|
|
916
|
+
[program:minion-agent]
|
|
917
|
+
${CURRENT_COMMAND}
|
|
918
|
+
${CURRENT_DIRECTORY}
|
|
919
|
+
${CURRENT_USER_LINE}
|
|
920
|
+
${ENV_LINE}
|
|
921
|
+
autorestart=true
|
|
922
|
+
priority=500
|
|
923
|
+
startsecs=3
|
|
924
|
+
stdout_logfile=/var/log/supervisor/minion-agent.log
|
|
925
|
+
stderr_logfile=/var/log/supervisor/minion-agent.log
|
|
926
|
+
SUPEOF
|
|
927
|
+
echo " -> supervisord conf updated"
|
|
928
|
+
fi
|
|
929
|
+
$SUDO supervisorctl reread > /dev/null 2>&1
|
|
930
|
+
$SUDO supervisorctl update > /dev/null 2>&1
|
|
931
|
+
echo " -> minion-agent restarted (supervisord)"
|
|
932
|
+
else
|
|
933
|
+
# systemd reads .env via EnvironmentFile — just restart
|
|
934
|
+
svc_control restart
|
|
935
|
+
echo " -> minion-agent restarted (systemd)"
|
|
936
|
+
fi
|
|
937
|
+
|
|
938
|
+
# Step 3: Health check
|
|
939
|
+
echo "[3/4] Verifying agent health..."
|
|
940
|
+
local HEALTH_OK=false
|
|
941
|
+
for i in $(seq 1 5); do
|
|
942
|
+
if curl -sf http://localhost:8080/api/health > /dev/null 2>&1; then
|
|
943
|
+
HEALTH_OK=true
|
|
944
|
+
break
|
|
945
|
+
fi
|
|
946
|
+
sleep 2
|
|
947
|
+
done
|
|
948
|
+
if [ "$HEALTH_OK" = true ]; then
|
|
949
|
+
echo " -> Agent is healthy"
|
|
950
|
+
else
|
|
951
|
+
echo " WARNING: Agent health check failed after 5 attempts"
|
|
952
|
+
echo " Check logs for details"
|
|
953
|
+
fi
|
|
954
|
+
|
|
955
|
+
# Step 4: Notify HQ (best-effort — heartbeat will also notify automatically)
|
|
956
|
+
echo "[4/4] Notifying HQ..."
|
|
957
|
+
local NOTIFY_RESPONSE
|
|
958
|
+
local HOSTNAME_VAL
|
|
959
|
+
local LAN_IP
|
|
960
|
+
HOSTNAME_VAL=$(hostname 2>/dev/null || echo "")
|
|
961
|
+
LAN_IP=$(detect_lan_ip)
|
|
962
|
+
|
|
963
|
+
local BODY
|
|
964
|
+
if [ -f /.dockerenv ]; then
|
|
965
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
966
|
+
elif [ -n "$LAN_IP" ]; then
|
|
967
|
+
BODY="{\"internal_ip_address\":\"${LAN_IP}\",\"ip_address\":\"${LAN_IP}\"}"
|
|
968
|
+
else
|
|
969
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
970
|
+
fi
|
|
971
|
+
|
|
972
|
+
NOTIFY_RESPONSE=$(curl -sfL -X POST "${HQ_URL}/api/minion/setup-complete" \
|
|
973
|
+
-H "Content-Type: application/json" \
|
|
974
|
+
-H "Authorization: Bearer ${API_TOKEN}" \
|
|
975
|
+
-d "$BODY" 2>&1) || true
|
|
976
|
+
|
|
977
|
+
if echo "$NOTIFY_RESPONSE" | grep -q '"success":true' 2>/dev/null; then
|
|
978
|
+
if [ -n "$LAN_IP" ] && [ ! -f /.dockerenv ]; then
|
|
979
|
+
echo " -> HQ notified successfully (LAN IP: ${LAN_IP})"
|
|
980
|
+
else
|
|
981
|
+
echo " -> HQ notified successfully"
|
|
982
|
+
fi
|
|
983
|
+
else
|
|
984
|
+
echo " -> Skipped (heartbeat will notify HQ within 30s)"
|
|
985
|
+
fi
|
|
986
|
+
|
|
987
|
+
echo ""
|
|
988
|
+
echo "========================================="
|
|
989
|
+
echo " Reconfigure Complete!"
|
|
990
|
+
echo "========================================="
|
|
991
|
+
echo ""
|
|
992
|
+
echo "The minion should appear online in HQ shortly."
|
|
993
|
+
}
|
|
994
|
+
|
|
766
995
|
# ============================================================
|
|
767
996
|
# Main command dispatch
|
|
768
997
|
# ============================================================
|
|
@@ -776,6 +1005,12 @@ case "${1:-}" in
|
|
|
776
1005
|
do_setup "$@"
|
|
777
1006
|
;;
|
|
778
1007
|
|
|
1008
|
+
reconfigure)
|
|
1009
|
+
require_root reconfigure
|
|
1010
|
+
shift
|
|
1011
|
+
do_reconfigure "$@"
|
|
1012
|
+
;;
|
|
1013
|
+
|
|
779
1014
|
status)
|
|
780
1015
|
curl -s "$AGENT_URL/api/status" | jq .
|
|
781
1016
|
;;
|
|
@@ -866,6 +1101,7 @@ case "${1:-}" in
|
|
|
866
1101
|
echo ""
|
|
867
1102
|
echo "Usage:"
|
|
868
1103
|
echo " sudo minion-cli setup [options] # Set up agent service (root)"
|
|
1104
|
+
echo " sudo minion-cli reconfigure [options] # Re-register with new HQ credentials (root)"
|
|
869
1105
|
echo " sudo minion-cli start # Start agent service (root)"
|
|
870
1106
|
echo " sudo minion-cli stop # Stop agent service (root)"
|
|
871
1107
|
echo " sudo minion-cli restart # Restart agent service (root)"
|
|
@@ -882,6 +1118,11 @@ case "${1:-}" in
|
|
|
882
1118
|
echo " --api-token <TOKEN> API token (optional)"
|
|
883
1119
|
echo " --setup-tunnel Set up cloudflared tunnel (requires --hq-url, --api-token)"
|
|
884
1120
|
echo ""
|
|
1121
|
+
echo "Reconfigure options:"
|
|
1122
|
+
echo " --hq-url <URL> HQ server URL (required)"
|
|
1123
|
+
echo " --minion-id <UUID> Minion ID (required)"
|
|
1124
|
+
echo " --api-token <TOKEN> API token (required)"
|
|
1125
|
+
echo ""
|
|
885
1126
|
echo "Status values: online, offline, busy"
|
|
886
1127
|
echo ""
|
|
887
1128
|
echo "Environment:"
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
const { spawn } = require('child_process')
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const path = require('path')
|
|
20
|
-
const { verifyToken } = require('
|
|
21
|
-
const { config } = require('
|
|
22
|
-
const chatStore = require('
|
|
20
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
21
|
+
const { config } = require('../../core/config')
|
|
22
|
+
const chatStore = require('../../core/stores/chat-store')
|
|
23
23
|
|
|
24
24
|
/** @type {import('child_process').ChildProcess | null} */
|
|
25
25
|
let activeChatChild = null
|
|
@@ -10,7 +10,7 @@ const { exec } = require('child_process')
|
|
|
10
10
|
const { promisify } = require('util')
|
|
11
11
|
const execAsync = promisify(exec)
|
|
12
12
|
|
|
13
|
-
const { verifyToken } = require('
|
|
13
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
14
14
|
const { detectProcessManager, buildAllowedCommands } = require('../lib/process-manager')
|
|
15
15
|
|
|
16
16
|
const PROC_MGR = detectProcessManager()
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
const { execSync } = require('child_process')
|
|
11
11
|
const fs = require('fs')
|
|
12
12
|
const path = require('path')
|
|
13
|
-
const { verifyToken } = require('
|
|
14
|
-
const { clearLlmCache } = require('
|
|
15
|
-
const { config } = require('
|
|
13
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
14
|
+
const { clearLlmCache } = require('../../core/lib/llm-checker')
|
|
15
|
+
const { config } = require('../../core/config')
|
|
16
16
|
|
|
17
17
|
/** Keys that can be read/written via the config API */
|
|
18
18
|
const ALLOWED_ENV_KEYS = ['LLM_COMMAND']
|
|
@@ -12,12 +12,12 @@ const fs = require('fs').promises
|
|
|
12
12
|
const path = require('path')
|
|
13
13
|
const crypto = require('crypto')
|
|
14
14
|
|
|
15
|
-
const { verifyToken } = require('
|
|
16
|
-
const { config } = require('
|
|
17
|
-
const { writeSkillToLocal } = require('
|
|
15
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
16
|
+
const { config } = require('../../core/config')
|
|
17
|
+
const { writeSkillToLocal } = require('../../core/routes/skills')
|
|
18
18
|
const workflowRunner = require('../workflow-runner')
|
|
19
|
-
const executionStore = require('
|
|
20
|
-
const logManager = require('
|
|
19
|
+
const executionStore = require('../../core/stores/execution-store')
|
|
20
|
+
const logManager = require('../../core/lib/log-manager')
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Parse frontmatter from skill content to extract body
|
|
@@ -16,8 +16,8 @@ const fsSync = require('fs')
|
|
|
16
16
|
const path = require('path')
|
|
17
17
|
const { spawn } = require('child_process')
|
|
18
18
|
|
|
19
|
-
const { verifyToken } = require('
|
|
20
|
-
const { config } = require('
|
|
19
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
20
|
+
const { config } = require('../../core/config')
|
|
21
21
|
|
|
22
22
|
/** Base directory for file storage */
|
|
23
23
|
const FILES_DIR = path.join(config.HOME_DIR, 'files')
|