agor-live 0.16.4 → 0.17.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/dist/cli/commands/admin/sync-unix.d.ts +1 -48
- package/dist/cli/commands/admin/sync-unix.js +702 -390
- package/dist/cli/commands/daemon/start.d.ts +1 -0
- package/dist/cli/commands/daemon/start.js +42 -4
- package/dist/cli/commands/version.d.ts +19 -0
- package/dist/cli/commands/version.js +63 -0
- package/dist/cli/lib/check-migrations.d.ts +36 -0
- package/dist/cli/lib/check-migrations.js +24 -0
- package/dist/cli/lib/check-migrations.test.d.ts +2 -0
- package/dist/cli/lib/check-migrations.test.js +17295 -0
- package/dist/cli/lib/daemon-manager.test.js +6 -6
- package/dist/core/{agentic-tool-Cs4nK-CC.d.cts → agentic-tool-BulhIUGb.d.cts} +4 -2
- package/dist/core/{agentic-tool-B6RT-ZX5.d.ts → agentic-tool-cYqsU5i5.d.ts} +4 -2
- package/dist/core/api/index.cjs +9 -0
- package/dist/core/api/index.d.cts +13 -13
- package/dist/core/api/index.d.ts +13 -13
- package/dist/core/api/index.js +9 -0
- package/dist/core/{artifact-DaHQPZVX.d.cts → artifact-B2AqjUMb.d.cts} +23 -3
- package/dist/core/{artifact-CIQzxjNP.d.ts → artifact-bMxYKV3b.d.ts} +23 -3
- package/dist/core/{board-DG--dAS_.d.ts → board-D3YJ0_ih.d.ts} +10 -3
- package/dist/core/{board-DogjFoWy.d.cts → board-D7SnPqp-.d.cts} +10 -3
- package/dist/core/{board-comment-9ORrSlA1.d.ts → board-comment-3N-jSgsk.d.ts} +2 -2
- package/dist/core/{board-comment-WzJC3SuF.d.cts → board-comment-DIdOJrSF.d.cts} +2 -2
- package/dist/core/claude/index.cjs +191 -32
- package/dist/core/claude/index.d.cts +4 -4
- package/dist/core/claude/index.d.ts +4 -4
- package/dist/core/claude/index.js +213 -40
- package/dist/core/client/index.cjs +1397 -0
- package/dist/core/client/index.d.cts +24 -0
- package/dist/core/client/index.d.ts +24 -0
- package/dist/core/client/index.js +1279 -0
- package/dist/core/{client-XpghdMQL.d.cts → client-B5PyIvNc.d.cts} +883 -131
- package/dist/core/{client-NFCS0H8T.d.ts → client-C1CsRi19.d.ts} +883 -131
- package/dist/core/config/browser.cjs +249 -2
- package/dist/core/config/browser.d.cts +138 -9
- package/dist/core/config/browser.d.ts +138 -9
- package/dist/core/config/browser.js +229 -1
- package/dist/core/config/index.cjs +950 -118
- package/dist/core/config/index.d.cts +285 -67
- package/dist/core/config/index.d.ts +285 -67
- package/dist/core/config/index.js +951 -123
- package/dist/core/{config-manager-BbMvB3Lz.d.cts → config-manager-BqXXvlih.d.ts} +34 -2
- package/dist/core/{config-manager-etFWO6Wo.d.ts → config-manager-C8zzUrMs.d.cts} +34 -2
- package/dist/core/{config-services-C848cfbD.d.ts → config-services-4zjcxc8Z.d.ts} +5 -5
- package/dist/core/{config-services-CDhfaNpd.d.cts → config-services-CXe0OHq7.d.cts} +5 -5
- package/dist/core/{context-ByxGjp5l.d.cts → context-BpkCELpO.d.cts} +1 -1
- package/dist/core/{context-ByxGjp5l.d.ts → context-BpkCELpO.d.ts} +1 -1
- package/dist/core/db/index.cjs +993 -294
- package/dist/core/db/index.d.cts +1076 -166
- package/dist/core/db/index.d.ts +1076 -166
- package/dist/core/db/index.js +998 -295
- package/dist/core/db/session-guard.d.cts +9 -9
- package/dist/core/db/session-guard.d.ts +9 -9
- package/dist/core/drizzle/postgres/0024_add_serialized_sessions.sql +24 -0
- package/dist/core/drizzle/postgres/0025_add_session_env_selections.sql +15 -0
- package/dist/core/drizzle/postgres/0026_env_command_variants.sql +41 -0
- package/dist/core/drizzle/postgres/0027_mcp_oauth_token_refresh.sql +72 -0
- package/dist/core/drizzle/postgres/meta/_journal.json +28 -0
- package/dist/core/drizzle/sqlite/0035_add_serialized_sessions.sql +22 -0
- package/dist/core/drizzle/sqlite/0036_add_session_env_selections.sql +18 -0
- package/dist/core/drizzle/sqlite/0037_env_command_variants.sql +38 -0
- package/dist/core/drizzle/sqlite/0038_mcp_oauth_token_refresh.sql +109 -0
- package/dist/core/drizzle/sqlite/meta/_journal.json +28 -0
- package/dist/core/environment/render-snapshot.cjs +172 -0
- package/dist/core/environment/render-snapshot.d.cts +76 -0
- package/dist/core/environment/render-snapshot.d.ts +76 -0
- package/dist/core/environment/render-snapshot.js +135 -0
- package/dist/core/environment/variable-resolver.cjs +6 -2
- package/dist/core/environment/variable-resolver.d.cts +3 -3
- package/dist/core/environment/variable-resolver.d.ts +3 -3
- package/dist/core/environment/variable-resolver.js +6 -2
- package/dist/core/feathers/index.cjs +6 -0
- package/dist/core/feathers/index.d.cts +1 -1
- package/dist/core/feathers/index.d.ts +1 -1
- package/dist/core/feathers/index.js +11 -1
- package/dist/core/{feathers-C8PkF35p.d.ts → feathers-BK9CCjVG.d.ts} +4 -4
- package/dist/core/{feathers--R3ml98e.d.cts → feathers-DnypeNDM.d.cts} +4 -4
- package/dist/core/gateway/index.d.cts +6 -6
- package/dist/core/gateway/index.d.ts +6 -6
- package/dist/core/{gateway-BYCTTJVJ.d.ts → gateway-B_4X-v07.d.ts} +5 -5
- package/dist/core/{gateway-D5me_jjo.d.cts → gateway-BnOlAelY.d.cts} +5 -5
- package/dist/core/git/index.cjs +294 -123
- package/dist/core/git/index.d.cts +89 -10
- package/dist/core/git/index.d.ts +89 -10
- package/dist/core/git/index.js +291 -124
- package/dist/core/{id-2oR2NdLp.d.cts → id-CoIbY_uc.d.cts} +43 -8
- package/dist/core/{id-2oR2NdLp.d.ts → id-CoIbY_uc.d.ts} +43 -8
- package/dist/core/index.cjs +2778 -791
- package/dist/core/index.d.cts +27 -26
- package/dist/core/index.d.ts +27 -26
- package/dist/core/index.js +2705 -767
- package/dist/core/lib/feathers-validation.d.cts +1 -1
- package/dist/core/lib/feathers-validation.d.ts +1 -1
- package/dist/core/mcp/index.cjs +150 -13
- package/dist/core/mcp/index.d.cts +2 -2
- package/dist/core/mcp/index.d.ts +2 -2
- package/dist/core/mcp/index.js +152 -13
- package/dist/core/{mcp-DUrvGUDS.d.cts → mcp-DC7GLAg2.d.cts} +10 -2
- package/dist/core/{mcp-D7eTnVUO.d.ts → mcp-z5v19H-r.d.ts} +10 -2
- package/dist/core/{message-C4Bb-L6c.d.ts → message-CHUUP6OS.d.ts} +2 -2
- package/dist/core/{message-BbDSJvyl.d.cts → message-DinITA7a.d.cts} +2 -2
- package/dist/core/models/browser.cjs +351 -0
- package/dist/core/models/browser.d.cts +208 -0
- package/dist/core/models/browser.d.ts +208 -0
- package/dist/core/models/browser.js +313 -0
- package/dist/core/models/gemini-shared.cjs +126 -0
- package/dist/core/models/gemini-shared.d.cts +39 -0
- package/dist/core/models/gemini-shared.d.ts +39 -0
- package/dist/core/models/gemini-shared.js +98 -0
- package/dist/core/models/index.cjs +68 -4
- package/dist/core/models/index.d.cts +80 -239
- package/dist/core/models/index.d.ts +80 -239
- package/dist/core/models/index.js +65 -3
- package/dist/core/package.json +35 -0
- package/dist/core/permissions/index.d.cts +2 -2
- package/dist/core/permissions/index.d.ts +2 -2
- package/dist/core/{repo-DaP4omZL.d.ts → repo-BcPbf8Ck.d.ts} +178 -9
- package/dist/core/{repo-zg1xnWQQ.d.cts → repo-CVyROjO4.d.cts} +178 -9
- package/dist/core/seed/index.cjs +912 -327
- package/dist/core/seed/index.d.cts +1 -1
- package/dist/core/seed/index.d.ts +1 -1
- package/dist/core/seed/index.js +921 -321
- package/dist/core/{session-C7mvs-rD.d.cts → session-CAfhv1qL.d.ts} +35 -15
- package/dist/core/{session-elEYFVev.d.ts → session-MNU4BQv4.d.cts} +35 -15
- package/dist/core/{session-guard-DOQgVFL6.d.cts → session-guard-C0LkDEN7.d.ts} +9 -4
- package/dist/core/{session-guard-D7hUa4D2.d.ts → session-guard-kvAx_8Fb.d.cts} +9 -4
- package/dist/core/{task-DJMxZTv4.d.cts → task-BLPFCORT.d.ts} +17 -3
- package/dist/core/{task-C8SPRSHg.d.ts → task-wht1RJEL.d.cts} +17 -3
- package/dist/core/templates/handlebars-helpers.cjs +3 -0
- package/dist/core/templates/handlebars-helpers.d.cts +5 -1
- package/dist/core/templates/handlebars-helpers.d.ts +5 -1
- package/dist/core/templates/handlebars-helpers.js +3 -0
- package/dist/core/templates/session-context.d.cts +6 -6
- package/dist/core/templates/session-context.d.ts +6 -6
- package/dist/core/templates/session-context.js +1 -1
- package/dist/core/tools/mcp/jwt-auth.d.cts +2 -2
- package/dist/core/tools/mcp/jwt-auth.d.ts +2 -2
- package/dist/core/tools/mcp/oauth-mcp-transport.d.cts +20 -4
- package/dist/core/tools/mcp/oauth-mcp-transport.d.ts +20 -4
- package/dist/core/tools/mcp/oauth-refresh.cjs +2902 -0
- package/dist/core/tools/mcp/oauth-refresh.d.cts +129 -0
- package/dist/core/tools/mcp/oauth-refresh.d.ts +129 -0
- package/dist/core/tools/mcp/oauth-refresh.js +2897 -0
- package/dist/core/types/index.cjs +45 -5
- package/dist/core/types/index.d.cts +17 -17
- package/dist/core/types/index.d.ts +17 -17
- package/dist/core/types/index.js +39 -5
- package/dist/core/{types-CvXKxTNP.d.ts → types-BsvM6vfZ.d.cts} +282 -4
- package/dist/core/{types-BQRGoDkg.d.cts → types-DuWSldjW.d.ts} +282 -4
- package/dist/core/unix/index.cjs +1448 -257
- package/dist/core/unix/index.d.cts +442 -36
- package/dist/core/unix/index.d.ts +442 -36
- package/dist/core/unix/index.js +1429 -247
- package/dist/core/{user-C9UDwwtA.d.ts → user-BfVWBmXA.d.ts} +46 -9
- package/dist/core/{user-wScngdUE.d.cts → user-DkNdeFoz.d.cts} +46 -9
- package/dist/core/utils/board-placement.d.cts +3 -3
- package/dist/core/utils/board-placement.d.ts +3 -3
- package/dist/core/utils/host-ip.cjs +51 -0
- package/dist/core/utils/host-ip.d.cts +27 -0
- package/dist/core/utils/host-ip.d.ts +27 -0
- package/dist/core/utils/host-ip.js +25 -0
- package/dist/core/utils/permission-mode-mapper.d.cts +4 -4
- package/dist/core/utils/permission-mode-mapper.d.ts +4 -4
- package/dist/core/utils/url.cjs +4 -3
- package/dist/core/utils/url.d.cts +1 -1
- package/dist/core/utils/url.d.ts +1 -1
- package/dist/core/utils/url.js +5 -4
- package/dist/core/yaml/index.cjs +49 -0
- package/dist/core/yaml/index.d.cts +19 -0
- package/dist/core/yaml/index.d.ts +19 -0
- package/dist/core/yaml/index.js +12 -0
- package/dist/daemon/auth/session-token-strategy.js +5 -5
- package/dist/daemon/declarations.d.ts +10 -3
- package/dist/daemon/index.js +5259 -2322
- package/dist/daemon/main.js +5259 -2322
- package/dist/daemon/mcp/resolve-ids.d.ts +1 -1
- package/dist/daemon/mcp/server.js +379 -138
- package/dist/daemon/mcp/tokens.d.ts +51 -60
- package/dist/daemon/mcp/tokens.js +93 -45
- package/dist/daemon/mcp/tools/analytics.js +49 -21
- package/dist/daemon/mcp/tools/artifacts.js +202 -13
- package/dist/daemon/mcp/tools/boards.js +51 -11
- package/dist/daemon/mcp/tools/card-types.js +42 -10
- package/dist/daemon/mcp/tools/cards.js +42 -10
- package/dist/daemon/mcp/tools/environment.js +42 -10
- package/dist/daemon/mcp/tools/mcp-servers.js +44 -14
- package/dist/daemon/mcp/tools/messages.js +57 -9
- package/dist/daemon/mcp/tools/repos.js +42 -10
- package/dist/daemon/mcp/tools/search.js +42 -10
- package/dist/daemon/mcp/tools/sessions.js +69 -30
- package/dist/daemon/mcp/tools/tasks.js +42 -10
- package/dist/daemon/mcp/tools/users.js +42 -10
- package/dist/daemon/mcp/tools/worktrees.js +46 -23
- package/dist/daemon/oauth-cache.d.ts +15 -20
- package/dist/daemon/oauth-cache.js +21 -94
- package/dist/daemon/register-hooks.d.ts +59 -1
- package/dist/daemon/register-hooks.js +496 -186
- package/dist/daemon/register-routes.d.ts +16 -1
- package/dist/daemon/register-routes.js +919 -201
- package/dist/daemon/register-services.d.ts +2 -0
- package/dist/daemon/register-services.js +2097 -479
- package/dist/daemon/services/artifacts.d.ts +76 -2
- package/dist/daemon/services/artifacts.js +276 -3
- package/dist/daemon/services/boards.js +1 -0
- package/dist/daemon/services/context.js +1 -0
- package/dist/daemon/services/file.js +1 -0
- package/dist/daemon/services/gateway.js +7 -16
- package/dist/daemon/services/github-app-setup.d.ts +70 -7
- package/dist/daemon/services/github-app-setup.js +161 -13
- package/dist/daemon/services/github-install-state.d.ts +52 -0
- package/dist/daemon/services/github-install-state.js +58 -0
- package/dist/daemon/services/leaderboard.d.ts +18 -4
- package/dist/daemon/services/leaderboard.js +104 -26
- package/dist/daemon/services/mcp-servers.d.ts +7 -2
- package/dist/daemon/services/messages.js +10 -3
- package/dist/daemon/services/oauth-disconnect.d.ts +5 -1
- package/dist/daemon/services/oauth-disconnect.js +10 -13
- package/dist/daemon/services/repos.d.ts +33 -2
- package/dist/daemon/services/repos.js +130 -33
- package/dist/daemon/services/scheduler.d.ts +58 -8
- package/dist/daemon/services/scheduler.js +136 -14
- package/dist/daemon/services/session-env-selections.d.ts +56 -0
- package/dist/daemon/services/session-env-selections.js +39 -0
- package/dist/daemon/services/sessions.d.ts +39 -2
- package/dist/daemon/services/sessions.js +180 -30
- package/dist/daemon/services/tasks.js +1 -1
- package/dist/daemon/services/terminals.js +62 -7
- package/dist/daemon/services/users.d.ts +7 -3
- package/dist/daemon/services/users.js +66 -15
- package/dist/daemon/services/worktree-owners.js +15 -7
- package/dist/daemon/services/worktrees.d.ts +46 -0
- package/dist/daemon/services/worktrees.js +342 -38
- package/dist/daemon/setup/build-info.d.ts +37 -0
- package/dist/daemon/setup/build-info.js +51 -0
- package/dist/daemon/setup/cors.d.ts +59 -22
- package/dist/daemon/setup/cors.js +69 -33
- package/dist/daemon/setup/database.js +15 -17
- package/dist/daemon/setup/index.d.ts +2 -1
- package/dist/daemon/setup/index.js +309 -66
- package/dist/daemon/setup/security-headers.d.ts +48 -0
- package/dist/daemon/setup/security-headers.js +35 -0
- package/dist/daemon/setup/service-tiers.d.ts +1 -1
- package/dist/daemon/setup/socketio.d.ts +93 -2
- package/dist/daemon/setup/socketio.js +179 -13
- package/dist/daemon/startup.js +153 -14
- package/dist/daemon/utils/auth-rate-limit-key.d.ts +21 -0
- package/dist/daemon/utils/auth-rate-limit-key.js +12 -0
- package/dist/daemon/utils/authorization.d.ts +14 -1
- package/dist/daemon/utils/authorization.js +17 -1
- package/dist/daemon/utils/html.d.ts +18 -0
- package/dist/daemon/utils/html.js +8 -0
- package/dist/daemon/utils/inject-created-by.d.ts +31 -0
- package/dist/daemon/utils/inject-created-by.js +28 -0
- package/dist/daemon/utils/session-state-hooks.d.ts +55 -0
- package/dist/daemon/utils/session-state-hooks.js +188 -0
- package/dist/daemon/utils/session-state.d.ts +45 -0
- package/dist/daemon/utils/session-state.js +100 -0
- package/dist/daemon/utils/spawn-executor.d.ts +9 -4
- package/dist/daemon/utils/spawn-executor.js +15 -7
- package/dist/daemon/utils/upload.d.ts +36 -1
- package/dist/daemon/utils/upload.js +89 -6
- package/dist/daemon/utils/worktree-authorization.d.ts +210 -3
- package/dist/daemon/utils/worktree-authorization.js +161 -27
- package/dist/executor/cli.js +10 -2
- package/dist/executor/commands/git.d.ts.map +1 -1
- package/dist/executor/commands/git.js +121 -56
- package/dist/executor/commands/unix.d.ts +7 -0
- package/dist/executor/commands/unix.d.ts.map +1 -1
- package/dist/executor/commands/unix.js +52 -8
- package/dist/executor/handlers/sdk/base-executor.d.ts.map +1 -1
- package/dist/executor/handlers/sdk/base-executor.js +3 -0
- package/dist/executor/index.d.ts.map +1 -1
- package/dist/executor/index.js +3 -0
- package/dist/executor/payload-types.d.ts +16 -14
- package/dist/executor/payload-types.d.ts.map +1 -1
- package/dist/executor/payload-types.js +2 -0
- package/dist/executor/sdk-handlers/claude/model-utils.d.ts +22 -0
- package/dist/executor/sdk-handlers/claude/model-utils.d.ts.map +1 -0
- package/dist/executor/sdk-handlers/claude/model-utils.js +28 -0
- package/dist/executor/sdk-handlers/claude/normalizer.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/claude/normalizer.js +2 -1
- package/dist/executor/sdk-handlers/claude/query-builder.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/claude/query-builder.js +20 -20
- package/dist/executor/sdk-handlers/codex/prompt-service.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/codex/prompt-service.js +7 -3
- package/dist/executor/sdk-handlers/copilot/prompt-service.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/copilot/prompt-service.js +4 -1
- package/dist/executor/sdk-handlers/gemini/prompt-service.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/gemini/prompt-service.js +4 -3
- package/dist/executor/sdk-handlers/opencode/opencode-tool.d.ts.map +1 -1
- package/dist/executor/sdk-handlers/opencode/opencode-tool.js +2 -1
- package/dist/ui/assets/CodeEditor.inner-bDJMowpz.js +2 -0
- package/dist/ui/assets/CodeEditor.inner-bDJMowpz.js.gz +0 -0
- package/dist/ui/assets/{_basePickBy-CQZh_v13.js → _basePickBy-Bp06Otx8.js} +1 -1
- package/dist/ui/assets/_basePickBy-Bp06Otx8.js.gz +0 -0
- package/dist/ui/assets/{_baseUniq-Cgf5LtCM.js → _baseUniq-BfcctmWO.js} +1 -1
- package/dist/ui/assets/_baseUniq-BfcctmWO.js.gz +0 -0
- package/dist/ui/assets/{arc-DnTl2a4f.js → arc-CNqJvXfe.js} +1 -1
- package/dist/ui/assets/arc-CNqJvXfe.js.gz +0 -0
- package/dist/ui/assets/{architectureDiagram-VXUJARFQ-DZHKQxRL.js → architectureDiagram-VXUJARFQ-Lda-Lg4v.js} +1 -1
- package/dist/ui/assets/architectureDiagram-VXUJARFQ-Lda-Lg4v.js.gz +0 -0
- package/dist/ui/assets/{base-80a1f760-pDLzaKJY.js → base-80a1f760-If1fxi5k.js} +1 -1
- package/dist/ui/assets/{blockDiagram-VD42YOAC-BAOUMLja.js → blockDiagram-VD42YOAC-CziS0xWd.js} +1 -1
- package/dist/ui/assets/blockDiagram-VD42YOAC-CziS0xWd.js.gz +0 -0
- package/dist/ui/assets/{c4Diagram-YG6GDRKO-C0xcWHxz.js → c4Diagram-YG6GDRKO-Cu_7Cpwg.js} +1 -1
- package/dist/ui/assets/c4Diagram-YG6GDRKO-Cu_7Cpwg.js.gz +0 -0
- package/dist/ui/assets/channel-CTywVsey.js +1 -0
- package/dist/ui/assets/{chunk-4BX2VUAB-DCYNUaJZ.js → chunk-4BX2VUAB-CL7awCKO.js} +1 -1
- package/dist/ui/assets/{chunk-55IACEB6-DU3b1X7p.js → chunk-55IACEB6-CdEpihRe.js} +1 -1
- package/dist/ui/assets/{chunk-B4BG7PRW-BJlNsfTo.js → chunk-B4BG7PRW-CJZNfnap.js} +1 -1
- package/dist/ui/assets/chunk-B4BG7PRW-CJZNfnap.js.gz +0 -0
- package/dist/ui/assets/{chunk-DI55MBZ5-CKQY-IiF.js → chunk-DI55MBZ5-CVxoCWqP.js} +1 -1
- package/dist/ui/assets/chunk-DI55MBZ5-CVxoCWqP.js.gz +0 -0
- package/dist/ui/assets/{chunk-FMBD7UC4-DmjQ-Dzu.js → chunk-FMBD7UC4-DP03W4uO.js} +1 -1
- package/dist/ui/assets/{chunk-QN33PNHL-Bps50N51.js → chunk-QN33PNHL-BAZubWUQ.js} +1 -1
- package/dist/ui/assets/{chunk-QZHKN3VN-CNDKr6Xh.js → chunk-QZHKN3VN-BzRHvECc.js} +1 -1
- package/dist/ui/assets/{chunk-TZMSLE5B-R2Vz5IHZ.js → chunk-TZMSLE5B-iEWip6lm.js} +1 -1
- package/dist/ui/assets/chunk-TZMSLE5B-iEWip6lm.js.gz +0 -0
- package/dist/ui/assets/classDiagram-2ON5EDUG-DZGEyZn1.js +1 -0
- package/dist/ui/assets/classDiagram-v2-WZHVMYZB-DZGEyZn1.js +1 -0
- package/dist/ui/assets/clone-DTzUqr7D.js +1 -0
- package/dist/ui/assets/{consoleHook-59e792cb-MfGBgEJx.js → consoleHook-59e792cb-CVUkgtJ5.js} +1 -1
- package/dist/ui/assets/consoleHook-59e792cb-CVUkgtJ5.js.gz +0 -0
- package/dist/ui/assets/{cose-bilkent-S5V4N54A-Bkbv_h_w.js → cose-bilkent-S5V4N54A-CaNMYr_q.js} +1 -1
- package/dist/ui/assets/cose-bilkent-S5V4N54A-CaNMYr_q.js.gz +0 -0
- package/dist/ui/assets/{dagre-6UL2VRFP-DUoxEnB9.js → dagre-6UL2VRFP-PgKTEyMg.js} +1 -1
- package/dist/ui/assets/dagre-6UL2VRFP-PgKTEyMg.js.gz +0 -0
- package/dist/ui/assets/{diagram-PSM6KHXK-BUdRu7ma.js → diagram-PSM6KHXK-Bbakw1vV.js} +1 -1
- package/dist/ui/assets/diagram-PSM6KHXK-Bbakw1vV.js.gz +0 -0
- package/dist/ui/assets/{diagram-QEK2KX5R-DEoZ3bpF.js → diagram-QEK2KX5R-DORE6MDc.js} +1 -1
- package/dist/ui/assets/diagram-QEK2KX5R-DORE6MDc.js.gz +0 -0
- package/dist/ui/assets/{diagram-S2PKOQOG-CxM9wSuL.js → diagram-S2PKOQOG-CWMSw88L.js} +1 -1
- package/dist/ui/assets/diagram-S2PKOQOG-CWMSw88L.js.gz +0 -0
- package/dist/ui/assets/{erDiagram-Q2GNP2WA-D5VuZpBE.js → erDiagram-Q2GNP2WA-ClUb9rMg.js} +1 -1
- package/dist/ui/assets/erDiagram-Q2GNP2WA-ClUb9rMg.js.gz +0 -0
- package/dist/ui/assets/{flowDiagram-NV44I4VS-uuzmVpS4.js → flowDiagram-NV44I4VS-NGGzalyO.js} +1 -1
- package/dist/ui/assets/flowDiagram-NV44I4VS-NGGzalyO.js.gz +0 -0
- package/dist/ui/assets/ganttDiagram-LVOFAZNH-BD2QM47F.js +267 -0
- package/dist/ui/assets/ganttDiagram-LVOFAZNH-BD2QM47F.js.gz +0 -0
- package/dist/ui/assets/{gitGraphDiagram-NY62KEGX-DJDE3D90.js → gitGraphDiagram-NY62KEGX-IuwPjdlq.js} +2 -2
- package/dist/ui/assets/gitGraphDiagram-NY62KEGX-IuwPjdlq.js.gz +0 -0
- package/dist/ui/assets/{graph-BmWYHUOg.js → graph-mTd64UTw.js} +1 -1
- package/dist/ui/assets/graph-mTd64UTw.js.gz +0 -0
- package/dist/ui/assets/{index-599aeaf7-C76CG5K3.js → index-599aeaf7-C31VkrEQ.js} +1 -1
- package/dist/ui/assets/index-599aeaf7-C31VkrEQ.js.gz +0 -0
- package/dist/ui/assets/{index-CMrDOI5G.js → index-BmF5q3WZ.js} +435 -346
- package/dist/ui/assets/index-BmF5q3WZ.js.gz +0 -0
- package/dist/ui/assets/{index-CuMJFr7K.js → index-DlTdTyWM.js} +3 -3
- package/dist/ui/assets/index-DlTdTyWM.js.gz +0 -0
- package/dist/ui/assets/{index-fQo-B6L6.js → index-DlWtzMCo.js} +1 -1
- package/dist/ui/assets/index-DlWtzMCo.js.gz +0 -0
- package/dist/ui/assets/index-cfuYAmaV.js +4 -0
- package/dist/ui/assets/index-cfuYAmaV.js.gz +0 -0
- package/dist/ui/assets/{infoDiagram-ER5ION4S--dZLWvYP.js → infoDiagram-ER5ION4S-DPQFLihF.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-XKPGCS4Q-C-Mzd3Ky.js → journeyDiagram-XKPGCS4Q-DTcUc9R_.js} +1 -1
- package/dist/ui/assets/journeyDiagram-XKPGCS4Q-DTcUc9R_.js.gz +0 -0
- package/dist/ui/assets/{kanban-definition-3W4ZIXB7-DoDzQCSc.js → kanban-definition-3W4ZIXB7-BS9ZD8g6.js} +5 -5
- package/dist/ui/assets/kanban-definition-3W4ZIXB7-BS9ZD8g6.js.gz +0 -0
- package/dist/ui/assets/{layout-BmSvKYXZ.js → layout-BjEUAE2D.js} +1 -1
- package/dist/ui/assets/layout-BjEUAE2D.js.gz +0 -0
- package/dist/ui/assets/linear-CLcpJ4o5.js +1 -0
- package/dist/ui/assets/linear-CLcpJ4o5.js.gz +0 -0
- package/dist/ui/assets/{mermaid.core-Suk7MkfT.js → mermaid.core-Br1XtbEr.js} +49 -49
- package/dist/ui/assets/mermaid.core-Br1XtbEr.js.gz +0 -0
- package/dist/ui/assets/{mindmap-definition-VGOIOE7T-Bj9HZQA9.js → mindmap-definition-VGOIOE7T-0S9U22YV.js} +3 -3
- package/dist/ui/assets/mindmap-definition-VGOIOE7T-0S9U22YV.js.gz +0 -0
- package/dist/ui/assets/{pieDiagram-ADFJNKIX-BIcJ5ee8.js → pieDiagram-ADFJNKIX-4yBZb57e.js} +2 -2
- package/dist/ui/assets/pieDiagram-ADFJNKIX-4yBZb57e.js.gz +0 -0
- package/dist/ui/assets/{quadrantDiagram-AYHSOK5B-CJm5QQ8N.js → quadrantDiagram-AYHSOK5B-BGdI-z0e.js} +2 -2
- package/dist/ui/assets/quadrantDiagram-AYHSOK5B-BGdI-z0e.js.gz +0 -0
- package/dist/ui/assets/{requirementDiagram-UZGBJVZJ-Bg6r943V.js → requirementDiagram-UZGBJVZJ-CfZa19uA.js} +1 -1
- package/dist/ui/assets/requirementDiagram-UZGBJVZJ-CfZa19uA.js.gz +0 -0
- package/dist/ui/assets/{sankeyDiagram-TZEHDZUN-C2XnZZZp.js → sankeyDiagram-TZEHDZUN-uFBRhhPF.js} +1 -1
- package/dist/ui/assets/sankeyDiagram-TZEHDZUN-uFBRhhPF.js.gz +0 -0
- package/dist/ui/assets/{sequenceDiagram-WL72ISMW-DOg-bBN1.js → sequenceDiagram-WL72ISMW-BdGgfhvi.js} +1 -1
- package/dist/ui/assets/sequenceDiagram-WL72ISMW-BdGgfhvi.js.gz +0 -0
- package/dist/ui/assets/{stateDiagram-FKZM4ZOC-DgZgPM-o.js → stateDiagram-FKZM4ZOC-DKkzcGoH.js} +1 -1
- package/dist/ui/assets/stateDiagram-FKZM4ZOC-DKkzcGoH.js.gz +0 -0
- package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-DL2_q_1s.js +1 -0
- package/dist/ui/assets/{timeline-definition-IT6M3QCI-CmkWt6nL.js → timeline-definition-IT6M3QCI-BDzprIh8.js} +1 -1
- package/dist/ui/assets/timeline-definition-IT6M3QCI-BDzprIh8.js.gz +0 -0
- package/dist/ui/assets/{treemap-KMMF4GRG-CV8wwXTM.js → treemap-KMMF4GRG-BjxPgjXQ.js} +1 -1
- package/dist/ui/assets/treemap-KMMF4GRG-BjxPgjXQ.js.gz +0 -0
- package/dist/ui/assets/{xychartDiagram-PRI3JC2R-CvuPvlyn.js → xychartDiagram-PRI3JC2R-DVGB7LfP.js} +2 -2
- package/dist/ui/assets/xychartDiagram-PRI3JC2R-DVGB7LfP.js.gz +0 -0
- package/dist/ui/index.html +1 -1
- package/package.json +21 -20
- package/dist/ui/assets/_basePickBy-CQZh_v13.js.gz +0 -0
- package/dist/ui/assets/_baseUniq-Cgf5LtCM.js.gz +0 -0
- package/dist/ui/assets/arc-DnTl2a4f.js.gz +0 -0
- package/dist/ui/assets/architectureDiagram-VXUJARFQ-DZHKQxRL.js.gz +0 -0
- package/dist/ui/assets/blockDiagram-VD42YOAC-BAOUMLja.js.gz +0 -0
- package/dist/ui/assets/c4Diagram-YG6GDRKO-C0xcWHxz.js.gz +0 -0
- package/dist/ui/assets/channel-DpHH4gqH.js +0 -1
- package/dist/ui/assets/chunk-B4BG7PRW-BJlNsfTo.js.gz +0 -0
- package/dist/ui/assets/chunk-DI55MBZ5-CKQY-IiF.js.gz +0 -0
- package/dist/ui/assets/chunk-TZMSLE5B-R2Vz5IHZ.js.gz +0 -0
- package/dist/ui/assets/classDiagram-2ON5EDUG-CbDCU4XU.js +0 -1
- package/dist/ui/assets/classDiagram-v2-WZHVMYZB-CbDCU4XU.js +0 -1
- package/dist/ui/assets/clone-DSMLFNzW.js +0 -1
- package/dist/ui/assets/consoleHook-59e792cb-MfGBgEJx.js.gz +0 -0
- package/dist/ui/assets/cose-bilkent-S5V4N54A-Bkbv_h_w.js.gz +0 -0
- package/dist/ui/assets/dagre-6UL2VRFP-DUoxEnB9.js.gz +0 -0
- package/dist/ui/assets/diagram-PSM6KHXK-BUdRu7ma.js.gz +0 -0
- package/dist/ui/assets/diagram-QEK2KX5R-DEoZ3bpF.js.gz +0 -0
- package/dist/ui/assets/diagram-S2PKOQOG-CxM9wSuL.js.gz +0 -0
- package/dist/ui/assets/erDiagram-Q2GNP2WA-D5VuZpBE.js.gz +0 -0
- package/dist/ui/assets/flowDiagram-NV44I4VS-uuzmVpS4.js.gz +0 -0
- package/dist/ui/assets/ganttDiagram-LVOFAZNH-7spsgOUT.js +0 -267
- package/dist/ui/assets/ganttDiagram-LVOFAZNH-7spsgOUT.js.gz +0 -0
- package/dist/ui/assets/gitGraphDiagram-NY62KEGX-DJDE3D90.js.gz +0 -0
- package/dist/ui/assets/graph-BmWYHUOg.js.gz +0 -0
- package/dist/ui/assets/index-599aeaf7-C76CG5K3.js.gz +0 -0
- package/dist/ui/assets/index-B0lOKf7g.js +0 -4
- package/dist/ui/assets/index-B0lOKf7g.js.gz +0 -0
- package/dist/ui/assets/index-CMrDOI5G.js.gz +0 -0
- package/dist/ui/assets/index-CuMJFr7K.js.gz +0 -0
- package/dist/ui/assets/index-fQo-B6L6.js.gz +0 -0
- package/dist/ui/assets/journeyDiagram-XKPGCS4Q-C-Mzd3Ky.js.gz +0 -0
- package/dist/ui/assets/kanban-definition-3W4ZIXB7-DoDzQCSc.js.gz +0 -0
- package/dist/ui/assets/layout-BmSvKYXZ.js.gz +0 -0
- package/dist/ui/assets/linear-BnPF1K20.js +0 -1
- package/dist/ui/assets/linear-BnPF1K20.js.gz +0 -0
- package/dist/ui/assets/mermaid.core-Suk7MkfT.js.gz +0 -0
- package/dist/ui/assets/mindmap-definition-VGOIOE7T-Bj9HZQA9.js.gz +0 -0
- package/dist/ui/assets/pieDiagram-ADFJNKIX-BIcJ5ee8.js.gz +0 -0
- package/dist/ui/assets/quadrantDiagram-AYHSOK5B-CJm5QQ8N.js.gz +0 -0
- package/dist/ui/assets/requirementDiagram-UZGBJVZJ-Bg6r943V.js.gz +0 -0
- package/dist/ui/assets/sankeyDiagram-TZEHDZUN-C2XnZZZp.js.gz +0 -0
- package/dist/ui/assets/sequenceDiagram-WL72ISMW-DOg-bBN1.js.gz +0 -0
- package/dist/ui/assets/stateDiagram-FKZM4ZOC-DgZgPM-o.js.gz +0 -0
- package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-C_0TAQGU.js +0 -1
- package/dist/ui/assets/timeline-definition-IT6M3QCI-CmkWt6nL.js.gz +0 -0
- package/dist/ui/assets/treemap-KMMF4GRG-CV8wwXTM.js.gz +0 -0
- package/dist/ui/assets/xychartDiagram-PRI3JC2R-CvuPvlyn.js.gz +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/commands/admin/sync-unix.ts
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
-
import { existsSync } from "fs";
|
|
3
|
+
import { existsSync, readlinkSync } from "fs";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { loadConfig } from "@agor/core/config";
|
|
@@ -15,14 +15,29 @@ import {
|
|
|
15
15
|
worktreeOwners,
|
|
16
16
|
worktrees
|
|
17
17
|
} from "@agor/core/db";
|
|
18
|
+
import { restoreWorktreeFilesystem } from "@agor/core/git";
|
|
18
19
|
import {
|
|
19
20
|
AGOR_USERS_GROUP,
|
|
21
|
+
CommandError,
|
|
22
|
+
createAdminExecutor,
|
|
20
23
|
generateRepoGroupName,
|
|
21
24
|
generateWorktreeGroupName,
|
|
25
|
+
getGroupMembers,
|
|
26
|
+
getUserGroups,
|
|
27
|
+
getUserWorktreesDir,
|
|
28
|
+
getWorktreeDirectoryAction,
|
|
22
29
|
getWorktreePermissionMode,
|
|
30
|
+
getWorktreeSymlinkPath,
|
|
31
|
+
groupExists,
|
|
32
|
+
isUserInGroup,
|
|
33
|
+
listAgorUsers,
|
|
34
|
+
listRepoGroups,
|
|
35
|
+
listWorktreeGroups,
|
|
23
36
|
REPO_GIT_PERMISSION_MODE,
|
|
37
|
+
SymlinkCommands,
|
|
24
38
|
UnixGroupCommands,
|
|
25
|
-
UnixUserCommands
|
|
39
|
+
UnixUserCommands,
|
|
40
|
+
unixUserExists
|
|
26
41
|
} from "@agor/core/unix";
|
|
27
42
|
import { Command, Flags } from "@oclif/core";
|
|
28
43
|
import chalk from "chalk";
|
|
@@ -32,7 +47,8 @@ var SyncUnix = class _SyncUnix extends Command {
|
|
|
32
47
|
"<%= config.bin %> <%= command.id %> # Full sync (creates users, groups, sets permissions)",
|
|
33
48
|
"<%= config.bin %> <%= command.id %> --dry-run # Preview what would be done",
|
|
34
49
|
"<%= config.bin %> <%= command.id %> --cleanup # Full sync + remove stale users/groups",
|
|
35
|
-
"<%= config.bin %> <%= command.id %> --verbose # Show detailed output"
|
|
50
|
+
"<%= config.bin %> <%= command.id %> --verbose # Show detailed output",
|
|
51
|
+
"<%= config.bin %> <%= command.id %> --worktree-id <uuid> --dry-run # Preview sync for a single worktree"
|
|
36
52
|
];
|
|
37
53
|
static flags = {
|
|
38
54
|
"dry-run": Flags.boolean({
|
|
@@ -57,193 +73,79 @@ var SyncUnix = class _SyncUnix extends Command {
|
|
|
57
73
|
"cleanup-users": Flags.boolean({
|
|
58
74
|
description: "Delete stale agor_* users not in database (keeps home directories)",
|
|
59
75
|
default: false
|
|
76
|
+
}),
|
|
77
|
+
"worktree-id": Flags.string({
|
|
78
|
+
char: "w",
|
|
79
|
+
description: "Sync a single worktree and its parent repo (skips unrelated user/membership/symlink phases)"
|
|
60
80
|
})
|
|
61
81
|
};
|
|
62
|
-
/**
|
|
63
|
-
* Check if a Unix user exists on the system
|
|
64
|
-
*/
|
|
65
|
-
userExists(username) {
|
|
66
|
-
try {
|
|
67
|
-
execSync(UnixUserCommands.userExists(username), { stdio: "ignore" });
|
|
68
|
-
return true;
|
|
69
|
-
} catch {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get groups a Unix user belongs to
|
|
75
|
-
*/
|
|
76
|
-
getUserGroups(username) {
|
|
77
|
-
try {
|
|
78
|
-
const output = execSync(UnixUserCommands.getUserGroups(username), {
|
|
79
|
-
encoding: "utf-8",
|
|
80
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
81
|
-
});
|
|
82
|
-
return output.trim().split(/\s+/).filter(Boolean);
|
|
83
|
-
} catch {
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Check if a Unix group exists
|
|
89
|
-
*/
|
|
90
|
-
groupExists(groupName) {
|
|
91
|
-
try {
|
|
92
|
-
execSync(UnixGroupCommands.groupExists(groupName), { stdio: "ignore" });
|
|
93
|
-
return true;
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if a Unix user is in a group
|
|
100
|
-
*/
|
|
101
|
-
isUserInGroup(username, groupName) {
|
|
102
|
-
try {
|
|
103
|
-
execSync(UnixGroupCommands.isUserInGroup(username, groupName), { stdio: "ignore" });
|
|
104
|
-
return true;
|
|
105
|
-
} catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Create a Unix user (assumes running as root via sudo)
|
|
111
|
-
*/
|
|
112
|
-
createUser(username, dryRun) {
|
|
113
|
-
const cmd = UnixUserCommands.createUser(username);
|
|
114
|
-
if (dryRun) {
|
|
115
|
-
this.log(chalk.gray(` [dry-run] Would run: ${cmd}`));
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
execSync(cmd, { stdio: "inherit" });
|
|
120
|
-
return true;
|
|
121
|
-
} catch {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Add user to a group (assumes running as root via sudo)
|
|
127
|
-
*/
|
|
128
|
-
addUserToGroup(username, groupName, dryRun) {
|
|
129
|
-
const cmd = UnixGroupCommands.addUserToGroup(username, groupName);
|
|
130
|
-
if (dryRun) {
|
|
131
|
-
this.log(chalk.gray(` [dry-run] Would run: ${cmd}`));
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
execSync(cmd, { stdio: "inherit" });
|
|
136
|
-
return true;
|
|
137
|
-
} catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Create a Unix group (assumes running as root via sudo)
|
|
143
|
-
*/
|
|
144
|
-
createGroup(groupName, dryRun) {
|
|
145
|
-
const cmd = UnixGroupCommands.createGroup(groupName);
|
|
146
|
-
if (dryRun) {
|
|
147
|
-
this.log(chalk.gray(` [dry-run] Would run: ${cmd}`));
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
execSync(cmd, { stdio: "inherit" });
|
|
152
|
-
return true;
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Delete a Unix user (keeps home directory)
|
|
159
|
-
*/
|
|
160
|
-
deleteUser(username, dryRun) {
|
|
161
|
-
const cmd = UnixUserCommands.deleteUser(username);
|
|
162
|
-
if (dryRun) {
|
|
163
|
-
this.log(chalk.gray(` [dry-run] Would run: ${cmd}`));
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
execSync(cmd, { stdio: "inherit" });
|
|
168
|
-
return true;
|
|
169
|
-
} catch {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Delete a Unix group
|
|
175
|
-
*/
|
|
176
|
-
deleteGroup(groupName, dryRun) {
|
|
177
|
-
const cmd = UnixGroupCommands.deleteGroup(groupName);
|
|
178
|
-
if (dryRun) {
|
|
179
|
-
this.log(chalk.gray(` [dry-run] Would run: ${cmd}`));
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
execSync(cmd, { stdio: "inherit" });
|
|
184
|
-
return true;
|
|
185
|
-
} catch {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* List all agor_* users on the system (auto-generated format: agor_<8-hex>)
|
|
191
|
-
*/
|
|
192
|
-
listAgorUsers() {
|
|
193
|
-
try {
|
|
194
|
-
const output = execSync("getent passwd | grep '^agor_' | cut -d: -f1", {
|
|
195
|
-
encoding: "utf-8",
|
|
196
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
197
|
-
});
|
|
198
|
-
return output.trim().split("\n").filter((u) => u && /^agor_[0-9a-f]{8}$/.test(u));
|
|
199
|
-
} catch {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* List all agor_wt_* groups on the system
|
|
205
|
-
*/
|
|
206
|
-
listWorktreeGroups() {
|
|
207
|
-
try {
|
|
208
|
-
const output = execSync("getent group | grep '^agor_wt_' | cut -d: -f1", {
|
|
209
|
-
encoding: "utf-8",
|
|
210
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
211
|
-
});
|
|
212
|
-
return output.trim().split("\n").filter((g) => g && /^agor_wt_[0-9a-f]{8}$/.test(g));
|
|
213
|
-
} catch {
|
|
214
|
-
return [];
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* List all agor_rp_* (repo) groups on the system
|
|
219
|
-
*/
|
|
220
|
-
listRepoGroups() {
|
|
221
|
-
try {
|
|
222
|
-
const output = execSync("getent group | grep '^agor_rp_' | cut -d: -f1", {
|
|
223
|
-
encoding: "utf-8",
|
|
224
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
225
|
-
});
|
|
226
|
-
return output.trim().split("\n").filter((g) => g && /^agor_rp_[0-9a-f]{8}$/.test(g));
|
|
227
|
-
} catch {
|
|
228
|
-
return [];
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
82
|
async run() {
|
|
232
83
|
const { flags } = await this.parse(_SyncUnix);
|
|
233
84
|
const dryRun = flags["dry-run"];
|
|
234
85
|
const verbose = flags.verbose;
|
|
235
86
|
const cleanupGroups = flags.cleanup || flags["cleanup-groups"];
|
|
236
87
|
const cleanupUsers = flags.cleanup || flags["cleanup-users"];
|
|
88
|
+
const targetWorktreeId = flags["worktree-id"];
|
|
89
|
+
if (targetWorktreeId) {
|
|
90
|
+
this.log(chalk.cyan(`\u{1F3AF} Targeting single worktree: ${targetWorktreeId}
|
|
91
|
+
`));
|
|
92
|
+
}
|
|
237
93
|
if (dryRun) {
|
|
238
94
|
this.log(chalk.yellow("\u{1F50D} Dry run mode - no changes will be made\n"));
|
|
239
95
|
}
|
|
96
|
+
const executor = createAdminExecutor({ "dry-run": dryRun, verbose });
|
|
97
|
+
const logCmdError = (err, fallbackCmd) => {
|
|
98
|
+
if (err instanceof CommandError) {
|
|
99
|
+
const cmd = err.command || fallbackCmd;
|
|
100
|
+
const stderr = err.result.stderr.trim();
|
|
101
|
+
if (cmd) this.log(chalk.red(` \u21B3 ${cmd}`));
|
|
102
|
+
if (stderr) {
|
|
103
|
+
for (const line of stderr.split("\n").slice(0, 10)) {
|
|
104
|
+
this.log(chalk.red(` ${line}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.log(chalk.red(` (exit ${err.result.exitCode})`));
|
|
108
|
+
} else {
|
|
109
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
+
if (fallbackCmd) this.log(chalk.red(` \u21B3 ${fallbackCmd}`));
|
|
111
|
+
this.log(chalk.red(` ${msg}`));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const execCmd = async (cmd) => {
|
|
115
|
+
try {
|
|
116
|
+
await executor.exec(cmd);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logCmdError(err, cmd);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const execAllCmds = async (cmds) => {
|
|
124
|
+
try {
|
|
125
|
+
await executor.execAll(cmds);
|
|
126
|
+
return true;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
logCmdError(err);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
240
132
|
let groupsCreated = 0;
|
|
241
133
|
let groupsDeleted = 0;
|
|
242
134
|
let usersDeleted = 0;
|
|
243
135
|
let cleanupErrors = 0;
|
|
244
136
|
let worktreesSynced = 0;
|
|
137
|
+
let worktreesBackfilled = 0;
|
|
138
|
+
let worktreeDirsCreated = 0;
|
|
139
|
+
let worktreesRestored = 0;
|
|
140
|
+
let groupsCleaned = 0;
|
|
141
|
+
let statusFixed = 0;
|
|
142
|
+
let worktreesSkipped = 0;
|
|
245
143
|
let reposBackfilled = 0;
|
|
246
144
|
let reposPermSynced = 0;
|
|
145
|
+
let membershipsRemoved = 0;
|
|
146
|
+
let daemonAclsApplied = 0;
|
|
147
|
+
let symlinksCreated = 0;
|
|
148
|
+
let symlinksCleaned = 0;
|
|
247
149
|
let syncErrors = 0;
|
|
248
150
|
try {
|
|
249
151
|
let databaseUrl = process.env.DATABASE_URL;
|
|
@@ -302,11 +204,25 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
302
204
|
);
|
|
303
205
|
}
|
|
304
206
|
let daemonMembershipsAdded = 0;
|
|
207
|
+
let targetRepoId;
|
|
208
|
+
if (targetWorktreeId) {
|
|
209
|
+
const targetWts = await select(db).from(worktrees).where(eq(worktrees.worktree_id, targetWorktreeId)).all();
|
|
210
|
+
if (targetWts.length === 0) {
|
|
211
|
+
this.log(chalk.red(`\u2717 Worktree ${targetWorktreeId} not found in database
|
|
212
|
+
`));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
targetRepoId = targetWts[0].repo_id;
|
|
216
|
+
this.log(
|
|
217
|
+
chalk.cyan(` Parent repo: ${targetRepoId.substring(0, 8)} (also scoped to this repo)
|
|
218
|
+
`)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
305
221
|
this.log(chalk.cyan(`Checking ${AGOR_USERS_GROUP} group...
|
|
306
222
|
`));
|
|
307
|
-
if (!
|
|
223
|
+
if (!groupExists(AGOR_USERS_GROUP)) {
|
|
308
224
|
this.log(chalk.yellow(` \u2192 Creating ${AGOR_USERS_GROUP} group...`));
|
|
309
|
-
if (
|
|
225
|
+
if (await execCmd(UnixGroupCommands.createGroup(AGOR_USERS_GROUP))) {
|
|
310
226
|
groupsCreated++;
|
|
311
227
|
this.log(chalk.green(` \u2713 Created ${AGOR_USERS_GROUP} group
|
|
312
228
|
`));
|
|
@@ -321,136 +237,153 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
321
237
|
const allUsers = await select(db).from(users).all();
|
|
322
238
|
const validUsers = allUsers.filter((u) => u.unix_username);
|
|
323
239
|
const results = [];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.log(chalk.gray("\nTo set a unix_username for a user:"));
|
|
327
|
-
this.log(chalk.gray(" agor user update <email> --unix-username <username>\n"));
|
|
328
|
-
} else {
|
|
329
|
-
this.log(chalk.cyan(`Found ${validUsers.length} user(s) with unix_username
|
|
330
|
-
`));
|
|
331
|
-
const userIds = validUsers.map((u) => u.user_id);
|
|
332
|
-
const allOwnerships = await db.select().from(worktreeOwners).innerJoin(worktrees, eq(worktreeOwners.worktree_id, worktrees.worktree_id)).where(inArray(worktreeOwners.user_id, userIds));
|
|
333
|
-
const ownershipsByUser = /* @__PURE__ */ new Map();
|
|
334
|
-
for (const row of allOwnerships) {
|
|
335
|
-
const userId = row.worktree_owners.user_id;
|
|
336
|
-
const ownership = {
|
|
337
|
-
worktree_id: row.worktrees.worktree_id,
|
|
338
|
-
name: row.worktrees.name,
|
|
339
|
-
unix_group: row.worktrees.unix_group,
|
|
340
|
-
repo_id: row.worktrees.repo_id
|
|
341
|
-
};
|
|
342
|
-
const existing = ownershipsByUser.get(userId) || [];
|
|
343
|
-
existing.push(ownership);
|
|
344
|
-
ownershipsByUser.set(userId, existing);
|
|
345
|
-
}
|
|
346
|
-
let allRepos = await select(db).from(repos).all();
|
|
240
|
+
{
|
|
241
|
+
const reposInScope = targetRepoId ? await select(db).from(repos).where(eq(repos.repo_id, targetRepoId)).all() : await select(db).from(repos).all();
|
|
347
242
|
this.log(chalk.cyan.bold("\n\u2501\u2501\u2501 Sync Repos \u2501\u2501\u2501\n"));
|
|
348
|
-
|
|
349
|
-
(
|
|
350
|
-
);
|
|
351
|
-
if (reposWithoutGroup.length === 0) {
|
|
352
|
-
this.log(chalk.green(" \u2713 All repos have unix_group set\n"));
|
|
243
|
+
if (reposInScope.length === 0) {
|
|
244
|
+
this.log(chalk.yellow(" No repos in scope\n"));
|
|
353
245
|
} else {
|
|
246
|
+
this.log(chalk.cyan(`Processing ${reposInScope.length} repo(s)
|
|
247
|
+
`));
|
|
248
|
+
}
|
|
249
|
+
for (const repo of reposInScope) {
|
|
250
|
+
const rawRepo = repo;
|
|
251
|
+
const expectedGroup = rawRepo.unix_group || generateRepoGroupName(rawRepo.repo_id);
|
|
252
|
+
const dbNeedsBackfill = rawRepo.unix_group === null;
|
|
253
|
+
const groupMissingOnSystem = !groupExists(expectedGroup);
|
|
254
|
+
const repoPath = rawRepo.data?.local_path;
|
|
255
|
+
const pathUsable = repoPath ? existsSync(repoPath) : false;
|
|
256
|
+
this.log(chalk.bold(`\u{1F4C1} ${rawRepo.slug}`));
|
|
257
|
+
this.log(chalk.gray(` repo_id: ${rawRepo.repo_id.substring(0, 8)}`));
|
|
354
258
|
this.log(
|
|
355
|
-
chalk.
|
|
356
|
-
`Found ${reposWithoutGroup.length} repo(s) without unix_group (of ${allRepos.length} total)
|
|
357
|
-
`
|
|
358
|
-
)
|
|
259
|
+
chalk.gray(` unix_group: ${expectedGroup}${dbNeedsBackfill ? " (to backfill)" : ""}`)
|
|
359
260
|
);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this.log(chalk.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
261
|
+
if (repoPath) {
|
|
262
|
+
this.log(chalk.gray(` repo path: ${repoPath}${pathUsable ? "" : " (missing)"}`));
|
|
263
|
+
} else {
|
|
264
|
+
this.log(chalk.gray(` repo path: <none in data.local_path>`));
|
|
265
|
+
}
|
|
266
|
+
let hadError = false;
|
|
267
|
+
if (groupMissingOnSystem) {
|
|
268
|
+
this.log(chalk.yellow(` \u2192 Creating Unix group ${expectedGroup}...`));
|
|
269
|
+
if (await execCmd(UnixGroupCommands.createGroup(expectedGroup))) {
|
|
270
|
+
groupsCreated++;
|
|
271
|
+
this.log(chalk.green(` \u2713 Created Unix group ${expectedGroup}`));
|
|
369
272
|
} else {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
273
|
+
syncErrors++;
|
|
274
|
+
hadError = true;
|
|
275
|
+
this.log(chalk.red(` \u2717 Failed to create Unix group ${expectedGroup}`));
|
|
276
|
+
}
|
|
277
|
+
} else if (verbose) {
|
|
278
|
+
this.log(chalk.gray(` \u2713 Unix group exists`));
|
|
279
|
+
}
|
|
280
|
+
if (!hadError && daemonUser) {
|
|
281
|
+
const daemonInGroup = dryRun ? false : isUserInGroup(daemonUser, expectedGroup);
|
|
282
|
+
if (!daemonInGroup) {
|
|
283
|
+
this.log(
|
|
284
|
+
chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${expectedGroup}...`)
|
|
285
|
+
);
|
|
286
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(daemonUser, expectedGroup))) {
|
|
287
|
+
daemonMembershipsAdded++;
|
|
288
|
+
this.log(chalk.green(` \u2713 Added daemon user to ${expectedGroup}`));
|
|
374
289
|
} else {
|
|
375
290
|
syncErrors++;
|
|
376
|
-
this.log(chalk.red(` \u2717 Failed to
|
|
377
|
-
this.log("");
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
if (daemonUser) {
|
|
382
|
-
const daemonInGroup = dryRun ? false : this.isUserInGroup(daemonUser, repoGroup);
|
|
383
|
-
if (!daemonInGroup) {
|
|
384
|
-
this.log(chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${repoGroup}...`));
|
|
385
|
-
if (this.addUserToGroup(daemonUser, repoGroup, dryRun)) {
|
|
386
|
-
daemonMembershipsAdded++;
|
|
387
|
-
this.log(chalk.green(` \u2713 Added daemon user to ${repoGroup}`));
|
|
388
|
-
} else {
|
|
389
|
-
this.log(chalk.red(` \u2717 Failed to add daemon user to ${repoGroup}`));
|
|
390
|
-
}
|
|
391
|
-
} else if (verbose) {
|
|
392
|
-
this.log(chalk.gray(` \u2713 Daemon user already in ${repoGroup}`));
|
|
291
|
+
this.log(chalk.red(` \u2717 Failed to add daemon user to ${expectedGroup}`));
|
|
393
292
|
}
|
|
293
|
+
} else if (verbose) {
|
|
294
|
+
this.log(chalk.gray(` \u2713 Daemon user already in ${expectedGroup}`));
|
|
394
295
|
}
|
|
296
|
+
}
|
|
297
|
+
if (!hadError && dbNeedsBackfill) {
|
|
395
298
|
if (dryRun) {
|
|
396
299
|
this.log(
|
|
397
300
|
chalk.gray(
|
|
398
|
-
` [dry-run] Would update database: SET unix_group = '${
|
|
301
|
+
` [dry-run] Would update database: SET unix_group = '${expectedGroup}' WHERE repo_id = '${rawRepo.repo_id}'`
|
|
399
302
|
)
|
|
400
303
|
);
|
|
304
|
+
reposBackfilled++;
|
|
401
305
|
} else {
|
|
402
306
|
try {
|
|
403
|
-
await update(db, repos).set({ unix_group:
|
|
404
|
-
|
|
307
|
+
await update(db, repos).set({ unix_group: expectedGroup }).where(eq(repos.repo_id, rawRepo.repo_id)).run();
|
|
308
|
+
reposBackfilled++;
|
|
309
|
+
this.log(chalk.green(` \u2713 Backfilled unix_group in database`));
|
|
405
310
|
} catch (error) {
|
|
406
311
|
syncErrors++;
|
|
312
|
+
hadError = true;
|
|
407
313
|
this.log(chalk.red(` \u2717 Failed to update database: ${error}`));
|
|
408
|
-
this.log("");
|
|
409
|
-
continue;
|
|
410
314
|
}
|
|
411
315
|
}
|
|
412
|
-
|
|
413
|
-
|
|
316
|
+
}
|
|
317
|
+
if (!hadError) {
|
|
318
|
+
if (!repoPath) {
|
|
319
|
+
this.log(chalk.yellow(` \u26A0 No local_path in repo data, skipping permissions`));
|
|
320
|
+
} else if (!pathUsable) {
|
|
321
|
+
if (verbose) {
|
|
322
|
+
this.log(chalk.gray(` \u2298 Repo path missing on disk, skipping permissions`));
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
414
325
|
const gitPath = `${repoPath}/.git`;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
326
|
+
const rootCmds = UnixGroupCommands.setDirectoryGroupShallow(
|
|
327
|
+
repoPath,
|
|
328
|
+
expectedGroup,
|
|
329
|
+
REPO_GIT_PERMISSION_MODE
|
|
330
|
+
);
|
|
331
|
+
const cmds = existsSync(gitPath) ? [
|
|
332
|
+
...rootCmds,
|
|
333
|
+
...UnixGroupCommands.setDirectoryGroup(
|
|
334
|
+
gitPath,
|
|
335
|
+
expectedGroup,
|
|
336
|
+
REPO_GIT_PERMISSION_MODE
|
|
337
|
+
)
|
|
338
|
+
] : rootCmds;
|
|
339
|
+
if (await execAllCmds(cmds)) {
|
|
340
|
+
reposPermSynced++;
|
|
418
341
|
this.log(
|
|
419
|
-
chalk.
|
|
420
|
-
` [dry-run] Would run: chmod -R ${REPO_GIT_PERMISSION_MODE} "${gitPath}"`
|
|
421
|
-
)
|
|
342
|
+
chalk.green(` \u2713 Applied repo permissions (${REPO_GIT_PERMISSION_MODE})`)
|
|
422
343
|
);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
for (const cmd of UnixGroupCommands.setDirectoryGroup(
|
|
426
|
-
gitPath,
|
|
427
|
-
repoGroup,
|
|
428
|
-
REPO_GIT_PERMISSION_MODE
|
|
429
|
-
)) {
|
|
430
|
-
execSync(cmd, { stdio: "pipe" });
|
|
431
|
-
}
|
|
432
|
-
this.log(
|
|
433
|
-
chalk.green(` \u2713 Applied .git permissions (${REPO_GIT_PERMISSION_MODE})`)
|
|
434
|
-
);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
syncErrors++;
|
|
437
|
-
this.log(chalk.red(` \u2717 Failed to set .git permissions: ${error}`));
|
|
344
|
+
if (!existsSync(gitPath) && verbose) {
|
|
345
|
+
this.log(chalk.gray(` \u2298 .git path missing on disk, root traversal only`));
|
|
438
346
|
}
|
|
347
|
+
} else {
|
|
348
|
+
syncErrors++;
|
|
349
|
+
this.log(chalk.red(` \u2717 Failed to set repo permissions`));
|
|
439
350
|
}
|
|
440
|
-
} else {
|
|
441
|
-
this.log(chalk.yellow(` \u26A0 No local_path found, skipping .git permissions`));
|
|
442
351
|
}
|
|
443
|
-
reposBackfilled++;
|
|
444
|
-
this.log("");
|
|
445
|
-
}
|
|
446
|
-
this.log(chalk.bold("Repo Backfill Summary:"));
|
|
447
|
-
this.log(` Repos backfilled: ${reposBackfilled}${dryRun ? " (dry-run)" : ""}`);
|
|
448
|
-
if (syncErrors > 0) {
|
|
449
|
-
this.log(chalk.red(` Errors: ${syncErrors}`));
|
|
450
352
|
}
|
|
451
353
|
this.log("");
|
|
452
|
-
allRepos = await select(db).from(repos).all();
|
|
453
354
|
}
|
|
355
|
+
if (reposInScope.length > 0) {
|
|
356
|
+
this.log(chalk.bold("Sync Repos Summary:"));
|
|
357
|
+
this.log(` DB backfilled: ${reposBackfilled}${dryRun ? " (dry-run)" : ""}`);
|
|
358
|
+
this.log(` Permissions synced:${reposPermSynced}${dryRun ? " (dry-run)" : ""}`);
|
|
359
|
+
this.log("");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (targetWorktreeId) {
|
|
363
|
+
this.log(chalk.gray(" \u2298 Skipping user sync phase (--worktree-id mode)\n"));
|
|
364
|
+
} else if (validUsers.length === 0) {
|
|
365
|
+
this.log(chalk.yellow("No users with unix_username found in database"));
|
|
366
|
+
this.log(chalk.gray("\nTo set a unix_username for a user:"));
|
|
367
|
+
this.log(chalk.gray(" agor user update <email> --unix-username <username>\n"));
|
|
368
|
+
} else {
|
|
369
|
+
this.log(chalk.cyan(`Found ${validUsers.length} user(s) with unix_username
|
|
370
|
+
`));
|
|
371
|
+
const userIds = validUsers.map((u) => u.user_id);
|
|
372
|
+
const allOwnerships = await db.select().from(worktreeOwners).innerJoin(worktrees, eq(worktreeOwners.worktree_id, worktrees.worktree_id)).where(inArray(worktreeOwners.user_id, userIds));
|
|
373
|
+
const ownershipsByUser = /* @__PURE__ */ new Map();
|
|
374
|
+
for (const row of allOwnerships) {
|
|
375
|
+
const userId = row.worktree_owners.user_id;
|
|
376
|
+
const ownership = {
|
|
377
|
+
worktree_id: row.worktrees.worktree_id,
|
|
378
|
+
name: row.worktrees.name,
|
|
379
|
+
unix_group: row.worktrees.unix_group,
|
|
380
|
+
repo_id: row.worktrees.repo_id
|
|
381
|
+
};
|
|
382
|
+
const existing = ownershipsByUser.get(userId) || [];
|
|
383
|
+
existing.push(ownership);
|
|
384
|
+
ownershipsByUser.set(userId, existing);
|
|
385
|
+
}
|
|
386
|
+
const allRepos = await select(db).from(repos).all();
|
|
454
387
|
const repoGroupMap = /* @__PURE__ */ new Map();
|
|
455
388
|
for (const repo of allRepos) {
|
|
456
389
|
const r = repo;
|
|
@@ -472,13 +405,13 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
472
405
|
this.log(chalk.bold(`\u{1F4CB} ${user.email}`));
|
|
473
406
|
this.log(chalk.gray(` unix_username: ${user.unix_username}`));
|
|
474
407
|
this.log(chalk.gray(` user_id: ${user.user_id.substring(0, 8)}`));
|
|
475
|
-
result.unixUserExists =
|
|
408
|
+
result.unixUserExists = unixUserExists(user.unix_username);
|
|
476
409
|
if (result.unixUserExists) {
|
|
477
410
|
this.log(chalk.green(` \u2713 Unix user exists`));
|
|
478
411
|
} else {
|
|
479
412
|
this.log(chalk.red(` \u2717 Unix user does not exist`));
|
|
480
413
|
this.log(chalk.yellow(` \u2192 Creating Unix user...`));
|
|
481
|
-
if (
|
|
414
|
+
if (await execCmd(UnixUserCommands.createUser(user.unix_username))) {
|
|
482
415
|
result.unixUserCreated = true;
|
|
483
416
|
result.unixUserExists = true;
|
|
484
417
|
this.log(chalk.green(` \u2713 Unix user created`));
|
|
@@ -488,13 +421,15 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
488
421
|
}
|
|
489
422
|
}
|
|
490
423
|
if (result.unixUserExists || dryRun) {
|
|
491
|
-
result.groups.actual = result.unixUserExists ?
|
|
424
|
+
result.groups.actual = result.unixUserExists ? getUserGroups(user.unix_username) : [];
|
|
492
425
|
if (verbose && result.groups.actual.length > 0) {
|
|
493
426
|
this.log(chalk.gray(` Current groups: ${result.groups.actual.join(", ")}`));
|
|
494
427
|
}
|
|
495
428
|
if (!result.groups.actual.includes(AGOR_USERS_GROUP)) {
|
|
496
429
|
this.log(chalk.yellow(` \u2192 Adding to ${AGOR_USERS_GROUP}...`));
|
|
497
|
-
if (
|
|
430
|
+
if (await execCmd(
|
|
431
|
+
UnixGroupCommands.addUserToGroup(user.unix_username, AGOR_USERS_GROUP)
|
|
432
|
+
)) {
|
|
498
433
|
result.groups.added.push(AGOR_USERS_GROUP);
|
|
499
434
|
this.log(chalk.green(` \u2713 Added to ${AGOR_USERS_GROUP}`));
|
|
500
435
|
} else {
|
|
@@ -510,7 +445,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
510
445
|
const expectedGroup = wt.unix_group || generateWorktreeGroupName(wt.worktree_id);
|
|
511
446
|
result.groups.expected.push(expectedGroup);
|
|
512
447
|
const isInGroup = result.groups.actual.includes(expectedGroup);
|
|
513
|
-
const groupExistsOnSystem =
|
|
448
|
+
const groupExistsOnSystem = groupExists(expectedGroup);
|
|
514
449
|
if (verbose) {
|
|
515
450
|
this.log(
|
|
516
451
|
chalk.gray(
|
|
@@ -521,7 +456,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
521
456
|
let groupReady = groupExistsOnSystem;
|
|
522
457
|
if (!groupExistsOnSystem) {
|
|
523
458
|
this.log(chalk.yellow(` \u2192 Creating group ${expectedGroup}...`));
|
|
524
|
-
if (
|
|
459
|
+
if (await execCmd(UnixGroupCommands.createGroup(expectedGroup))) {
|
|
525
460
|
groupsCreated++;
|
|
526
461
|
groupReady = true;
|
|
527
462
|
this.log(chalk.green(` \u2713 Created group ${expectedGroup}`));
|
|
@@ -532,7 +467,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
532
467
|
}
|
|
533
468
|
if (groupReady && !isInGroup) {
|
|
534
469
|
this.log(chalk.yellow(` \u2192 Adding to group ${expectedGroup}...`));
|
|
535
|
-
if (
|
|
470
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(user.unix_username, expectedGroup))) {
|
|
536
471
|
result.groups.added.push(expectedGroup);
|
|
537
472
|
this.log(chalk.green(` \u2713 Added to ${expectedGroup}`));
|
|
538
473
|
} else {
|
|
@@ -541,12 +476,12 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
541
476
|
}
|
|
542
477
|
}
|
|
543
478
|
if (groupReady && daemonUser) {
|
|
544
|
-
const daemonInWtGroup = dryRun ? false :
|
|
479
|
+
const daemonInWtGroup = dryRun ? false : isUserInGroup(daemonUser, expectedGroup);
|
|
545
480
|
if (!daemonInWtGroup) {
|
|
546
481
|
this.log(
|
|
547
482
|
chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${expectedGroup}...`)
|
|
548
483
|
);
|
|
549
|
-
if (
|
|
484
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(daemonUser, expectedGroup))) {
|
|
550
485
|
daemonMembershipsAdded++;
|
|
551
486
|
this.log(chalk.green(` \u2713 Added daemon user to ${expectedGroup}`));
|
|
552
487
|
} else {
|
|
@@ -564,7 +499,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
564
499
|
const repoGroup = repoGroupMap.get(wt.repo_id) || generateRepoGroupName(wt.repo_id);
|
|
565
500
|
result.groups.expected.push(repoGroup);
|
|
566
501
|
const isInRepoGroup = result.groups.actual.includes(repoGroup);
|
|
567
|
-
const repoGroupExistsOnSystem =
|
|
502
|
+
const repoGroupExistsOnSystem = groupExists(repoGroup);
|
|
568
503
|
if (verbose) {
|
|
569
504
|
this.log(
|
|
570
505
|
chalk.gray(
|
|
@@ -575,7 +510,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
575
510
|
let repoGroupReady = repoGroupExistsOnSystem;
|
|
576
511
|
if (!repoGroupExistsOnSystem) {
|
|
577
512
|
this.log(chalk.yellow(` \u2192 Creating repo group ${repoGroup}...`));
|
|
578
|
-
if (
|
|
513
|
+
if (await execCmd(UnixGroupCommands.createGroup(repoGroup))) {
|
|
579
514
|
groupsCreated++;
|
|
580
515
|
repoGroupReady = true;
|
|
581
516
|
this.log(chalk.green(` \u2713 Created repo group ${repoGroup}`));
|
|
@@ -586,7 +521,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
586
521
|
}
|
|
587
522
|
if (repoGroupReady && !isInRepoGroup) {
|
|
588
523
|
this.log(chalk.yellow(` \u2192 Adding to repo group ${repoGroup}...`));
|
|
589
|
-
if (
|
|
524
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(user.unix_username, repoGroup))) {
|
|
590
525
|
result.groups.added.push(repoGroup);
|
|
591
526
|
this.log(chalk.green(` \u2713 Added to ${repoGroup}`));
|
|
592
527
|
} else {
|
|
@@ -595,12 +530,12 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
595
530
|
}
|
|
596
531
|
}
|
|
597
532
|
if (repoGroupReady && daemonUser) {
|
|
598
|
-
const daemonInRpGroup = dryRun ? false :
|
|
533
|
+
const daemonInRpGroup = dryRun ? false : isUserInGroup(daemonUser, repoGroup);
|
|
599
534
|
if (!daemonInRpGroup) {
|
|
600
535
|
this.log(
|
|
601
536
|
chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${repoGroup}...`)
|
|
602
537
|
);
|
|
603
|
-
if (
|
|
538
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(daemonUser, repoGroup))) {
|
|
604
539
|
daemonMembershipsAdded++;
|
|
605
540
|
this.log(chalk.green(` \u2713 Added daemon user to ${repoGroup}`));
|
|
606
541
|
} else {
|
|
@@ -616,11 +551,118 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
616
551
|
this.log("");
|
|
617
552
|
}
|
|
618
553
|
}
|
|
554
|
+
this.log(chalk.cyan.bold("\n\u2501\u2501\u2501 Sync Worktree Groups \u2501\u2501\u2501\n"));
|
|
555
|
+
const allWorktreesForBackfill = targetWorktreeId ? await select(db).from(worktrees).where(eq(worktrees.worktree_id, targetWorktreeId)).all() : await select(db).from(worktrees).all();
|
|
556
|
+
const worktreesForGroupSync = allWorktreesForBackfill.filter(
|
|
557
|
+
(wt) => !(wt.archived && wt.filesystem_status === "deleted")
|
|
558
|
+
);
|
|
559
|
+
if (worktreesForGroupSync.length === 0) {
|
|
560
|
+
this.log(chalk.yellow(" No active worktrees in scope\n"));
|
|
561
|
+
} else {
|
|
562
|
+
this.log(chalk.cyan(`Processing ${worktreesForGroupSync.length} worktree(s)
|
|
563
|
+
`));
|
|
564
|
+
for (const wt of worktreesForGroupSync) {
|
|
565
|
+
const rawWt = wt;
|
|
566
|
+
const expectedGroup = rawWt.unix_group || generateWorktreeGroupName(rawWt.worktree_id);
|
|
567
|
+
const dbNeedsBackfill = rawWt.unix_group === null;
|
|
568
|
+
const groupMissingOnSystem = !groupExists(expectedGroup);
|
|
569
|
+
if (!dbNeedsBackfill && !groupMissingOnSystem && !verbose) {
|
|
570
|
+
if (daemonUser && !isUserInGroup(daemonUser, expectedGroup)) {
|
|
571
|
+
this.log(chalk.bold(`\u{1F4C1} ${rawWt.name}`));
|
|
572
|
+
this.log(
|
|
573
|
+
chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${expectedGroup}...`)
|
|
574
|
+
);
|
|
575
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(daemonUser, expectedGroup))) {
|
|
576
|
+
daemonMembershipsAdded++;
|
|
577
|
+
this.log(chalk.green(` \u2713 Added daemon user to ${expectedGroup}
|
|
578
|
+
`));
|
|
579
|
+
} else {
|
|
580
|
+
syncErrors++;
|
|
581
|
+
this.log(chalk.red(` \u2717 Failed to add daemon user to ${expectedGroup}
|
|
582
|
+
`));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
this.log(chalk.bold(`\u{1F4C1} ${rawWt.name}`));
|
|
588
|
+
this.log(chalk.gray(` worktree_id: ${rawWt.worktree_id.substring(0, 8)}`));
|
|
589
|
+
this.log(
|
|
590
|
+
chalk.gray(` unix_group: ${expectedGroup}${dbNeedsBackfill ? " (to backfill)" : ""}`)
|
|
591
|
+
);
|
|
592
|
+
let hadError = false;
|
|
593
|
+
if (groupMissingOnSystem) {
|
|
594
|
+
this.log(chalk.yellow(` \u2192 Creating Unix group ${expectedGroup}...`));
|
|
595
|
+
if (await execCmd(UnixGroupCommands.createGroup(expectedGroup))) {
|
|
596
|
+
groupsCreated++;
|
|
597
|
+
this.log(chalk.green(` \u2713 Created Unix group ${expectedGroup}`));
|
|
598
|
+
} else {
|
|
599
|
+
syncErrors++;
|
|
600
|
+
hadError = true;
|
|
601
|
+
this.log(chalk.red(` \u2717 Failed to create Unix group ${expectedGroup}`));
|
|
602
|
+
}
|
|
603
|
+
} else if (verbose) {
|
|
604
|
+
this.log(chalk.gray(` \u2713 Unix group exists`));
|
|
605
|
+
}
|
|
606
|
+
if (!hadError && daemonUser) {
|
|
607
|
+
const daemonInGroup = dryRun ? false : isUserInGroup(daemonUser, expectedGroup);
|
|
608
|
+
if (!daemonInGroup) {
|
|
609
|
+
this.log(
|
|
610
|
+
chalk.yellow(` \u2192 Adding daemon user ${daemonUser} to ${expectedGroup}...`)
|
|
611
|
+
);
|
|
612
|
+
if (await execCmd(UnixGroupCommands.addUserToGroup(daemonUser, expectedGroup))) {
|
|
613
|
+
daemonMembershipsAdded++;
|
|
614
|
+
this.log(chalk.green(` \u2713 Added daemon user to ${expectedGroup}`));
|
|
615
|
+
} else {
|
|
616
|
+
syncErrors++;
|
|
617
|
+
this.log(chalk.red(` \u2717 Failed to add daemon user to ${expectedGroup}`));
|
|
618
|
+
}
|
|
619
|
+
} else if (verbose) {
|
|
620
|
+
this.log(chalk.gray(` \u2713 Daemon user already in ${expectedGroup}`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (!hadError && dbNeedsBackfill) {
|
|
624
|
+
if (dryRun) {
|
|
625
|
+
this.log(
|
|
626
|
+
chalk.gray(
|
|
627
|
+
` [dry-run] Would update database: SET unix_group = '${expectedGroup}' WHERE worktree_id = '${rawWt.worktree_id}'`
|
|
628
|
+
)
|
|
629
|
+
);
|
|
630
|
+
worktreesBackfilled++;
|
|
631
|
+
} else {
|
|
632
|
+
try {
|
|
633
|
+
await update(db, worktrees).set({ unix_group: expectedGroup }).where(eq(worktrees.worktree_id, rawWt.worktree_id)).run();
|
|
634
|
+
worktreesBackfilled++;
|
|
635
|
+
this.log(chalk.green(` \u2713 Backfilled unix_group in database`));
|
|
636
|
+
} catch (error) {
|
|
637
|
+
syncErrors++;
|
|
638
|
+
this.log(chalk.red(` \u2717 Failed to update database: ${error}`));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
this.log("");
|
|
643
|
+
}
|
|
644
|
+
if (worktreesBackfilled > 0 || groupsCreated > 0 || daemonMembershipsAdded > 0) {
|
|
645
|
+
this.log(chalk.bold("Sync Worktree Groups Summary:"));
|
|
646
|
+
this.log(` DB backfilled: ${worktreesBackfilled}${dryRun ? " (dry-run)" : ""}`);
|
|
647
|
+
this.log("");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
619
650
|
this.log(chalk.cyan.bold("\n\u2501\u2501\u2501 Sync Worktree Permissions \u2501\u2501\u2501\n"));
|
|
620
|
-
const allWorktreesForSync = await select(db).from(worktrees).all();
|
|
651
|
+
const allWorktreesForSync = targetWorktreeId ? await select(db).from(worktrees).where(eq(worktrees.worktree_id, targetWorktreeId)).all() : await select(db).from(worktrees).all();
|
|
621
652
|
const worktreesWithGroup = allWorktreesForSync.filter(
|
|
622
653
|
(wt) => wt.unix_group !== null
|
|
623
654
|
);
|
|
655
|
+
const allReposForWtSync = await select(db).from(repos).all();
|
|
656
|
+
const repoPathMap = /* @__PURE__ */ new Map();
|
|
657
|
+
for (const repo of allReposForWtSync) {
|
|
658
|
+
const r = repo;
|
|
659
|
+
if (r.data?.local_path) {
|
|
660
|
+
repoPathMap.set(r.repo_id, {
|
|
661
|
+
localPath: r.data.local_path,
|
|
662
|
+
defaultBranch: r.data.default_branch || "main"
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
624
666
|
if (worktreesWithGroup.length === 0) {
|
|
625
667
|
this.log(chalk.yellow("No worktrees with unix_group found\n"));
|
|
626
668
|
} else {
|
|
@@ -630,141 +672,398 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
630
672
|
const rawWorktree = wt;
|
|
631
673
|
const worktreePath = rawWorktree.data?.path;
|
|
632
674
|
if (!worktreePath) {
|
|
633
|
-
|
|
675
|
+
if (verbose) {
|
|
676
|
+
this.log(chalk.gray(` \u26A0 ${rawWorktree.name}: no path in data, skipping`));
|
|
677
|
+
}
|
|
678
|
+
worktreesSkipped++;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
const dirExists = existsSync(worktreePath);
|
|
682
|
+
const action = getWorktreeDirectoryAction(
|
|
683
|
+
dirExists,
|
|
684
|
+
rawWorktree.archived,
|
|
685
|
+
rawWorktree.filesystem_status
|
|
686
|
+
);
|
|
687
|
+
if (action === "cleanup") {
|
|
688
|
+
const wtGroup = rawWorktree.unix_group;
|
|
689
|
+
if (groupExists(wtGroup)) {
|
|
690
|
+
this.log(
|
|
691
|
+
chalk.yellow(
|
|
692
|
+
` \u{1F9F9} ${rawWorktree.name}: archived+deleted, removing group ${wtGroup}...`
|
|
693
|
+
)
|
|
694
|
+
);
|
|
695
|
+
if (await execCmd(UnixGroupCommands.deleteGroup(wtGroup))) {
|
|
696
|
+
groupsCleaned++;
|
|
697
|
+
this.log(chalk.green(` \u2713 Deleted group ${wtGroup}`));
|
|
698
|
+
} else {
|
|
699
|
+
syncErrors++;
|
|
700
|
+
this.log(chalk.red(` \u2717 Failed to delete group ${wtGroup}`));
|
|
701
|
+
}
|
|
702
|
+
} else if (verbose) {
|
|
703
|
+
this.log(
|
|
704
|
+
chalk.gray(
|
|
705
|
+
` \u2298 ${rawWorktree.name}: archived+deleted, group ${wtGroup} already gone`
|
|
706
|
+
)
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (action === "skip") {
|
|
712
|
+
if (verbose) {
|
|
713
|
+
const reason = rawWorktree.filesystem_status === "creating" ? "still creating" : rawWorktree.archived && !dirExists ? `archived (${rawWorktree.filesystem_status || "unknown"}), dir missing` : "unknown";
|
|
714
|
+
this.log(chalk.gray(` \u2298 ${rawWorktree.name}: ${reason}, skipping`));
|
|
715
|
+
}
|
|
716
|
+
worktreesSkipped++;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (action === "restore") {
|
|
720
|
+
const repoInfo = repoPathMap.get(rawWorktree.repo_id);
|
|
721
|
+
if (!repoInfo) {
|
|
722
|
+
if (verbose) {
|
|
723
|
+
this.log(
|
|
724
|
+
chalk.gray(
|
|
725
|
+
` \u2298 ${rawWorktree.name}: failed, no repo path found, skipping restore`
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
worktreesSkipped++;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const baseRef = rawWorktree.data?.base_ref || repoInfo.defaultBranch;
|
|
733
|
+
this.log(chalk.bold(`\u{1F527} ${rawWorktree.name}`));
|
|
634
734
|
this.log(chalk.gray(` worktree_id: ${rawWorktree.worktree_id.substring(0, 8)}`));
|
|
635
|
-
this.log(chalk.gray(`
|
|
636
|
-
this.log(chalk.
|
|
637
|
-
`));
|
|
735
|
+
this.log(chalk.gray(` status: failed \u2192 attempting restore`));
|
|
736
|
+
this.log(chalk.gray(` ref: ${rawWorktree.ref}, base: ${baseRef}`));
|
|
737
|
+
this.log(chalk.gray(` path: ${worktreePath}`));
|
|
738
|
+
if (dryRun) {
|
|
739
|
+
this.log(
|
|
740
|
+
chalk.gray(
|
|
741
|
+
` [dry-run] Would attempt restoreWorktreeFilesystem() for ${rawWorktree.ref} at ${worktreePath}`
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
worktreesRestored++;
|
|
745
|
+
this.log("");
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
this.log(chalk.yellow(` \u2192 Restoring worktree filesystem...`));
|
|
749
|
+
const result = await restoreWorktreeFilesystem(
|
|
750
|
+
repoInfo.localPath,
|
|
751
|
+
worktreePath,
|
|
752
|
+
rawWorktree.ref,
|
|
753
|
+
baseRef
|
|
754
|
+
);
|
|
755
|
+
if (result.success) {
|
|
756
|
+
await update(db, worktrees).set({ filesystem_status: "ready" }).where(eq(worktrees.worktree_id, rawWorktree.worktree_id)).run();
|
|
757
|
+
worktreesRestored++;
|
|
758
|
+
this.log(chalk.green(` \u2713 Restored worktree (${result.strategy}), status \u2192 ready`));
|
|
759
|
+
} else {
|
|
760
|
+
syncErrors++;
|
|
761
|
+
this.log(chalk.red(` \u2717 Failed to restore worktree: ${result.error}`));
|
|
762
|
+
}
|
|
763
|
+
this.log("");
|
|
638
764
|
continue;
|
|
639
765
|
}
|
|
640
766
|
this.log(chalk.bold(`\u{1F4C1} ${rawWorktree.name}`));
|
|
641
767
|
this.log(chalk.gray(` worktree_id: ${rawWorktree.worktree_id.substring(0, 8)}`));
|
|
642
768
|
this.log(chalk.gray(` unix_group: ${rawWorktree.unix_group}`));
|
|
643
769
|
this.log(chalk.gray(` path: ${worktreePath}`));
|
|
770
|
+
if (rawWorktree.archived) {
|
|
771
|
+
this.log(
|
|
772
|
+
chalk.gray(` archived: yes (fs: ${rawWorktree.filesystem_status || "preserved"})`)
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (action === "create") {
|
|
776
|
+
const repoInfo = repoPathMap.get(rawWorktree.repo_id);
|
|
777
|
+
if (repoInfo) {
|
|
778
|
+
const baseRef = rawWorktree.data?.base_ref || repoInfo.defaultBranch;
|
|
779
|
+
this.log(
|
|
780
|
+
chalk.yellow(
|
|
781
|
+
` \u2192 Directory missing, creating git worktree (branch: ${rawWorktree.ref}, base: ${baseRef})...`
|
|
782
|
+
)
|
|
783
|
+
);
|
|
784
|
+
if (dryRun) {
|
|
785
|
+
worktreeDirsCreated++;
|
|
786
|
+
this.log(
|
|
787
|
+
chalk.gray(
|
|
788
|
+
` [dry-run] Would run restoreWorktreeFilesystem() for ${rawWorktree.ref} at ${worktreePath}`
|
|
789
|
+
)
|
|
790
|
+
);
|
|
791
|
+
} else {
|
|
792
|
+
const result = await restoreWorktreeFilesystem(
|
|
793
|
+
repoInfo.localPath,
|
|
794
|
+
worktreePath,
|
|
795
|
+
rawWorktree.ref,
|
|
796
|
+
baseRef
|
|
797
|
+
);
|
|
798
|
+
if (result.success) {
|
|
799
|
+
worktreeDirsCreated++;
|
|
800
|
+
this.log(chalk.green(` \u2713 Created git worktree (${result.strategy})`));
|
|
801
|
+
} else {
|
|
802
|
+
this.log(
|
|
803
|
+
chalk.yellow(
|
|
804
|
+
` \u26A0 git worktree add failed (${result.error}), falling back to mkdir -p`
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
if (await execCmd(`sudo -n mkdir -p "${worktreePath}"`)) {
|
|
808
|
+
worktreeDirsCreated++;
|
|
809
|
+
this.log(chalk.green(` \u2713 Created directory (mkdir fallback)`));
|
|
810
|
+
} else {
|
|
811
|
+
syncErrors++;
|
|
812
|
+
this.log(chalk.red(` \u2717 Failed to create directory`));
|
|
813
|
+
this.log("");
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
this.log(
|
|
820
|
+
chalk.yellow(` \u2192 Directory missing, creating (no repo path for git worktree)...`)
|
|
821
|
+
);
|
|
822
|
+
if (await execCmd(`sudo -n mkdir -p "${worktreePath}"`)) {
|
|
823
|
+
worktreeDirsCreated++;
|
|
824
|
+
this.log(chalk.green(` \u2713 Created directory`));
|
|
825
|
+
} else {
|
|
826
|
+
syncErrors++;
|
|
827
|
+
this.log(chalk.red(` \u2717 Failed to create directory`));
|
|
828
|
+
this.log("");
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (action === "sync" && !rawWorktree.archived && (rawWorktree.filesystem_status === "deleted" || rawWorktree.filesystem_status === "preserved")) {
|
|
834
|
+
const gitFilePath = join(worktreePath, ".git");
|
|
835
|
+
if (existsSync(gitFilePath)) {
|
|
836
|
+
const oldStatus = rawWorktree.filesystem_status;
|
|
837
|
+
this.log(chalk.yellow(` \u2192 Fixing filesystem_status: ${oldStatus} \u2192 ready`));
|
|
838
|
+
if (!dryRun) {
|
|
839
|
+
try {
|
|
840
|
+
await update(db, worktrees).set({ filesystem_status: "ready" }).where(eq(worktrees.worktree_id, rawWorktree.worktree_id)).run();
|
|
841
|
+
this.log(
|
|
842
|
+
chalk.green(
|
|
843
|
+
` \u2713 Fixed filesystem_status: ${oldStatus} \u2192 ready for ${rawWorktree.name}`
|
|
844
|
+
)
|
|
845
|
+
);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
syncErrors++;
|
|
848
|
+
this.log(chalk.red(` \u2717 Failed to fix filesystem_status: ${error}`));
|
|
849
|
+
}
|
|
850
|
+
} else {
|
|
851
|
+
this.log(
|
|
852
|
+
chalk.gray(
|
|
853
|
+
` [dry-run] Would fix filesystem_status: ${oldStatus} \u2192 ready for ${rawWorktree.name}`
|
|
854
|
+
)
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
statusFixed++;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
644
860
|
const othersAccess = rawWorktree.others_fs_access || "read";
|
|
645
861
|
const permissionMode = getWorktreePermissionMode(othersAccess);
|
|
646
862
|
this.log(chalk.gray(` others_fs_access: ${othersAccess} \u2192 mode: ${permissionMode}`));
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
);
|
|
656
|
-
this.log("");
|
|
863
|
+
const permCmds = UnixGroupCommands.setDirectoryGroup(
|
|
864
|
+
worktreePath,
|
|
865
|
+
rawWorktree.unix_group,
|
|
866
|
+
permissionMode
|
|
867
|
+
);
|
|
868
|
+
if (await execAllCmds(permCmds)) {
|
|
869
|
+
worktreesSynced++;
|
|
870
|
+
this.log(chalk.green(` \u2713 Applied permissions (${permissionMode})`));
|
|
657
871
|
} else {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
872
|
+
syncErrors++;
|
|
873
|
+
this.log(chalk.red(` \u2717 Failed to set permissions`));
|
|
874
|
+
}
|
|
875
|
+
if (daemonUser && (dirExists || action === "create")) {
|
|
876
|
+
const aclCmds = UnixGroupCommands.setUserAcl(worktreePath, daemonUser);
|
|
877
|
+
if (await execAllCmds(aclCmds)) {
|
|
878
|
+
daemonAclsApplied++;
|
|
879
|
+
if (verbose) {
|
|
880
|
+
this.log(chalk.green(` \u2713 Applied daemon ACL for ${daemonUser}`));
|
|
665
881
|
}
|
|
666
|
-
|
|
667
|
-
this.log(chalk.green(` \u2713 Applied permissions (${permissionMode})
|
|
668
|
-
`));
|
|
669
|
-
} catch (error) {
|
|
882
|
+
} else {
|
|
670
883
|
syncErrors++;
|
|
671
|
-
this.log(chalk.red(` \u2717 Failed
|
|
672
|
-
`));
|
|
884
|
+
this.log(chalk.red(` \u2717 Failed to set daemon ACL`));
|
|
673
885
|
}
|
|
674
886
|
}
|
|
887
|
+
this.log("");
|
|
675
888
|
}
|
|
676
889
|
this.log(chalk.bold("Worktree Sync Summary:"));
|
|
677
890
|
this.log(` Worktrees synced: ${worktreesSynced}${dryRun ? " (dry-run)" : ""}`);
|
|
891
|
+
this.log(` Directories created: ${worktreeDirsCreated}${dryRun ? " (dry-run)" : ""}`);
|
|
892
|
+
this.log(` Worktrees restored: ${worktreesRestored}${dryRun ? " (dry-run)" : ""}`);
|
|
893
|
+
this.log(` Groups cleaned: ${groupsCleaned}${dryRun ? " (dry-run)" : ""}`);
|
|
894
|
+
this.log(` Status fixed: ${statusFixed}${dryRun ? " (dry-run)" : ""}`);
|
|
895
|
+
this.log(` Daemon ACLs applied: ${daemonAclsApplied}${dryRun ? " (dry-run)" : ""}`);
|
|
896
|
+
this.log(` Skipped: ${worktreesSkipped}`);
|
|
678
897
|
if (syncErrors > 0) {
|
|
679
898
|
this.log(chalk.red(` Errors: ${syncErrors}`));
|
|
680
899
|
}
|
|
681
900
|
this.log("");
|
|
682
901
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const reposWithGroup = allReposForSync.filter(
|
|
686
|
-
(r) => r.unix_group !== null
|
|
687
|
-
);
|
|
688
|
-
if (reposWithGroup.length === 0) {
|
|
689
|
-
this.log(chalk.yellow("No repos with unix_group found\n"));
|
|
902
|
+
if (targetWorktreeId) {
|
|
903
|
+
this.log(chalk.gray(" \u2298 Skipping membership pruning phase (--worktree-id mode)\n"));
|
|
690
904
|
} else {
|
|
691
|
-
this.log(chalk.cyan(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
`));
|
|
702
|
-
continue;
|
|
905
|
+
this.log(chalk.cyan.bold("\n\u2501\u2501\u2501 Prune Stale Group Memberships \u2501\u2501\u2501\n"));
|
|
906
|
+
{
|
|
907
|
+
const allWtForPrune = await select(db).from(worktrees).all();
|
|
908
|
+
const allOwnerRows = await select(db).from(worktreeOwners).all();
|
|
909
|
+
const wtGroupMap = /* @__PURE__ */ new Map();
|
|
910
|
+
for (const wt of allWtForPrune) {
|
|
911
|
+
const raw = wt;
|
|
912
|
+
if (raw.unix_group) {
|
|
913
|
+
wtGroupMap.set(raw.worktree_id, raw.unix_group);
|
|
914
|
+
}
|
|
703
915
|
}
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
916
|
+
const groupToOwnerIds = /* @__PURE__ */ new Map();
|
|
917
|
+
for (const row of allOwnerRows) {
|
|
918
|
+
const raw = row;
|
|
919
|
+
const group = wtGroupMap.get(raw.worktree_id);
|
|
920
|
+
if (group) {
|
|
921
|
+
const owners = groupToOwnerIds.get(group) || /* @__PURE__ */ new Set();
|
|
922
|
+
owners.add(raw.user_id);
|
|
923
|
+
groupToOwnerIds.set(group, owners);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
const allUsersForPrune = await select(db).from(users).all();
|
|
927
|
+
const userIdToUnixName = /* @__PURE__ */ new Map();
|
|
928
|
+
const unixNameToUserId = /* @__PURE__ */ new Map();
|
|
929
|
+
for (const u of allUsersForPrune) {
|
|
930
|
+
if (u.unix_username) {
|
|
931
|
+
userIdToUnixName.set(u.user_id, u.unix_username);
|
|
932
|
+
unixNameToUserId.set(u.unix_username, u.user_id);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
let pruneChecked = 0;
|
|
936
|
+
for (const [, group] of wtGroupMap.entries()) {
|
|
937
|
+
if (!groupExists(group)) continue;
|
|
938
|
+
pruneChecked++;
|
|
939
|
+
const ownerIds = groupToOwnerIds.get(group) || /* @__PURE__ */ new Set();
|
|
940
|
+
const expectedUsernames = /* @__PURE__ */ new Set();
|
|
941
|
+
for (const ownerId of ownerIds) {
|
|
942
|
+
const uname = userIdToUnixName.get(ownerId);
|
|
943
|
+
if (uname) expectedUsernames.add(uname);
|
|
944
|
+
}
|
|
945
|
+
if (daemonUser) expectedUsernames.add(daemonUser);
|
|
946
|
+
const actualMembers = getGroupMembers(group);
|
|
947
|
+
for (const member of actualMembers) {
|
|
948
|
+
if (expectedUsernames.has(member)) continue;
|
|
949
|
+
if (daemonUser && member === daemonUser) continue;
|
|
950
|
+
if (!unixNameToUserId.has(member)) continue;
|
|
951
|
+
this.log(chalk.yellow(` \u2192 Removing ${member} from ${group} (no longer owner)`));
|
|
952
|
+
if (await execCmd(UnixGroupCommands.removeUserFromGroup(member, group))) {
|
|
953
|
+
membershipsRemoved++;
|
|
954
|
+
this.log(chalk.green(` \u2713 Removed ${member} from ${group}`));
|
|
719
955
|
} else {
|
|
720
|
-
|
|
956
|
+
syncErrors++;
|
|
957
|
+
this.log(chalk.red(` \u2717 Failed to remove ${member} from ${group}`));
|
|
721
958
|
}
|
|
722
|
-
} else if (verbose) {
|
|
723
|
-
this.log(chalk.gray(` \u2713 Daemon user already in ${rawRepo.unix_group}`));
|
|
724
959
|
}
|
|
725
960
|
}
|
|
726
|
-
if (
|
|
961
|
+
if (membershipsRemoved === 0) {
|
|
727
962
|
this.log(
|
|
728
|
-
chalk.
|
|
729
|
-
|
|
730
|
-
this.log(
|
|
731
|
-
chalk.gray(
|
|
732
|
-
` [dry-run] Would run: chmod -R ${REPO_GIT_PERMISSION_MODE} "${gitPath}"`
|
|
733
|
-
)
|
|
963
|
+
chalk.green(` \u2713 No stale memberships found (checked ${pruneChecked} groups)
|
|
964
|
+
`)
|
|
734
965
|
);
|
|
735
|
-
this.log("");
|
|
736
966
|
} else {
|
|
967
|
+
this.log("");
|
|
968
|
+
this.log(chalk.bold("Membership Pruning Summary:"));
|
|
969
|
+
this.log(` Memberships removed: ${membershipsRemoved}${dryRun ? " (dry-run)" : ""}`);
|
|
970
|
+
this.log("");
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (targetWorktreeId) {
|
|
975
|
+
this.log(chalk.gray(" \u2298 Skipping symlink sync phase (--worktree-id mode)\n"));
|
|
976
|
+
} else if (validUsers.length > 0) {
|
|
977
|
+
this.log(chalk.cyan.bold("\n\u2501\u2501\u2501 Sync User Symlinks \u2501\u2501\u2501\n"));
|
|
978
|
+
const allWtForSymlinks = await select(db).from(worktrees).all();
|
|
979
|
+
const allOwnershipsForSymlinks = await select(db).from(worktreeOwners).all();
|
|
980
|
+
const wtInfoMap = /* @__PURE__ */ new Map();
|
|
981
|
+
for (const wt of allWtForSymlinks) {
|
|
982
|
+
const raw = wt;
|
|
983
|
+
wtInfoMap.set(raw.worktree_id, {
|
|
984
|
+
name: raw.name,
|
|
985
|
+
path: raw.data?.path,
|
|
986
|
+
archived: raw.archived,
|
|
987
|
+
filesystem_status: raw.filesystem_status
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const userToWorktrees = /* @__PURE__ */ new Map();
|
|
991
|
+
for (const row of allOwnershipsForSymlinks) {
|
|
992
|
+
const raw = row;
|
|
993
|
+
const existing = userToWorktrees.get(raw.user_id) || [];
|
|
994
|
+
existing.push(raw.worktree_id);
|
|
995
|
+
userToWorktrees.set(raw.user_id, existing);
|
|
996
|
+
}
|
|
997
|
+
for (const user of validUsers) {
|
|
998
|
+
const worktreesDir = getUserWorktreesDir(user.unix_username);
|
|
999
|
+
if (verbose) {
|
|
1000
|
+
this.log(chalk.gray(` ${user.unix_username}: checking symlinks...`));
|
|
1001
|
+
}
|
|
1002
|
+
if (!existsSync(worktreesDir)) {
|
|
1003
|
+
const setupCmds = UnixUserCommands.setupWorktreesDir(user.unix_username);
|
|
1004
|
+
if (!await execAllCmds(setupCmds)) {
|
|
1005
|
+
if (verbose) {
|
|
1006
|
+
this.log(chalk.gray(` \u26A0 Could not create ${worktreesDir}`));
|
|
1007
|
+
}
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (existsSync(worktreesDir)) {
|
|
1012
|
+
await execCmd(SymlinkCommands.removeBrokenSymlinks(worktreesDir));
|
|
1013
|
+
symlinksCleaned++;
|
|
1014
|
+
}
|
|
1015
|
+
const ownedWtIds = userToWorktrees.get(user.user_id) || [];
|
|
1016
|
+
for (const wtId of ownedWtIds) {
|
|
1017
|
+
const wtInfo = wtInfoMap.get(wtId);
|
|
1018
|
+
if (!wtInfo?.path) continue;
|
|
1019
|
+
if (wtInfo.archived && wtInfo.filesystem_status === "deleted") continue;
|
|
1020
|
+
if (!existsSync(wtInfo.path)) continue;
|
|
1021
|
+
const symlinkPath = getWorktreeSymlinkPath(user.unix_username, wtInfo.name);
|
|
1022
|
+
let needsCreate = true;
|
|
737
1023
|
try {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1024
|
+
const currentTarget = readlinkSync(symlinkPath);
|
|
1025
|
+
if (currentTarget === wtInfo.path) {
|
|
1026
|
+
needsCreate = false;
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
if (!needsCreate) continue;
|
|
1031
|
+
const symlinkCmds = SymlinkCommands.createSymlinkWithOwnership(
|
|
1032
|
+
wtInfo.path,
|
|
1033
|
+
symlinkPath,
|
|
1034
|
+
user.unix_username
|
|
1035
|
+
).map((cmd) => `sudo -n ${cmd}`);
|
|
1036
|
+
if (await execAllCmds(symlinkCmds)) {
|
|
1037
|
+
symlinksCreated++;
|
|
1038
|
+
if (verbose) {
|
|
1039
|
+
this.log(
|
|
1040
|
+
chalk.green(` \u2713 ${user.unix_username}: ${wtInfo.name} \u2192 ${wtInfo.path}`)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
} else {
|
|
1044
|
+
if (verbose) {
|
|
1045
|
+
this.log(chalk.red(` \u2717 Failed to create symlink for ${wtInfo.name}`));
|
|
744
1046
|
}
|
|
745
|
-
reposPermSynced++;
|
|
746
|
-
this.log(
|
|
747
|
-
chalk.green(` \u2713 Applied .git permissions (${REPO_GIT_PERMISSION_MODE})
|
|
748
|
-
`)
|
|
749
|
-
);
|
|
750
|
-
} catch (error) {
|
|
751
1047
|
syncErrors++;
|
|
752
|
-
this.log(chalk.red(` \u2717 Failed: ${error}
|
|
753
|
-
`));
|
|
754
1048
|
}
|
|
755
1049
|
}
|
|
756
1050
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
this.log(
|
|
1051
|
+
if (symlinksCreated > 0 || symlinksCleaned > 0) {
|
|
1052
|
+
this.log("");
|
|
1053
|
+
this.log(chalk.bold("Symlink Sync Summary:"));
|
|
1054
|
+
this.log(` Symlinks created: ${symlinksCreated}${dryRun ? " (dry-run)" : ""}`);
|
|
1055
|
+
this.log(` Users cleaned: ${symlinksCleaned}${dryRun ? " (dry-run)" : ""}`);
|
|
1056
|
+
this.log("");
|
|
1057
|
+
} else {
|
|
1058
|
+
this.log(chalk.green(" \u2713 All symlinks up to date\n"));
|
|
761
1059
|
}
|
|
762
|
-
this.log("");
|
|
763
1060
|
}
|
|
764
|
-
if (cleanupGroups || cleanupUsers) {
|
|
1061
|
+
if (targetWorktreeId && (cleanupGroups || cleanupUsers)) {
|
|
1062
|
+
this.log(chalk.gray(" \u2298 Skipping cleanup phase (--worktree-id mode)\n"));
|
|
1063
|
+
} else if (cleanupGroups || cleanupUsers) {
|
|
765
1064
|
this.log(chalk.cyan.bold("\u2501\u2501\u2501 Cleanup \u2501\u2501\u2501\n"));
|
|
766
1065
|
}
|
|
767
|
-
if (cleanupGroups) {
|
|
1066
|
+
if (cleanupGroups && !targetWorktreeId) {
|
|
768
1067
|
this.log(chalk.cyan("Checking for stale worktree groups...\n"));
|
|
769
1068
|
const allWorktrees = await select(db).from(worktrees).all();
|
|
770
1069
|
const expectedGroups = new Set(
|
|
@@ -772,7 +1071,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
772
1071
|
(wt) => wt.unix_group || generateWorktreeGroupName(wt.worktree_id)
|
|
773
1072
|
)
|
|
774
1073
|
);
|
|
775
|
-
const systemGroups =
|
|
1074
|
+
const systemGroups = listWorktreeGroups();
|
|
776
1075
|
if (verbose) {
|
|
777
1076
|
this.log(chalk.gray(` Found ${systemGroups.length} agor_wt_* group(s) on system`));
|
|
778
1077
|
this.log(chalk.gray(` Expected ${expectedGroups.size} group(s) from database`));
|
|
@@ -785,7 +1084,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
785
1084
|
`));
|
|
786
1085
|
for (const groupName of staleGroups) {
|
|
787
1086
|
this.log(chalk.yellow(` \u2192 Deleting group ${groupName}...`));
|
|
788
|
-
if (
|
|
1087
|
+
if (await execCmd(UnixGroupCommands.deleteGroup(groupName))) {
|
|
789
1088
|
groupsDeleted++;
|
|
790
1089
|
this.log(chalk.green(` \u2713 Deleted ${groupName}`));
|
|
791
1090
|
} else {
|
|
@@ -802,7 +1101,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
802
1101
|
(r) => r.unix_group || generateRepoGroupName(r.repo_id)
|
|
803
1102
|
)
|
|
804
1103
|
);
|
|
805
|
-
const systemRepoGroups =
|
|
1104
|
+
const systemRepoGroups = listRepoGroups();
|
|
806
1105
|
if (verbose) {
|
|
807
1106
|
this.log(chalk.gray(` Found ${systemRepoGroups.length} agor_rp_* group(s) on system`));
|
|
808
1107
|
this.log(chalk.gray(` Expected ${expectedRepoGroups.size} group(s) from database`));
|
|
@@ -817,7 +1116,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
817
1116
|
);
|
|
818
1117
|
for (const groupName of staleRepoGroups) {
|
|
819
1118
|
this.log(chalk.yellow(` \u2192 Deleting group ${groupName}...`));
|
|
820
|
-
if (
|
|
1119
|
+
if (await execCmd(UnixGroupCommands.deleteGroup(groupName))) {
|
|
821
1120
|
groupsDeleted++;
|
|
822
1121
|
this.log(chalk.green(` \u2713 Deleted ${groupName}`));
|
|
823
1122
|
} else {
|
|
@@ -828,12 +1127,12 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
828
1127
|
this.log("");
|
|
829
1128
|
}
|
|
830
1129
|
}
|
|
831
|
-
if (cleanupUsers) {
|
|
1130
|
+
if (cleanupUsers && !targetWorktreeId) {
|
|
832
1131
|
this.log(chalk.cyan("Checking for stale Agor users...\n"));
|
|
833
1132
|
const expectedUsers = new Set(
|
|
834
1133
|
validUsers.map((u) => u.unix_username).filter((u) => /^agor_[0-9a-f]{8}$/.test(u))
|
|
835
1134
|
);
|
|
836
|
-
const systemUsers =
|
|
1135
|
+
const systemUsers = listAgorUsers();
|
|
837
1136
|
if (verbose) {
|
|
838
1137
|
this.log(chalk.gray(` Found ${systemUsers.length} agor_* user(s) on system`));
|
|
839
1138
|
this.log(chalk.gray(` Expected ${expectedUsers.size} user(s) from database`));
|
|
@@ -847,7 +1146,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
847
1146
|
this.log(chalk.gray(" Note: Home directories will be kept\n"));
|
|
848
1147
|
for (const username of staleUsers) {
|
|
849
1148
|
this.log(chalk.yellow(` \u2192 Deleting user ${username}...`));
|
|
850
|
-
if (
|
|
1149
|
+
if (await execCmd(UnixUserCommands.deleteUser(username))) {
|
|
851
1150
|
usersDeleted++;
|
|
852
1151
|
this.log(chalk.green(` \u2713 Deleted ${username}`));
|
|
853
1152
|
} else {
|
|
@@ -869,15 +1168,28 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
869
1168
|
this.log(` Users created: ${usersCreated}${dryRunSuffix}`);
|
|
870
1169
|
this.log(` Groups created: ${groupsCreated}${dryRunSuffix}`);
|
|
871
1170
|
this.log(` Memberships added: ${groupsAdded}${dryRunSuffix}`);
|
|
1171
|
+
this.log(` Memberships removed: ${membershipsRemoved}${dryRunSuffix}`);
|
|
872
1172
|
if (daemonUser) {
|
|
873
1173
|
this.log(` Daemon memberships: ${daemonMembershipsAdded}${dryRunSuffix}`);
|
|
874
1174
|
}
|
|
875
1175
|
this.log("");
|
|
876
1176
|
this.log(chalk.bold("Filesystem Sync:"));
|
|
1177
|
+
this.log(` WT groups backfilled: ${worktreesBackfilled}${dryRunSuffix}`);
|
|
877
1178
|
this.log(` Worktrees synced: ${worktreesSynced}${dryRunSuffix}`);
|
|
1179
|
+
this.log(` Dirs created: ${worktreeDirsCreated}${dryRunSuffix}`);
|
|
1180
|
+
this.log(` Worktrees restored:${worktreesRestored}${dryRunSuffix}`);
|
|
1181
|
+
this.log(` Groups cleaned: ${groupsCleaned}${dryRunSuffix}`);
|
|
1182
|
+
this.log(` Status fixed: ${statusFixed}${dryRunSuffix}`);
|
|
1183
|
+
this.log(` Skipped: ${worktreesSkipped}`);
|
|
1184
|
+
this.log(` Daemon ACLs: ${daemonAclsApplied}${dryRunSuffix}`);
|
|
878
1185
|
this.log(` Repos backfilled: ${reposBackfilled}${dryRunSuffix}`);
|
|
879
1186
|
this.log(` Repo perms synced: ${reposPermSynced}${dryRunSuffix}`);
|
|
1187
|
+
this.log("");
|
|
1188
|
+
this.log(chalk.bold("Symlinks:"));
|
|
1189
|
+
this.log(` Created: ${symlinksCreated}${dryRunSuffix}`);
|
|
1190
|
+
this.log(` Users cleaned: ${symlinksCleaned}${dryRunSuffix}`);
|
|
880
1191
|
if (syncErrors > 0) {
|
|
1192
|
+
this.log("");
|
|
881
1193
|
this.log(chalk.red(` Sync errors: ${syncErrors}`));
|
|
882
1194
|
}
|
|
883
1195
|
if (cleanupGroups || cleanupUsers) {
|
|
@@ -894,7 +1206,7 @@ Hint: Running as root via sudo. Expected database at ~${sudoUser}/.agor/agor.db`
|
|
|
894
1206
|
this.log("");
|
|
895
1207
|
this.log(chalk.red(`Errors: ${totalErrors}`));
|
|
896
1208
|
}
|
|
897
|
-
const hasChanges = usersCreated > 0 || groupsAdded > 0 || groupsCreated > 0 || daemonMembershipsAdded > 0 || usersDeleted > 0 || groupsDeleted > 0 || worktreesSynced > 0 || reposBackfilled > 0 || reposPermSynced > 0;
|
|
1209
|
+
const hasChanges = usersCreated > 0 || groupsAdded > 0 || groupsCreated > 0 || daemonMembershipsAdded > 0 || membershipsRemoved > 0 || usersDeleted > 0 || groupsDeleted > 0 || worktreesSynced > 0 || worktreesBackfilled > 0 || worktreeDirsCreated > 0 || worktreesRestored > 0 || groupsCleaned > 0 || statusFixed > 0 || daemonAclsApplied > 0 || reposBackfilled > 0 || reposPermSynced > 0 || symlinksCreated > 0 || symlinksCleaned > 0;
|
|
898
1210
|
if (dryRun && hasChanges) {
|
|
899
1211
|
this.log(chalk.yellow("\nRun without --dry-run to apply changes"));
|
|
900
1212
|
}
|