Skip to content

Nix flake + nixosModule for deployment#12

Merged
web3dev1337 merged 5 commits intoweb3dev1337:masterfrom
ropwareJB:master
Mar 18, 2026
Merged

Nix flake + nixosModule for deployment#12
web3dev1337 merged 5 commits intoweb3dev1337:masterfrom
ropwareJB:master

Conversation

@ropwareJB
Copy link
Copy Markdown
Contributor

Creates a Nix flake and deployment module.

  • 396bb4c (user/group opts): Updated the NixOS deployment module to support explicit service identity settings by adding services.gastown-gui.user and services.gastown-gui.group options (defaulting to gastown), and switched systemd config from DynamicUser = true to fixed User/Group.
  • 30a3555 (feat: support beads binary in service path): Extended the deployment module with a new optional beadsPackage option, and added that package to the systemd service path so the beads binary is available at runtime when provided.
  • 5b1fd8c (feat: allow configuring gt binary for deployment service): Added optional gtPackage support to the deployment module and included it in the service path, enabling custom provisioning of the gt binary for the service.
  • 66422b2 (feat: add nix flake build and NixOS service module): Introduced initial Nix support by adding flake.nix/flake.lock, defining a buildNpmPackage output and default app, exporting a NixOS module (nix/deployment.nix) for services.gastown-gui, and documenting build/service usage in README.md plus references in CODEBASE_DOCUMENTATION.md.

@web3dev1337
Copy link
Copy Markdown
Owner

Thanks @ropwareJB , will check this out, sorry I didn't see it earlier!

@web3dev1337
Copy link
Copy Markdown
Owner

@codex

@web3dev1337
Copy link
Copy Markdown
Owner

@ropwareJB thanks for the PR. I completed a full review and wrote detailed notes in docs/PR12-REVIEW.md (commit e376626).

Summary:

  • Decision: Request Changes before merge.
  • Packaging direction is good (buildNpmPackage, dontNpmBuild, PUPPETEER_SKIP_DOWNLOAD usage are generally appropriate for this repo).
  • Main blocker: NixOS module sets User=gastown/Group=gastown but does not create them. On a fresh host this can fail service startup.
  • Security concern: service hardening is minimal; baseline systemd sandboxing options are missing.

Required before merge:

  1. Add managed user/group creation (or switch to a safe DynamicUser + state-dir pattern).
  2. Add baseline service hardening (NoNewPrivileges, PrivateTmp, ProtectSystem, etc.).
  3. Clarify README deployment instructions for production-safe setup.

If those items are fixed, I consider this PR legit and mergeable. I do not see an architectural reason to reject the approach.

@web3dev1337
Copy link
Copy Markdown
Owner

@ropwareJB I prepared a fix branch that addresses the review blockers (managed service user/group defaults, baseline systemd hardening, firewall option, README deployment guidance): #15

@web3dev1337
Copy link
Copy Markdown
Owner

@ropwareJB thanks for the contribution here and for iterating quickly on this. I pushed a maintainer follow-up commit to your PR branch and addressed the deployment blockers:\n\n- added managed identity defaults in (/, default user/group creation)\n- added baseline systemd hardening (, , , , capability drop, etc.)\n- added option and absolute-path assertion for \n- updated README NixOS section to document deployment options and hardened defaults\n\nI also re-ran unit tests after the patch (

gastown-gui@0.9.4 test:unit
vitest run --config vitest.unit.config.js

RUN v4.0.18 /home/ab/GitHub/tools/gastown-gui/work3

✓ test/unit/state.test.js > State Management > get() > should return undefined for unset keys 2ms
✓ test/unit/state.test.js > State Management > get() > should return empty arrays for list keys 1ms
✓ test/unit/state.test.js > State Management > setStatus() > should set status and notify subscribers 2ms
✓ test/unit/state.test.js > State Management > setStatus() > should extract agents from status 1ms
✓ test/unit/state.test.js > State Management > setConvoys() > should set convoys array 0ms
✓ test/unit/state.test.js > State Management > setConvoys() > should handle null/undefined 0ms
✓ test/unit/state.test.js > State Management > setConvoys() > should notify subscribers 0ms
✓ test/unit/state.test.js > State Management > updateConvoy() > should add new convoy to beginning 0ms
✓ test/unit/state.test.js > State Management > updateConvoy() > should update existing convoy 0ms
✓ test/unit/state.test.js > State Management > updateConvoy() > should not add convoy without id 0ms
✓ test/unit/state.test.js > State Management > setAgents() > should set agents array 0ms
✓ test/unit/state.test.js > State Management > setAgents() > should handle null/undefined 0ms
✓ test/unit/state.test.js > State Management > addEvent() > should add event to beginning of list 1ms
✓ test/unit/state.test.js > State Management > addEvent() > should add timestamp if missing 0ms
✓ test/unit/state.test.js > State Management > addEvent() > should preserve existing timestamp 0ms
✓ test/unit/state.test.js > State Management > addEvent() > should trim to max events 3ms
✓ test/unit/state.test.js > State Management > clearEvents() > should clear all events 0ms
✓ test/unit/state.test.js > State Management > clearEvents() > should notify subscribers 0ms
✓ test/unit/state.test.js > State Management > setMail() > should set mail array 0ms
✓ test/unit/state.test.js > State Management > setMail() > should handle null/undefined 0ms
✓ test/unit/state.test.js > State Management > markMailRead() > should mark mail as read 0ms
✓ test/unit/state.test.js > State Management > markMailRead() > should notify subscribers 0ms
✓ test/unit/state.test.js > State Management > markMailRead() > should do nothing for non-existent mail 0ms
✓ test/unit/state.test.js > State Management > subscribe() > should return unsubscribe function 0ms
✓ test/unit/state.test.js > State Management > subscribe() > should support multiple subscribers 0ms
✓ test/unit/state.test.js > State Management > subscribe() > should isolate subscribers by key 0ms
✓ test/unit/state.test.js > State Integration > should handle typical app initialization flow 1ms
✓ test/unit/state.test.js > State Integration > should handle real-time updates 0ms
✓ test/unit/state.test.js > State Integration > should handle activity stream 0ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should wrap simple strings in single quotes 1ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should handle empty strings 0ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should handle null 0ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should handle undefined 0ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should convert numbers to strings 0ms
✓ test/unit/quoteArg.test.js > quoteArg > basic functionality > should handle strings with spaces 0ms
✓ test/unit/quoteArg.test.js > quoteArg > special character escaping > should escape single quotes 0ms
✓ test/unit/quoteArg.test.js > quoteArg > special character escaping > should escape multiple single quotes 0ms
✓ test/unit/quoteArg.test.js > quoteArg > special character escaping > should NOT escape double quotes (safe inside single quotes) 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle backticks 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle $() subshell 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle ${} variable expansion 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle semicolons 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle pipes 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle redirects 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle newlines 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle ampersands 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle logical OR 0ms
✓ test/unit/quoteArg.test.js > quoteArg > command injection prevention > should safely handle complex injection attempt 0ms
✓ test/unit/quoteArg.test.js > quoteArg > realistic rig/agent names > should handle typical rig names 0ms
✓ test/unit/quoteArg.test.js > quoteArg > realistic rig/agent names > should handle rig names with dots 0ms
✓ test/unit/quoteArg.test.js > quoteArg > realistic rig/agent names > should handle session names 0ms
✓ test/unit/quoteArg.test.js > quoteArg > realistic rig/agent names > should handle paths 0ms
✓ test/unit/quoteArg.test.js > quoteArg > realistic rig/agent names > should handle paths with spaces 0ms
✓ test/unit/cacheRegistry.test.js > CacheRegistry > returns undefined on miss 1ms
✓ test/unit/cacheRegistry.test.js > CacheRegistry > stores and returns values until TTL expires 1ms
✓ test/unit/cacheRegistry.test.js > CacheRegistry > deduplicates concurrent getOrExecute calls 11ms
✓ test/unit/cacheRegistry.test.js > CacheRegistry > does not allow storing undefined 1ms
✓ test/unit/githubService.test.js > GitHubService > lists PRs across rigs, annotates with rig/repo, sorts, and caches 4ms
✓ test/unit/githubService.test.js > GitHubService > sorts repos by pushedAt descending 1ms
✓ test/unit/bdGateway.test.js > BDGateway > sets BEADS_DIR and cwd for exec 3ms
✓ test/unit/bdGateway.test.js > BDGateway > list() builds args and parses JSON 0ms
✓ test/unit/bdGateway.test.js > BDGateway > search() uses list when query is empty 0ms
✓ test/unit/bdGateway.test.js > BDGateway > create() builds args and extracts beadId 0ms
✓ test/unit/bdGateway.test.js > BDGateway > markDone() uses bd close with -r flag 0ms
✓ test/unit/bdGateway.test.js > BDGateway > park() uses bd defer with -r flag 0ms
✓ test/unit/bdGateway.test.js > BDGateway > release() uses bd update --status open 0ms
✓ test/unit/bdGateway.test.js > BDGateway > reassign() uses bd update --assignee 0ms
✓ test/unit/statusService.test.js > StatusService > enriches rigs with git_url and hook.running based on tmux sessions 5ms
✓ test/unit/statusService.test.js > StatusService > caches status but still refreshes when requested 1ms
✓ test/unit/statusService.test.js > StatusService > keeps rig config cached across status refresh 3ms
✓ test/unit/formattingTime.test.js > formatting time helpers > formatTimeAgoCompact returns compact relative times 4ms
✓ test/unit/formattingTime.test.js > formatting time helpers > formatTimeAgoCompact allows customizing the just-now label 1ms
✓ test/unit/formattingTime.test.js > formatting time helpers > formatTimeAgoOrDate returns compact relative time for <1 day 1ms
✓ test/unit/formattingTime.test.js > formatting time helpers > formatActivityFeedTime returns seconds for very recent events 1ms
✓ test/unit/formattingTime.test.js > formatting time helpers > formatActivityFeedTime returns minutes for events <1 hour 1ms
✓ test/unit/commandRunner.test.js > CommandRunner > exec returns ok=true for exit code 0 48ms
✓ test/unit/commandRunner.test.js > CommandRunner > exec returns ok=false for non-allowed exit codes 33ms
✓ test/unit/commandRunner.test.js > CommandRunner > exec returns ok=true for allowed non-zero exit codes 25ms
✓ test/unit/statusRoutes.test.js > Status routes (real Express app) > GET /api/status returns status JSON 32ms
✓ test/unit/statusRoutes.test.js > Status routes (real Express app) > GET /api/status?refresh=true passes refresh=true to service 10ms
✓ test/unit/targetRoutes.test.js > Target routes (real Express app) > GET /api/targets returns targets JSON 44ms
✓ test/unit/targetRoutes.test.js > Target routes (real Express app) > GET /api/targets?refresh=true passes refresh=true to service 4ms
✓ test/unit/githubRoutes.test.js > GitHub routes (real Express app) > GET /api/github/prs forwards query params 31ms
✓ test/unit/githubRoutes.test.js > GitHub routes (real Express app) > GET /api/github/pr/:repo/:number decodes repo param 12ms
✓ test/unit/githubRoutes.test.js > GitHub routes (real Express app) > GET /api/github/repos forwards limit/visibility 2ms
✓ test/unit/convoyRoutes.test.js > Convoy routes (real Express app) > GET /api/convoys forwards query params 34ms
✓ test/unit/convoyRoutes.test.js > Convoy routes (real Express app) > GET /api/convoy/:id returns convoy JSON 10ms
✓ test/unit/convoyRoutes.test.js > Convoy routes (real Express app) > POST /api/convoy returns success payload 21ms
✓ test/unit/beadRoutes.test.js > Bead routes (real Express app) > GET /api/beads forwards status 41ms
✓ test/unit/beadRoutes.test.js > Bead routes (real Express app) > GET /api/beads/search forwards q 8ms
✓ test/unit/beadRoutes.test.js > Bead routes (real Express app) > POST /api/beads returns 400 when title is missing 19ms
✓ test/unit/beadRoutes.test.js > Bead routes (real Express app) > GET /api/bead/:beadId returns 404 when missing 2ms
✓ test/unit/workRoutes.test.js > Work routes (real Express app) > POST /api/sling forwards body and returns success 53ms
✓ test/unit/workRoutes.test.js > Work routes (real Express app) > POST /api/escalate returns success 18ms
✓ test/unit/workRoutes.test.js > Work routes (real Express app) > POST /api/work/:beadId/done calls service and returns message 4ms
✓ test/unit/workRoutes.test.js > Work routes (real Express app) > POST /api/work/:beadId/reassign returns 400 when target missing 8ms
✓ test/unit/formulaRoutes.test.js > Formula routes (real Express app) > PUT /api/formula/:name returns 400 when template missing 42ms
✓ test/unit/formulaRoutes.test.js > Formula routes (real Express app) > PUT /api/formula/:name returns 404 when file missing 10ms
✓ test/unit/formulaRoutes.test.js > Formula routes (real Express app) > PUT /api/formula/:name updates existing TOML file 26ms
✓ test/unit/formulaRoutes.test.js > Formula routes (real Express app) > DELETE /api/formula/:name deletes existing TOML file 4ms
✓ test/unit/githubGateway.test.js > GitHubGateway > getDefaultBranch() calls gh api and returns branch 2ms
✓ test/unit/githubGateway.test.js > GitHubGateway > listPullRequests() builds args and parses JSON 1ms
✓ test/unit/githubGateway.test.js > GitHubGateway > viewIssue() builds args and parses JSON 0ms
✓ test/unit/commandRunner.test.js > CommandRunner > exec returns ok=false when command is missing 130ms
✓ test/unit/commandRunner.test.js > CommandRunner > exec returns ok=false when the command times out 15ms
✓ test/unit/formulaService.test.js > FormulaService > use() calls "formula run" with --rig flag 2ms
✓ test/unit/formulaService.test.js > FormulaService > use() omits --rig when target is not provided 0ms
✓ test/unit/formulaService.test.js > FormulaService > use() omits --args when args is not provided 0ms
✓ test/unit/formulaService.test.js > FormulaService > use() returns error when gt exec fails 0ms
✓ test/unit/convoyService.test.js > ConvoyService > lists convoys via gt and caches by query 3ms
✓ test/unit/convoyService.test.js > ConvoyService > creates a convoy and emits convoy_created 1ms
✓ test/unit/gtGateway.test.js > GTGateway > execs gt in gtRoot 2ms
✓ test/unit/gtGateway.test.js > GTGateway > status() builds correct args and parses JSON 0ms
✓ test/unit/gtGateway.test.js > GTGateway > status() forwards allowExitCodes to runner 0ms
✓ test/unit/gtGateway.test.js > GTGateway > listConvoys() supports all + status options 0ms
✓ test/unit/gtGateway.test.js > GTGateway > createConvoy() extracts convoyId from output 1ms
✓ test/unit/gtGateway.test.js > GTGateway > sling() builds args for target/molecule/quality/args 0ms
✓ test/unit/gtGateway.test.js > GTGateway > escalate() builds args and returns raw output 0ms
✓ test/unit/tmuxGateway.test.js > TmuxGateway > hasSession returns true when exitCode is 0 2ms
✓ test/unit/tmuxGateway.test.js > TmuxGateway > hasSession returns false when exitCode is 1 0ms
✓ test/unit/tmuxGateway.test.js > TmuxGateway > capturePane returns last N lines 0ms
✓ test/unit/tmuxGateway.test.js > TmuxGateway > killSession reports killed=false when exitCode is 1 0ms
✓ test/unit/workService.test.js > WorkService > treats sling as success when output indicates work attached 2ms
✓ test/unit/workService.test.js > WorkService > returns a typed 400 when formula is missing 0ms
✓ test/unit/workService.test.js > WorkService > maps priorities to severities for escalate 0ms
✓ test/unit/githubRepos.test.js > github repos > returns null for empty input 3ms
✓ test/unit/githubRepos.test.js > github repos > matches by bead prefix (case-insensitive) 0ms
✓ test/unit/githubRepos.test.js > github repos > matches by rig key substring 0ms
✓ test/unit/githubRepos.test.js > github repos > falls back to the first available repo 0ms
✓ test/unit/safeSegment.test.js > SafeSegment > accepts typical values 1ms
✓ test/unit/safeSegment.test.js > SafeSegment > rejects empty, dot, dotdot 1ms
✓ test/unit/safeSegment.test.js > SafeSegment > rejects path separators and spaces 0ms
✓ test/unit/htmlUtils.test.js > html utils > escapeHtml escapes HTML special characters 1ms
✓ test/unit/htmlUtils.test.js > html utils > escapeAttr escapes attribute special characters 0ms
✓ test/unit/htmlUtils.test.js > html utils > truncate truncates with ellipsis 0ms
✓ test/unit/htmlUtils.test.js > html utils > capitalize capitalizes the first character 0ms
✓ test/unit/targetService.test.js > TargetService > returns global, rig, and running agent targets 2ms
✓ test/unit/targetService.test.js > TargetService > returns global targets even when status fails 1ms
✓ test/unit/eventBus.test.js > EventBus > broadcasts emitted events when configured 1ms
✓ test/unit/eventBus.test.js > EventBus > supports typed subscriptions 1ms
✓ test/unit/eventBus.test.js > EventBus > supports onAny subscriptions 0ms
✓ test/unit/beadService.test.js > BeadService > maps UI priorities and emits bead_created 2ms
✓ test/unit/beadService.test.js > BeadService > omits default/normal priority 0ms
✓ test/unit/beadService.test.js > BeadService > returns ok=false for missing beads 1ms
✓ test/unit/agentPath.test.js > AgentPath > formats toString and toSessionName 1ms
✓ test/unit/agentPath.test.js > AgentPath > fromString parses rig/name 0ms
✓ test/unit/agentPath.test.js > AgentPath > fromString rejects invalid formats 1ms
✓ test/unit/animationsShared.test.js > animations shared > caps stagger index to MAX_STAGGER_INDEX 1ms
✓ test/unit/animationsShared.test.js > animations shared > normalizes invalid values 0ms
✓ test/unit/animationsShared.test.js > animations shared > returns the expected class 0ms
✓ test/unit/gitGateway.test.js > GitGateway > getRemoteOriginUrl() calls git -C and returns url 1ms
✓ test/unit/beadsShared.test.js > beads shared > defaults missing/invalid priorities 1ms
✓ test/unit/beadsShared.test.js > beads shared > normalizes numeric and numeric-string priorities 0ms

Test Files 31 passed (31)
Tests 153 passed (153)
Start at 09:35:16
Duration 580ms (transform 814ms, setup 0ms, import 2.05s, tests 895ms, environment 4ms): 31 files / 153 tests passing).\n\nAppreciate the solid foundation in this PR. I’m merging this now.

@web3dev1337
Copy link
Copy Markdown
Owner

@ropwareJB thank you again for the strong contribution.

Maintainer follow-up commit 64d33e5 is now on this PR branch with the requested fixes:

  • managed identity defaults in nix/deployment.nix (createUser/createGroup and default service user/group creation)
  • baseline systemd hardening (NoNewPrivileges, PrivateTmp, PrivateDevices, ProtectSystem, capability drop, etc.)
  • openFirewall option and absolute-path assertion for gtRoot
  • README NixOS deployment section updated to document these options/defaults

Validation re-run: npm run test:unit passed (31 files, 153 tests).

Proceeding with merge.

@web3dev1337 web3dev1337 merged commit a296746 into web3dev1337:master Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants