@geekbeer/minion 3.4.7 → 3.5.6
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/api.js +6 -5
- package/core/lib/thread-watcher.js +19 -5
- package/docs/api-reference.md +43 -16
- package/package.json +1 -1
- package/rules/core.md +28 -2
- package/win/minion-cli.ps1 +156 -163
package/core/api.js
CHANGED
|
@@ -87,8 +87,8 @@ async function sendHeartbeat(data) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Create a
|
|
91
|
-
* @param {object} data - { project_id
|
|
90
|
+
* Create a thread (project-scoped or workspace-scoped) on HQ.
|
|
91
|
+
* @param {object} data - { project_id?, scope?, category?, title, content, thread_type?, mentions?, context? }
|
|
92
92
|
* @returns {Promise<{ thread: object }>}
|
|
93
93
|
*/
|
|
94
94
|
async function createThread(data) {
|
|
@@ -99,11 +99,12 @@ async function createThread(data) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Get open threads
|
|
102
|
+
* Get open threads accessible to this minion.
|
|
103
|
+
* @param {'project' | 'workspace' | 'all'} [scope='all'] - Filter by scope
|
|
103
104
|
* @returns {Promise<{ threads: object[] }>}
|
|
104
105
|
*/
|
|
105
|
-
async function getOpenThreads() {
|
|
106
|
-
return request(
|
|
106
|
+
async function getOpenThreads(scope = 'all') {
|
|
107
|
+
return request(`/threads/open?scope=${scope}`)
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/**
|
|
@@ -130,9 +130,15 @@ async function pollOnce() {
|
|
|
130
130
|
* Process a single thread: check for new activity and evaluate if needed.
|
|
131
131
|
*/
|
|
132
132
|
async function processThread(thread, myProjects, now) {
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
// For workspace threads, create a synthetic project context
|
|
134
|
+
// For project threads, find role via membership
|
|
135
|
+
let myProject
|
|
136
|
+
if (thread.scope === 'workspace') {
|
|
137
|
+
myProject = { id: null, name: 'Workspace', role: 'engineer' }
|
|
138
|
+
} else {
|
|
139
|
+
myProject = myProjects.find(p => p.id === thread.project_id)
|
|
140
|
+
if (!myProject) return // Not a member
|
|
141
|
+
}
|
|
136
142
|
|
|
137
143
|
const state = readState.get(thread.id) || { lastMessageCount: 0, lastEvalAt: 0 }
|
|
138
144
|
|
|
@@ -212,7 +218,12 @@ async function evaluateWithLlm(threadSummary, threadDetail, allMessages, newMess
|
|
|
212
218
|
ctx.attempted_resolution ? `試行済み: ${ctx.attempted_resolution}` : '',
|
|
213
219
|
].filter(Boolean).join('\n')
|
|
214
220
|
|
|
215
|
-
const
|
|
221
|
+
const isWorkspace = threadDetail.scope === 'workspace'
|
|
222
|
+
const scopeContext = isWorkspace
|
|
223
|
+
? `あなたはワークスペースのメンバー(ID: ${config.MINION_ID})です。\nこれはプロジェクトに属さないワークスペーススレッドです(カテゴリ: ${threadDetail.category || 'general'})。`
|
|
224
|
+
: `あなたはプロジェクト「${myProject.name}」のチームメンバー(ロール: ${myProject.role}、ID: ${config.MINION_ID})です。`
|
|
225
|
+
|
|
226
|
+
const prompt = `${scopeContext}
|
|
216
227
|
以下のスレッドに対して、あなたが返信すべきかどうかを判断し、返信する場合はその内容を生成してください。
|
|
217
228
|
|
|
218
229
|
スレッドタイプ: ${threadType}
|
|
@@ -235,7 +246,7 @@ ${messageHistory || '(メッセージなし)'}
|
|
|
235
246
|
判断基準:
|
|
236
247
|
- 自分が起票したスレッドの場合、他のメンバーの回答を待つべき(追加情報がある場合を除く)
|
|
237
248
|
- メンション対象が特定のロールやミニオンに限定されている場合、自分が対象でなければ静観する
|
|
238
|
-
- 自分のロール(${myProject.role}
|
|
249
|
+
${isWorkspace ? '- ワークスペーススレッドではすべてのミニオンが参加可能。自分が貢献できる場合は積極的に参加する' : `- 自分のロール(${myProject.role})に関連する話題か`}
|
|
239
250
|
- 自分が貢献できる知見や意見があるか
|
|
240
251
|
- 既に十分な回答がある場合は重複を避ける
|
|
241
252
|
- 人間に聞くべき場合は @user メンションを含めて返信する`
|
|
@@ -281,6 +292,9 @@ ${messageHistory || '(メッセージなし)'}
|
|
|
281
292
|
*/
|
|
282
293
|
async function fallbackMemoryMatch(thread) {
|
|
283
294
|
try {
|
|
295
|
+
// Workspace threads have no project_id — skip memory matching
|
|
296
|
+
if (!thread.project_id) return
|
|
297
|
+
|
|
284
298
|
const ctx = thread.context || {}
|
|
285
299
|
const category = ctx.category || ''
|
|
286
300
|
const searchParam = category ? `&category=${category}` : ''
|
package/docs/api-reference.md
CHANGED
|
@@ -274,26 +274,30 @@ GET `/api/daemons/status` response:
|
|
|
274
274
|
|--------|------|
|
|
275
275
|
| `step_poller` | ワークフローステップの取得・実行(30秒間隔) |
|
|
276
276
|
| `revision_watcher` | リビジョン要求の検知(30秒間隔、PMのみ) |
|
|
277
|
-
| `thread_watcher` |
|
|
277
|
+
| `thread_watcher` | プロジェクト・ワークスペーススレッドの監視・LLM評価(15秒間隔) |
|
|
278
278
|
| `reflection_scheduler` | 1日1回の振り返り(cron) |
|
|
279
279
|
| `heartbeat` | HQへのハートビート(30秒間隔) |
|
|
280
280
|
|
|
281
|
-
###
|
|
281
|
+
### Threads (スレッド)
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
プロジェクト内またはワークスペースレベルのコミュニケーションチャネル。ブロッカー共有やチーム議論に使う。
|
|
284
284
|
リクエストは HQ にプロキシされる。
|
|
285
285
|
|
|
286
|
+
**2つのスコープ:**
|
|
287
|
+
- **Project** (`scope: "project"`): プロジェクトに紐づくスレッド。`project_id` 必須。プロジェクトメンバーが参加。
|
|
288
|
+
- **Workspace** (`scope: "workspace"`): プロジェクトに属さないスレッド。ルーティンのブロッカー、全体連絡等。オーナーの全ミニオンが参加。
|
|
289
|
+
|
|
286
290
|
| Method | Endpoint | Description |
|
|
287
291
|
|--------|----------|-------------|
|
|
288
|
-
| GET | `/api/threads` |
|
|
292
|
+
| GET | `/api/threads` | オープンスレッド一覧(プロジェクト+ワークスペース) |
|
|
289
293
|
| POST | `/api/threads` | スレッドを起票(初回メッセージを同時作成) |
|
|
290
294
|
| GET | `/api/threads/:id` | スレッド詳細 + メッセージ一覧 |
|
|
291
295
|
| POST | `/api/threads/:id/messages` | スレッドにメッセージを投稿 |
|
|
292
296
|
| POST | `/api/threads/:id/resolve` | スレッドを解決済みにする |
|
|
293
297
|
| POST | `/api/threads/:id/cancel` | スレッドをキャンセル |
|
|
294
|
-
| DELETE | `/api/threads/:id` | スレッドを完全削除(PM
|
|
298
|
+
| DELETE | `/api/threads/:id` | スレッドを完全削除(project: PMのみ、workspace: オーナーの全ミニオン) |
|
|
295
299
|
|
|
296
|
-
POST `/api/threads` body (
|
|
300
|
+
POST `/api/threads` body (プロジェクトスレッド — ヘルプ):
|
|
297
301
|
```json
|
|
298
302
|
{
|
|
299
303
|
"project_id": "uuid",
|
|
@@ -308,7 +312,23 @@ POST `/api/threads` body (ヘルプスレッド起票):
|
|
|
308
312
|
}
|
|
309
313
|
```
|
|
310
314
|
|
|
311
|
-
POST `/api/threads` body (
|
|
315
|
+
POST `/api/threads` body (ワークスペーススレッド — ルーティンのブロッカー):
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"scope": "workspace",
|
|
319
|
+
"category": "general",
|
|
320
|
+
"thread_type": "help",
|
|
321
|
+
"title": "朝作業ルーティン: APIキーの期限切れ",
|
|
322
|
+
"content": "外部サービスのAPIキーが期限切れでアクセスできない。更新が必要。",
|
|
323
|
+
"mentions": ["user"],
|
|
324
|
+
"context": {
|
|
325
|
+
"category": "auth",
|
|
326
|
+
"attempted_resolution": "既存のAPIキーで再試行済み、401エラー"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
POST `/api/threads` body (プロジェクトスレッド — ディスカッション):
|
|
312
332
|
```json
|
|
313
333
|
{
|
|
314
334
|
"project_id": "uuid",
|
|
@@ -321,13 +341,20 @@ POST `/api/threads` body (ディスカッションスレッド起票):
|
|
|
321
341
|
|
|
322
342
|
| Field | Type | Required | Description |
|
|
323
343
|
|-------|------|----------|-------------|
|
|
324
|
-
| `
|
|
344
|
+
| `scope` | string | No | `project`(デフォルト)or `workspace` |
|
|
345
|
+
| `project_id` | string | scope=project時 Yes | プロジェクト UUID |
|
|
346
|
+
| `category` | string | No | ワークスペーススレッドのカテゴリ: `general`(デフォルト), `ops`, `standup` |
|
|
325
347
|
| `thread_type` | string | No | `help`(デフォルト)or `discussion` |
|
|
326
348
|
| `title` | string | Yes | スレッドの要約 |
|
|
327
349
|
| `content` | string | Yes | スレッド本文(thread_messagesの最初のメッセージとして保存) |
|
|
328
350
|
| `mentions` | string[] | No | メンション対象。形式: `role:engineer`, `role:pm`, `minion:<minion_id>`, `user` |
|
|
329
351
|
| `context` | object | No | 任意のメタデータ(category, urgency, workflow_execution_id等) |
|
|
330
352
|
|
|
353
|
+
**scope の使い分け:**
|
|
354
|
+
- ワークフロー実行中(プロジェクトあり)→ `scope: "project"` + `project_id`
|
|
355
|
+
- ルーティン実行中(プロジェクトなし)→ `scope: "workspace"`
|
|
356
|
+
- プロジェクト外の一般的な質問・報告 → `scope: "workspace"` + `category`
|
|
357
|
+
|
|
331
358
|
**thread_type の違い:**
|
|
332
359
|
- `help`: ブロッカー解決。`resolve` で解決
|
|
333
360
|
- `discussion`: チーム内ディスカッション。`close` で完了
|
|
@@ -411,10 +438,10 @@ POST `/api/project-memories` body:
|
|
|
411
438
|
| `source_thread_id` | string | No | 知見の出典ヘルプスレッド UUID |
|
|
412
439
|
|
|
413
440
|
**推奨ワークフロー:**
|
|
414
|
-
1. ブロッカー発生 → `GET /api/project-memories?project_id=...&category=auth&search=2fa` で既知の知見を検索
|
|
441
|
+
1. ブロッカー発生 → プロジェクトコンテキストの場合は `GET /api/project-memories?project_id=...&category=auth&search=2fa` で既知の知見を検索
|
|
415
442
|
2. 該当あり → 知見に基づいて自己解決 or 即エスカレーション
|
|
416
|
-
3. 該当なし → `POST /api/threads`
|
|
417
|
-
4. 解決後 → `POST /api/project-memories` で知見を蓄積
|
|
443
|
+
3. 該当なし → `POST /api/threads` でブロッカー起票(プロジェクトなら `scope: "project"` + `project_id`、ルーティンなら `scope: "workspace"`)
|
|
444
|
+
4. 解決後 → プロジェクトスレッドの場合は `POST /api/project-memories` で知見を蓄積
|
|
418
445
|
|
|
419
446
|
### Commands
|
|
420
447
|
|
|
@@ -774,20 +801,20 @@ Response:
|
|
|
774
801
|
|
|
775
802
|
スキルはバージョン管理される。push ごとに新バージョンが作成され、ファイルは Supabase Storage に保存される。
|
|
776
803
|
|
|
777
|
-
###
|
|
804
|
+
### Threads (HQ)
|
|
778
805
|
|
|
779
806
|
| Method | Endpoint | Description |
|
|
780
807
|
|--------|----------|-------------|
|
|
781
|
-
| POST | `/api/minion/threads` |
|
|
782
|
-
| GET | `/api/minion/threads/open` |
|
|
808
|
+
| POST | `/api/minion/threads` | スレッドを起票(scope, category対応。初回メッセージを同時作成) |
|
|
809
|
+
| GET | `/api/minion/threads/open` | 未解決スレッド一覧(`?scope=all\|project\|workspace`) |
|
|
783
810
|
| GET | `/api/minion/threads/:id` | スレッド詳細 + メッセージ一覧 |
|
|
784
811
|
| POST | `/api/minion/threads/:id/messages` | スレッドにメッセージを投稿 |
|
|
785
812
|
| PATCH | `/api/minion/threads/:id/resolve` | スレッドを解決済みにする。Body: `{resolution}` |
|
|
786
813
|
| PATCH | `/api/minion/threads/:id/cancel` | スレッドをキャンセル。Body: `{reason?}` |
|
|
787
|
-
| DELETE | `/api/minion/threads/:id` | スレッドを完全削除(PM
|
|
814
|
+
| DELETE | `/api/minion/threads/:id` | スレッドを完全削除(project: PMのみ、workspace: オーナーの全ミニオン)。メッセージもCASCADE削除 |
|
|
788
815
|
|
|
789
816
|
ローカルエージェントの `/api/threads` は上記 HQ API へのプロキシ。
|
|
790
|
-
詳細なリクエスト/レスポンス仕様はローカル API セクションの「
|
|
817
|
+
詳細なリクエスト/レスポンス仕様はローカル API セクションの「Threads」を参照。
|
|
791
818
|
|
|
792
819
|
### Project Memories (HQ)
|
|
793
820
|
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -174,12 +174,15 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
174
174
|
### 対処フロー
|
|
175
175
|
|
|
176
176
|
```
|
|
177
|
-
1.
|
|
177
|
+
1. プロジェクトコンテキストの場合:
|
|
178
|
+
プロジェクトメモリーを検索(過去に同じ問題が解決済みか確認)
|
|
178
179
|
GET /api/project-memories?project_id=...&category=...&search=キーワード
|
|
179
180
|
|
|
180
181
|
2-a. 該当する知見あり → 知見に基づいて自己解決を試みる
|
|
181
182
|
|
|
182
183
|
2-b. 該当なし or 自己解決不可 → ヘルプスレッドを起票
|
|
184
|
+
|
|
185
|
+
■ ワークフロー実行中(プロジェクトあり)→ プロジェクトスレッド:
|
|
183
186
|
POST /api/threads
|
|
184
187
|
{
|
|
185
188
|
"project_id": "...",
|
|
@@ -193,9 +196,24 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
198
|
|
|
199
|
+
■ ルーティン実行中(プロジェクトなし)→ ワークスペーススレッド:
|
|
200
|
+
POST /api/threads
|
|
201
|
+
{
|
|
202
|
+
"scope": "workspace",
|
|
203
|
+
"category": "general",
|
|
204
|
+
"thread_type": "help",
|
|
205
|
+
"title": "問題の要約(1行)",
|
|
206
|
+
"content": "状況の詳細説明",
|
|
207
|
+
"mentions": ["user"],
|
|
208
|
+
"context": {
|
|
209
|
+
"category": "auth|environment|external-service|information|approval",
|
|
210
|
+
"attempted_resolution": "試行した内容"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
3. スレッドの返信を待つ(thread_watcher が自動監視)
|
|
197
215
|
|
|
198
|
-
4. 解決後 →
|
|
216
|
+
4. 解決後 → プロジェクトスレッドの場合はプロジェクトメモリーに知見を保存
|
|
199
217
|
POST /api/project-memories
|
|
200
218
|
{
|
|
201
219
|
"project_id": "...",
|
|
@@ -206,6 +224,14 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
206
224
|
}
|
|
207
225
|
```
|
|
208
226
|
|
|
227
|
+
### スレッドスコープの使い分け
|
|
228
|
+
|
|
229
|
+
| 状況 | スコープ | 説明 |
|
|
230
|
+
|------|---------|------|
|
|
231
|
+
| ワークフロー実行中のブロッカー | `scope: "project"` (デフォルト) | `project_id` 必須。プロジェクトメンバーが参加 |
|
|
232
|
+
| ルーティン実行中のブロッカー | `scope: "workspace"` | `project_id` 不要。オーナーの全ミニオンが参加 |
|
|
233
|
+
| プロジェクト外の一般的な質問・報告 | `scope: "workspace"` | カテゴリ: `general`, `ops`, `standup` |
|
|
234
|
+
|
|
209
235
|
### メンションの使い分け
|
|
210
236
|
|
|
211
237
|
| 状況 | メンション |
|
package/win/minion-cli.ps1
CHANGED
|
@@ -35,8 +35,6 @@ while ($i -lt $args.Count) {
|
|
|
35
35
|
|
|
36
36
|
$ErrorActionPreference = 'Stop'
|
|
37
37
|
|
|
38
|
-
# Load System.Web for password generation (used in setup)
|
|
39
|
-
Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue
|
|
40
38
|
|
|
41
39
|
# ============================================================
|
|
42
40
|
# Require Administrator for service management commands
|
|
@@ -280,7 +278,7 @@ function Invoke-HealthCheck {
|
|
|
280
278
|
function Assert-NssmAvailable {
|
|
281
279
|
if (-not $NssmPath -or -not (Test-Path $NssmPath)) {
|
|
282
280
|
Write-Error "NSSM not found. Expected at: $vendorNssm"
|
|
283
|
-
Write-Host " Reinstall the package: npm install -g @geekbeer/minion" -ForegroundColor Yellow
|
|
281
|
+
Write-Host " Reinstall the package (admin PowerShell): npm install -g @geekbeer/minion" -ForegroundColor Yellow
|
|
284
282
|
exit 1
|
|
285
283
|
}
|
|
286
284
|
}
|
|
@@ -392,7 +390,7 @@ function Restart-MinionService {
|
|
|
392
390
|
# ============================================================
|
|
393
391
|
|
|
394
392
|
function Invoke-Setup {
|
|
395
|
-
$totalSteps =
|
|
393
|
+
$totalSteps = 11
|
|
396
394
|
|
|
397
395
|
# Minionization warning
|
|
398
396
|
Write-Host ""
|
|
@@ -403,7 +401,6 @@ function Invoke-Setup {
|
|
|
403
401
|
|
|
404
402
|
Write-Host " This setup will:" -ForegroundColor Yellow
|
|
405
403
|
Write-Host " - Install and configure software (Node.js, Claude Code, VNC)"
|
|
406
|
-
Write-Host " - Create dedicated 'minion' service account"
|
|
407
404
|
Write-Host " - Register Windows Services via NSSM"
|
|
408
405
|
Write-Host " - Configure firewall rules"
|
|
409
406
|
Write-Host ""
|
|
@@ -431,6 +428,14 @@ function Invoke-Setup {
|
|
|
431
428
|
|
|
432
429
|
# Save setup user's SID for SDDL grants (so non-admin can control services later)
|
|
433
430
|
$setupUserSid = ([System.Security.Principal.WindowsIdentity]::GetCurrent().User).Value
|
|
431
|
+
# Also resolve target user's SID (may differ from setup user when run from admin account)
|
|
432
|
+
$targetUserName = Split-Path $TargetUserProfile -Leaf
|
|
433
|
+
try {
|
|
434
|
+
$targetUserSid = (New-Object System.Security.Principal.NTAccount($targetUserName)).Translate(
|
|
435
|
+
[System.Security.Principal.SecurityIdentifier]).Value
|
|
436
|
+
} catch {
|
|
437
|
+
$targetUserSid = $null
|
|
438
|
+
}
|
|
434
439
|
New-Item -Path $DataDir -ItemType Directory -Force | Out-Null
|
|
435
440
|
[System.IO.File]::WriteAllText((Join-Path $DataDir '.setup-user-sid'), $setupUserSid)
|
|
436
441
|
# Save target user profile so configure/uninstall can find it
|
|
@@ -571,74 +576,8 @@ function Invoke-Setup {
|
|
|
571
576
|
Write-Host " Please run 'claude' in a terminal to complete the authentication process." -ForegroundColor Yellow
|
|
572
577
|
Write-Host ""
|
|
573
578
|
|
|
574
|
-
# Step 4: Create
|
|
575
|
-
Write-Step 4 $totalSteps "Creating
|
|
576
|
-
$MinionSvcUser = 'minion'
|
|
577
|
-
$MinionSvcUserFull = ".\$MinionSvcUser"
|
|
578
|
-
$minionUserExists = [bool](Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue)
|
|
579
|
-
if ($minionUserExists) {
|
|
580
|
-
Write-Detail "Service account '$MinionSvcUser' already exists"
|
|
581
|
-
} else {
|
|
582
|
-
# Generate a random password (service account — not used interactively)
|
|
583
|
-
$svcPassword = [System.Web.Security.Membership]::GeneratePassword(24, 4)
|
|
584
|
-
$securePassword = ConvertTo-SecureString $svcPassword -AsPlainText -Force
|
|
585
|
-
New-LocalUser -Name $MinionSvcUser -Password $securePassword -Description 'Minion Agent Service Account' -PasswordNeverExpires -UserMayNotChangePassword -AccountNeverExpires | Out-Null
|
|
586
|
-
# Deny interactive/remote logon (service-only account)
|
|
587
|
-
& net localgroup "Users" $MinionSvcUser /delete 2>$null
|
|
588
|
-
Write-Detail "Service account '$MinionSvcUser' created (non-interactive)"
|
|
589
|
-
}
|
|
590
|
-
# Store password for NSSM ObjectName configuration
|
|
591
|
-
if (-not $minionUserExists) {
|
|
592
|
-
# Save password to a protected file for NSSM service registration
|
|
593
|
-
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
594
|
-
New-Item -Path (Split-Path $svcPasswordFile) -ItemType Directory -Force | Out-Null
|
|
595
|
-
[System.IO.File]::WriteAllText($svcPasswordFile, $svcPassword)
|
|
596
|
-
# Restrict file access to current user only
|
|
597
|
-
$acl = Get-Acl $svcPasswordFile
|
|
598
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
599
|
-
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
600
|
-
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name, 'FullControl', 'Allow')
|
|
601
|
-
$acl.AddAccessRule($adminRule)
|
|
602
|
-
Set-Acl $svcPasswordFile $acl
|
|
603
|
-
Write-Detail "Service account credentials stored"
|
|
604
|
-
} else {
|
|
605
|
-
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
606
|
-
if (Test-Path $svcPasswordFile) {
|
|
607
|
-
$svcPassword = [System.IO.File]::ReadAllText($svcPasswordFile).Trim()
|
|
608
|
-
} else {
|
|
609
|
-
# Re-generate password for existing account (reset)
|
|
610
|
-
$svcPassword = [System.Web.Security.Membership]::GeneratePassword(24, 4)
|
|
611
|
-
$securePassword = ConvertTo-SecureString $svcPassword -AsPlainText -Force
|
|
612
|
-
Set-LocalUser -Name $MinionSvcUser -Password $securePassword
|
|
613
|
-
New-Item -Path (Split-Path $svcPasswordFile) -ItemType Directory -Force | Out-Null
|
|
614
|
-
[System.IO.File]::WriteAllText($svcPasswordFile, $svcPassword)
|
|
615
|
-
$acl = Get-Acl $svcPasswordFile
|
|
616
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
617
|
-
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
618
|
-
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name, 'FullControl', 'Allow')
|
|
619
|
-
$acl.AddAccessRule($adminRule)
|
|
620
|
-
Set-Acl $svcPasswordFile $acl
|
|
621
|
-
Write-Detail "Service account password reset and stored"
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
# Grant 'Log on as a service' right to the minion user
|
|
625
|
-
$tempCfg = Join-Path $env:TEMP 'minion-secedit.cfg'
|
|
626
|
-
$tempDb = Join-Path $env:TEMP 'minion-secedit.sdb'
|
|
627
|
-
& secedit /export /cfg $tempCfg /areas USER_RIGHTS 2>$null
|
|
628
|
-
$cfgContent = Get-Content $tempCfg -Raw
|
|
629
|
-
if ($cfgContent -match 'SeServiceLogonRight\s*=\s*(.*)') {
|
|
630
|
-
$existing = $Matches[1]
|
|
631
|
-
if ($existing -notmatch $MinionSvcUser) {
|
|
632
|
-
$cfgContent = $cfgContent -replace "(SeServiceLogonRight\s*=\s*)(.*)", "`$1`$2,$MinionSvcUser"
|
|
633
|
-
[System.IO.File]::WriteAllText($tempCfg, $cfgContent)
|
|
634
|
-
& secedit /configure /db $tempDb /cfg $tempCfg /areas USER_RIGHTS 2>$null
|
|
635
|
-
Write-Detail "Granted 'Log on as a service' right to '$MinionSvcUser'"
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
Remove-Item $tempCfg, $tempDb -Force -ErrorAction SilentlyContinue
|
|
639
|
-
|
|
640
|
-
# Step 5: Create config directory and default .env
|
|
641
|
-
Write-Step 5 $totalSteps "Creating config directory..."
|
|
579
|
+
# Step 4: Create config directory and default .env
|
|
580
|
+
Write-Step 4 $totalSteps "Creating config directory..."
|
|
642
581
|
New-Item -Path $DataDir -ItemType Directory -Force | Out-Null
|
|
643
582
|
New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
|
|
644
583
|
if (-not (Test-Path $EnvFile)) {
|
|
@@ -653,16 +592,8 @@ function Invoke-Setup {
|
|
|
653
592
|
Write-Detail "$EnvFile already exists, preserving"
|
|
654
593
|
}
|
|
655
594
|
|
|
656
|
-
#
|
|
657
|
-
$
|
|
658
|
-
$minionRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
659
|
-
$MinionSvcUser, 'Modify', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
|
660
|
-
$minionAcl.AddAccessRule($minionRule)
|
|
661
|
-
Set-Acl $DataDir $minionAcl
|
|
662
|
-
Write-Detail "Granted '$MinionSvcUser' access to $DataDir"
|
|
663
|
-
|
|
664
|
-
# Step 6: Install node-pty (required for Windows terminal management)
|
|
665
|
-
Write-Step 6 $totalSteps "Installing terminal support (node-pty)..."
|
|
595
|
+
# Step 5: Install node-pty (required for Windows terminal management)
|
|
596
|
+
Write-Step 5 $totalSteps "Installing terminal support (node-pty)..."
|
|
666
597
|
$minionPkgDir = $CliDir
|
|
667
598
|
if (Test-Path $minionPkgDir) {
|
|
668
599
|
Push-Location $minionPkgDir
|
|
@@ -696,22 +627,22 @@ function Invoke-Setup {
|
|
|
696
627
|
}
|
|
697
628
|
else {
|
|
698
629
|
Write-Warn "Minion package not found at $minionPkgDir"
|
|
699
|
-
Write-Host " Please run: npm install -g @geekbeer/minion"
|
|
630
|
+
Write-Host " Please run (admin PowerShell): npm install -g @geekbeer/minion"
|
|
700
631
|
}
|
|
701
632
|
|
|
702
633
|
# Step 7: Verify NSSM
|
|
703
|
-
Write-Step
|
|
634
|
+
Write-Step 6 $totalSteps "Verifying NSSM..."
|
|
704
635
|
Assert-NssmAvailable
|
|
705
636
|
$nssmVersion = Invoke-Nssm version
|
|
706
637
|
Write-Detail "NSSM available: $NssmPath ($nssmVersion)"
|
|
707
638
|
|
|
708
639
|
# Step 8: Register Windows Services via NSSM
|
|
709
|
-
Write-Step
|
|
640
|
+
Write-Step 7 $totalSteps "Registering Windows Services..."
|
|
710
641
|
|
|
711
642
|
$serverJs = Join-Path $minionPkgDir 'win\server.js'
|
|
712
643
|
if (-not (Test-Path $serverJs)) {
|
|
713
644
|
Write-Error "server.js not found at $serverJs"
|
|
714
|
-
Write-Host " Please run: npm install -g @geekbeer/minion"
|
|
645
|
+
Write-Host " Please run (admin PowerShell): npm install -g @geekbeer/minion"
|
|
715
646
|
exit 1
|
|
716
647
|
}
|
|
717
648
|
$nodePath = (Get-Command node).Source
|
|
@@ -743,13 +674,15 @@ function Invoke-Setup {
|
|
|
743
674
|
Invoke-Nssm set minion-agent Start SERVICE_AUTO_START
|
|
744
675
|
Invoke-Nssm set minion-agent DisplayName "Minion Agent"
|
|
745
676
|
Invoke-Nssm set minion-agent Description "GeekBeer Minion AI Agent Service"
|
|
746
|
-
#
|
|
747
|
-
Invoke-Nssm set minion-agent ObjectName $MinionSvcUserFull $svcPassword
|
|
677
|
+
# Runs as LocalSystem (NSSM default). USERPROFILE/HOME env vars point to target user's profile.
|
|
748
678
|
Grant-ServiceControlToUser 'minion-agent' $setupUserSid
|
|
749
|
-
|
|
679
|
+
if ($targetUserSid -and $targetUserSid -ne $setupUserSid) {
|
|
680
|
+
Grant-ServiceControlToUser 'minion-agent' $targetUserSid
|
|
681
|
+
}
|
|
682
|
+
Write-Detail "minion-agent service registered (runs as LocalSystem)"
|
|
750
683
|
|
|
751
|
-
# Step 9: Install and configure TightVNC (runs as
|
|
752
|
-
Write-Step
|
|
684
|
+
# Step 9: Install and configure TightVNC (runs as logon task in user session for desktop capture)
|
|
685
|
+
Write-Step 8 $totalSteps "Setting up TightVNC Server..."
|
|
753
686
|
$vncSystemPath = 'C:\Program Files\TightVNC\tvnserver.exe'
|
|
754
687
|
$vncPortableDir = Join-Path $DataDir 'tightvnc'
|
|
755
688
|
$vncPortablePath = Join-Path $vncPortableDir 'PFiles\TightVNC\tvnserver.exe'
|
|
@@ -798,31 +731,32 @@ function Invoke-Setup {
|
|
|
798
731
|
}
|
|
799
732
|
|
|
800
733
|
# Configure TightVNC registry (localhost-only, no VNC auth)
|
|
801
|
-
|
|
734
|
+
# Write to both HKCU (for user-session -run mode) and HKLM (fallback)
|
|
802
735
|
if ($vncExePath) {
|
|
803
|
-
|
|
804
|
-
|
|
736
|
+
foreach ($vncRegPath in @('HKCU:\Software\TightVNC\Server', 'HKLM:\Software\TightVNC\Server')) {
|
|
737
|
+
if (-not (Test-Path $vncRegPath)) {
|
|
738
|
+
New-Item -Path $vncRegPath -Force | Out-Null
|
|
739
|
+
}
|
|
740
|
+
Set-ItemProperty -Path $vncRegPath -Name 'LoopbackOnly' -Value 1 -Type DWord
|
|
741
|
+
Set-ItemProperty -Path $vncRegPath -Name 'AllowLoopback' -Value 1 -Type DWord
|
|
742
|
+
Set-ItemProperty -Path $vncRegPath -Name 'UseVncAuthentication' -Value 0 -Type DWord
|
|
743
|
+
Set-ItemProperty -Path $vncRegPath -Name 'UseControlAuthentication' -Value 0 -Type DWord
|
|
744
|
+
Set-ItemProperty -Path $vncRegPath -Name 'RfbPort' -Value 5900 -Type DWord
|
|
805
745
|
}
|
|
806
|
-
|
|
807
|
-
Set-ItemProperty -Path $vncRegPath -Name 'AllowLoopback' -Value 1 -Type DWord
|
|
808
|
-
Set-ItemProperty -Path $vncRegPath -Name 'UseVncAuthentication' -Value 0 -Type DWord
|
|
809
|
-
Set-ItemProperty -Path $vncRegPath -Name 'UseControlAuthentication' -Value 0 -Type DWord
|
|
810
|
-
Set-ItemProperty -Path $vncRegPath -Name 'RfbPort' -Value 5900 -Type DWord
|
|
746
|
+
Write-Detail "TightVNC registry configured (HKCU + HKLM)"
|
|
811
747
|
|
|
812
|
-
#
|
|
748
|
+
# Remove legacy NSSM service if present (VNC now runs as logon task)
|
|
813
749
|
Invoke-Nssm stop minion-vnc
|
|
814
750
|
Invoke-Nssm remove minion-vnc confirm
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
Grant-ServiceControlToUser 'minion-vnc' $setupUserSid
|
|
821
|
-
Write-Detail "minion-vnc service registered"
|
|
751
|
+
|
|
752
|
+
# Register VNC as logon task (must run in user session for desktop capture)
|
|
753
|
+
schtasks /Delete /TN "MinionVNC" /F 2>$null
|
|
754
|
+
schtasks /Create /TN "MinionVNC" /TR "'$vncExePath' -run" /SC ONLOGON /RL HIGHEST /F | Out-Null
|
|
755
|
+
Write-Detail "TightVNC registered as logon task (user session, not service)"
|
|
822
756
|
}
|
|
823
757
|
|
|
824
758
|
# Step 10: Setup websockify (runs as LocalSystem, paired with VNC)
|
|
825
|
-
Write-Step
|
|
759
|
+
Write-Step 9 $totalSteps "Setting up websockify..."
|
|
826
760
|
[array]$wsCmd = Get-WebsockifyCommand
|
|
827
761
|
if (-not $wsCmd) {
|
|
828
762
|
# Ensure Python is installed
|
|
@@ -878,7 +812,7 @@ function Invoke-Setup {
|
|
|
878
812
|
}
|
|
879
813
|
|
|
880
814
|
if ($wsCmd -and $vncExePath) {
|
|
881
|
-
# Register websockify as NSSM service
|
|
815
|
+
# Register websockify as NSSM service (no dependency on minion-vnc — VNC runs as logon task)
|
|
882
816
|
Invoke-Nssm stop minion-websockify
|
|
883
817
|
Invoke-Nssm remove minion-websockify confirm
|
|
884
818
|
if ($wsCmd.Count -eq 1) {
|
|
@@ -888,19 +822,21 @@ function Invoke-Setup {
|
|
|
888
822
|
$wsArgs = ($wsCmd[1..($wsCmd.Count-1)] + @('6080', 'localhost:5900')) -join ' '
|
|
889
823
|
Invoke-Nssm install minion-websockify $wsCmd[0] $wsArgs
|
|
890
824
|
}
|
|
891
|
-
Invoke-Nssm set minion-websockify DependOnService minion-vnc
|
|
892
825
|
Invoke-Nssm set minion-websockify Start SERVICE_AUTO_START
|
|
893
826
|
Invoke-Nssm set minion-websockify DisplayName "Minion Websockify"
|
|
894
827
|
Invoke-Nssm set minion-websockify Description "WebSocket proxy for VNC (6080 -> 5900)"
|
|
895
828
|
Invoke-Nssm set minion-websockify AppRestartDelay 3000
|
|
896
829
|
Grant-ServiceControlToUser 'minion-websockify' $setupUserSid
|
|
897
|
-
|
|
830
|
+
if ($targetUserSid -and $targetUserSid -ne $setupUserSid) {
|
|
831
|
+
Grant-ServiceControlToUser 'minion-websockify' $targetUserSid
|
|
832
|
+
}
|
|
833
|
+
Write-Detail "minion-websockify service registered"
|
|
898
834
|
} else {
|
|
899
835
|
Write-Warn "websockify not available, VNC WebSocket proxy will not be registered"
|
|
900
836
|
}
|
|
901
837
|
|
|
902
838
|
# Step 11: Disable screensaver, lock screen, and sleep
|
|
903
|
-
Write-Step
|
|
839
|
+
Write-Step 10 $totalSteps "Disabling screensaver, lock screen, and sleep..."
|
|
904
840
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveActive -Value '0'
|
|
905
841
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value '0'
|
|
906
842
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name SCRNSAVE.EXE -Value ''
|
|
@@ -914,7 +850,7 @@ function Invoke-Setup {
|
|
|
914
850
|
Write-Detail "Sleep and monitor timeout disabled"
|
|
915
851
|
|
|
916
852
|
# Configure firewall rules
|
|
917
|
-
Write-Step
|
|
853
|
+
Write-Step 11 $totalSteps "Configuring firewall rules..."
|
|
918
854
|
$fwRules = @(
|
|
919
855
|
@{ Name = 'Minion Agent'; Port = 8080 },
|
|
920
856
|
@{ Name = 'Minion Terminal'; Port = 7681 },
|
|
@@ -930,25 +866,73 @@ function Invoke-Setup {
|
|
|
930
866
|
}
|
|
931
867
|
}
|
|
932
868
|
|
|
933
|
-
# Grant
|
|
934
|
-
$
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
869
|
+
# Grant target user access to admin's minion package and bin links (so target user can run minion-cli-win)
|
|
870
|
+
$adminNpmBin = Split-Path (Get-Command minion-cli-win -ErrorAction SilentlyContinue).Source -ErrorAction SilentlyContinue
|
|
871
|
+
if (-not $adminNpmBin) {
|
|
872
|
+
$adminNpmBin = & npm config get prefix 2>$null
|
|
873
|
+
}
|
|
874
|
+
if ($adminNpmBin -and ($adminNpmBin -ne (Join-Path $TargetUserProfile 'AppData\Roaming\npm'))) {
|
|
875
|
+
$targetUserName = Split-Path $TargetUserProfile -Leaf
|
|
876
|
+
|
|
877
|
+
# Grant ReadAndExecute on bin links (minion-cli-win.cmd, hq-win.cmd, etc.)
|
|
878
|
+
$binFiles = Get-ChildItem -Path $adminNpmBin -Filter '*minion*' -ErrorAction SilentlyContinue
|
|
879
|
+
$binFiles += Get-ChildItem -Path $adminNpmBin -Filter '*hq-win*' -ErrorAction SilentlyContinue
|
|
880
|
+
foreach ($f in $binFiles) {
|
|
881
|
+
icacls $f.FullName /grant "${targetUserName}:(RX)" /Q 2>$null | Out-Null
|
|
882
|
+
}
|
|
883
|
+
# Grant ReadAndExecute on the @geekbeer/minion package directory (recursive)
|
|
884
|
+
$minionPkgDir = Join-Path (Join-Path $adminNpmBin 'node_modules') '@geekbeer\minion'
|
|
885
|
+
if (Test-Path $minionPkgDir) {
|
|
886
|
+
icacls $minionPkgDir /grant "${targetUserName}:(OI)(CI)RX" /T /Q 2>$null | Out-Null
|
|
887
|
+
Write-Detail "Granted target user read access to $minionPkgDir"
|
|
888
|
+
}
|
|
889
|
+
# Grant traverse access on ancestor directories so the path is reachable
|
|
890
|
+
# e.g., C:\Users\yunoda -> AppData -> Roaming -> npm -> node_modules -> @geekbeer
|
|
891
|
+
$traverseDirs = @(
|
|
892
|
+
$adminNpmBin,
|
|
893
|
+
(Join-Path $adminNpmBin 'node_modules'),
|
|
894
|
+
(Join-Path $adminNpmBin 'node_modules\@geekbeer')
|
|
895
|
+
)
|
|
896
|
+
# Walk up from npm bin dir to drive root to grant traverse (list+read) on each
|
|
897
|
+
$walkDir = $adminNpmBin
|
|
898
|
+
while ($walkDir) {
|
|
899
|
+
$parent = Split-Path $walkDir -Parent
|
|
900
|
+
if (-not $parent -or $parent -eq $walkDir) { break }
|
|
901
|
+
$traverseDirs += $parent
|
|
902
|
+
$walkDir = $parent
|
|
903
|
+
# Stop at drive root
|
|
904
|
+
if ($parent.Length -le 3) { break }
|
|
905
|
+
}
|
|
906
|
+
foreach ($dir in ($traverseDirs | Select-Object -Unique)) {
|
|
907
|
+
if (Test-Path $dir) {
|
|
908
|
+
# Grant only traverse + list (no recursive, no inherit)
|
|
909
|
+
icacls $dir /grant "${targetUserName}:(RX)" /Q 2>$null | Out-Null
|
|
910
|
+
}
|
|
911
|
+
}
|
|
942
912
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
913
|
+
# Add admin's npm bin to target user's PATH
|
|
914
|
+
$targetUserSid = $null
|
|
915
|
+
try {
|
|
916
|
+
$targetUserSid = (New-Object System.Security.Principal.NTAccount($targetUserName)).Translate(
|
|
917
|
+
[System.Security.Principal.SecurityIdentifier]).Value
|
|
918
|
+
} catch {}
|
|
919
|
+
if ($targetUserSid) {
|
|
920
|
+
$regPath = "Registry::HKEY_USERS\$targetUserSid\Environment"
|
|
921
|
+
if (Test-Path $regPath) {
|
|
922
|
+
$currentPath = (Get-ItemProperty -Path $regPath -Name PATH -ErrorAction SilentlyContinue).PATH
|
|
923
|
+
if ($currentPath -and $currentPath -notlike "*$adminNpmBin*") {
|
|
924
|
+
Set-ItemProperty -Path $regPath -Name PATH -Value "$currentPath;$adminNpmBin"
|
|
925
|
+
Write-Detail "Added $adminNpmBin to target user's PATH"
|
|
926
|
+
} elseif (-not $currentPath) {
|
|
927
|
+
Set-ItemProperty -Path $regPath -Name PATH -Value $adminNpmBin
|
|
928
|
+
Write-Detail "Set target user's PATH to $adminNpmBin"
|
|
929
|
+
} else {
|
|
930
|
+
Write-Detail "Target user's PATH already contains $adminNpmBin"
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
Write-Warn "Target user's registry not loaded. User must log in and re-run setup, or manually add $adminNpmBin to PATH."
|
|
934
|
+
}
|
|
935
|
+
}
|
|
952
936
|
}
|
|
953
937
|
|
|
954
938
|
Write-Host ""
|
|
@@ -958,8 +942,8 @@ function Invoke-Setup {
|
|
|
958
942
|
Write-Host ""
|
|
959
943
|
Write-Host "Services registered (not yet started):"
|
|
960
944
|
Write-Host " minion-agent - AI Agent (port 8080)"
|
|
961
|
-
Write-Host " minion-vnc - TightVNC Server (port 5900)"
|
|
962
945
|
Write-Host " minion-websockify - WebSocket proxy (port 6080)"
|
|
946
|
+
Write-Host " MinionVNC (task) - TightVNC (port 5900, starts at logon)"
|
|
963
947
|
Write-Host ""
|
|
964
948
|
Write-Host "Next step: Connect to HQ (run as regular user):" -ForegroundColor Yellow
|
|
965
949
|
Write-Host " minion-cli-win configure ``"
|
|
@@ -1001,12 +985,12 @@ function Invoke-Uninstall {
|
|
|
1001
985
|
}
|
|
1002
986
|
Write-Host ""
|
|
1003
987
|
|
|
1004
|
-
$totalSteps =
|
|
988
|
+
$totalSteps = 6
|
|
1005
989
|
|
|
1006
990
|
# Step 1: Stop and remove all NSSM services
|
|
1007
991
|
Write-Step 1 $totalSteps "Stopping and removing services..."
|
|
1008
992
|
if ($NssmPath -and (Test-Path $NssmPath)) {
|
|
1009
|
-
foreach ($svc in @('minion-cloudflared', 'minion-websockify', 'minion-
|
|
993
|
+
foreach ($svc in @('minion-cloudflared', 'minion-websockify', 'minion-agent')) {
|
|
1010
994
|
$status = Invoke-Nssm status $svc
|
|
1011
995
|
if ($status) {
|
|
1012
996
|
Invoke-Nssm stop $svc
|
|
@@ -1016,6 +1000,12 @@ function Invoke-Uninstall {
|
|
|
1016
1000
|
}
|
|
1017
1001
|
}
|
|
1018
1002
|
|
|
1003
|
+
# Remove VNC logon task and legacy NSSM service
|
|
1004
|
+
schtasks /Delete /TN "MinionVNC" /F 2>$null
|
|
1005
|
+
Invoke-Nssm stop minion-vnc
|
|
1006
|
+
Invoke-Nssm remove minion-vnc confirm
|
|
1007
|
+
Write-Detail "VNC logon task and legacy service removed"
|
|
1008
|
+
|
|
1019
1009
|
# Also stop legacy processes
|
|
1020
1010
|
Stop-Process -Name tvnserver -Force -ErrorAction SilentlyContinue
|
|
1021
1011
|
Stop-Process -Name websockify -Force -ErrorAction SilentlyContinue
|
|
@@ -1024,7 +1014,7 @@ function Invoke-Uninstall {
|
|
|
1024
1014
|
|
|
1025
1015
|
# Step 2: Remove firewall rules
|
|
1026
1016
|
Write-Step 2 $totalSteps "Removing firewall rules..."
|
|
1027
|
-
foreach ($ruleName in @('Minion Agent', 'Minion VNC')) {
|
|
1017
|
+
foreach ($ruleName in @('Minion Agent', 'Minion Terminal', 'Minion VNC')) {
|
|
1028
1018
|
$existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
|
1029
1019
|
if ($existing) {
|
|
1030
1020
|
Remove-NetFirewallRule -DisplayName $ruleName
|
|
@@ -1095,24 +1085,15 @@ function Invoke-Uninstall {
|
|
|
1095
1085
|
Write-Detail "Removed rules: core.md"
|
|
1096
1086
|
}
|
|
1097
1087
|
|
|
1098
|
-
#
|
|
1099
|
-
Write-Step 6 $totalSteps "Removing service account..."
|
|
1100
|
-
$MinionSvcUser = 'minion'
|
|
1101
|
-
if (Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue) {
|
|
1102
|
-
Remove-LocalUser -Name $MinionSvcUser
|
|
1103
|
-
Write-Detail "Removed local user '$MinionSvcUser'"
|
|
1104
|
-
} else {
|
|
1105
|
-
Write-Detail "Service account '$MinionSvcUser' not found, skipping"
|
|
1106
|
-
}
|
|
1107
|
-
# Remove stored service password
|
|
1088
|
+
# Clean up legacy service account and password file (from v3.1.0-v3.4.x)
|
|
1108
1089
|
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
1109
1090
|
if (Test-Path $svcPasswordFile) {
|
|
1110
1091
|
Remove-Item $svcPasswordFile -Force
|
|
1111
|
-
Write-Detail "Removed service credentials file"
|
|
1092
|
+
Write-Detail "Removed legacy service credentials file"
|
|
1112
1093
|
}
|
|
1113
1094
|
|
|
1114
|
-
# Step
|
|
1115
|
-
Write-Step
|
|
1095
|
+
# Step 6: Remove Cloudflare Tunnel configuration
|
|
1096
|
+
Write-Step 6 $totalSteps "Removing Cloudflare Tunnel configuration..."
|
|
1116
1097
|
$cfConfigDir = Join-Path $TargetUserProfile '.cloudflared'
|
|
1117
1098
|
if (Test-Path $cfConfigDir) {
|
|
1118
1099
|
Remove-Item $cfConfigDir -Recurse -Force
|
|
@@ -1280,13 +1261,24 @@ function Invoke-Configure {
|
|
|
1280
1261
|
# Start services (uses sc.exe — works without admin via SDDL)
|
|
1281
1262
|
$startStep = if ($SetupTunnel) { $totalSteps - 2 } else { $totalSteps - 2 }
|
|
1282
1263
|
Write-Step ($totalSteps - 1) $totalSteps "Starting services..."
|
|
1283
|
-
# Start VNC
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1264
|
+
# Start VNC (logon task — runs in user session for desktop capture)
|
|
1265
|
+
$vncProcess = Get-Process -Name tvnserver -ErrorAction SilentlyContinue
|
|
1266
|
+
if (-not $vncProcess) {
|
|
1267
|
+
$vncSystemPath = 'C:\Program Files\TightVNC\tvnserver.exe'
|
|
1268
|
+
$vncPortablePath = Join-Path $DataDir 'tightvnc\PFiles\TightVNC\tvnserver.exe'
|
|
1269
|
+
$vncExe = if (Test-Path $vncSystemPath) { $vncSystemPath } elseif (Test-Path $vncPortablePath) { $vncPortablePath } else { $null }
|
|
1270
|
+
if ($vncExe) {
|
|
1271
|
+
Start-Process -FilePath $vncExe -ArgumentList '-run'
|
|
1272
|
+
Write-Detail "TightVNC started (user session)"
|
|
1289
1273
|
}
|
|
1274
|
+
} else {
|
|
1275
|
+
Write-Detail "TightVNC already running"
|
|
1276
|
+
}
|
|
1277
|
+
# Start websockify service
|
|
1278
|
+
$wsState = Get-ServiceState 'minion-websockify'
|
|
1279
|
+
if ($wsState -and $wsState -ne 'RUNNING') {
|
|
1280
|
+
sc.exe start minion-websockify 2>&1 | Out-Null
|
|
1281
|
+
Write-Detail "minion-websockify started"
|
|
1290
1282
|
}
|
|
1291
1283
|
Start-MinionService
|
|
1292
1284
|
|
|
@@ -1343,7 +1335,7 @@ function Show-Status {
|
|
|
1343
1335
|
}
|
|
1344
1336
|
|
|
1345
1337
|
function Show-Daemons {
|
|
1346
|
-
foreach ($svc in @('minion-agent', 'minion-
|
|
1338
|
+
foreach ($svc in @('minion-agent', 'minion-websockify', 'minion-cloudflared')) {
|
|
1347
1339
|
$state = Get-ServiceState $svc
|
|
1348
1340
|
if ($state) {
|
|
1349
1341
|
Write-Host "${svc}: $state"
|
|
@@ -1351,6 +1343,13 @@ function Show-Daemons {
|
|
|
1351
1343
|
Write-Host "${svc}: not installed"
|
|
1352
1344
|
}
|
|
1353
1345
|
}
|
|
1346
|
+
# VNC runs as logon task, not NSSM service
|
|
1347
|
+
$vncProc = Get-Process -Name tvnserver -ErrorAction SilentlyContinue
|
|
1348
|
+
if ($vncProc) {
|
|
1349
|
+
Write-Host "vnc (task): RUNNING (PID $($vncProc[0].Id))"
|
|
1350
|
+
} else {
|
|
1351
|
+
Write-Host "vnc (task): not running"
|
|
1352
|
+
}
|
|
1354
1353
|
}
|
|
1355
1354
|
|
|
1356
1355
|
function Show-Health {
|
|
@@ -1383,13 +1382,7 @@ function Show-Diagnose {
|
|
|
1383
1382
|
Write-Host ""
|
|
1384
1383
|
|
|
1385
1384
|
Write-Host "Service Account:" -ForegroundColor Yellow
|
|
1386
|
-
|
|
1387
|
-
$svcUser = Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue
|
|
1388
|
-
if ($svcUser) {
|
|
1389
|
-
Write-Host " User: $MinionSvcUser (Enabled: $($svcUser.Enabled))"
|
|
1390
|
-
} else {
|
|
1391
|
-
Write-Host " User: NOT FOUND (services run as LocalSystem)" -ForegroundColor Yellow
|
|
1392
|
-
}
|
|
1385
|
+
Write-Host " Runs as: LocalSystem"
|
|
1393
1386
|
Write-Host ""
|
|
1394
1387
|
|
|
1395
1388
|
Write-Host "NSSM:" -ForegroundColor Yellow
|