Skip to content

feat(build): generate agent-box.nix from split sources; extract settings daemon (#140)#143

Merged
defangdevs merged 2 commits into
masterfrom
feat/issue-140-split-source
Jul 22, 2026
Merged

feat(build): generate agent-box.nix from split sources; extract settings daemon (#140)#143
defangdevs merged 2 commits into
masterfrom
feat/issue-140-split-source

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Part 1 of issue #140. Splits the module source for maintainability while keeping the deployed fetched artifact a single self-contained file.

What changes

modules/agent-box.nix is now generated from modules/agent-box.nix.in + modules/src/* by bin/assemble-module.py, which re-embeds assets through @@include:...@@ markers (dedent on extract, re-indent + Nix-escape on assemble).

This slice extracts the ~1930-line settings daemon (≈45% of the file) to modules/src/settings-daemon.py — now a real file ruff/py_compile/an editor can see, instead of a Nix '' string.

What does NOT change

The deploy path is untouched. CFN user-data and agent-box-update.service still builtins.fetchurl the one modules/agent-box.nix at a pinned {rev, sha256} — same dual-stack raw.githubusercontent, same single hash, no unpack step (issue #51). I deliberately did not pursue the "multi-file fetch / tarball" alternative from the issue's second checkbox: it would reintroduce the IPv4-only + hash-fragility risk that design exists to avoid. We only decouple source layout from the fetched artifact.

This is also the fix for what sank PR #98 (readFile ./sibling, resolved on-box → 404 on next rebuild): the split is now resolved at build time, and the committed file stays sibling-free.

Byte-identity

The generated module is byte-identical to the previous hand-written one except a 4-line GENERATED banner (verified by diff), so no derivation changes and behavior is provably unchanged.

Tooling / guards

  • nix run .#assemble — regenerate in place after editing the template or src/.
  • check module-generated-up-to-date — re-runs the generator in --check mode and fails with a diff if the committed file is stale (wired into CI).
  • existing module-single-file check still guards that the OUTPUT has no sibling refs.
  • .gitattributes marks the file linguist-generated; AGENTS.md documents the workflow.

Validation

Green locally: module-single-file, module-generated-up-to-date, multi-user, and the settings-page VM test (daemon serves the page + password-change flow end-to-end).

Follow-up (part 2)

Separate PR: split the daemon's embedded CSS/JS into their own .css/.js files (unlocks eslint/prettier/stylelint), and pull the remaining shell scripts / Caddyfile / default AGENTS.md out. The generator's nested-include + per-host-escaping support is already in place for it.

🤖 Generated with Claude Code

defangdevs and others added 2 commits July 22, 2026 05:05
Add `aws/lightsail-template.yaml`, a separate (not a toggle) template that
runs the same services.agent-box on AWS Lightsail for one flat monthly
bundle price — folding compute, SSD, static IPv4, and a multi-TB transfer
allowance into a single line item (no separate EBS/IPv4 charges).

Lightsail has no NixOS blueprint and CloudFormation can't boot a custom
image, so the box launches the Ubuntu 24.04 blueprint and converts itself to
NixOS in-place with nixos-infect (pinned by commit). PROVIDER=lightsail is
first-class in nixos-infect and imports the same virtualisation/amazon-image.nix
the EC2 template uses, so the platform layer is the proven one; the baked
configuration.nix is the EC2 config minus Spot/NAT64 plus the Lightsail bits
(amazon-image import + GRUB on nvme). selfUpdate, the web-password-hash
activation script, the first-boot WaitCondition signal, and the amazon-init
disable all carry over.

Design notes:
- No VPC/subnet/IGW/SG/EIP/IAM — Lightsail manages networking; firewall is
  the Networking.Ports block (443 always, 22 when DebugSsh=true).
- A static IP is always attached (free while attached, stable across
  stop/start). It attaches after instance creation, so to avoid a dependency
  cycle the box discovers its settled public IPv4 at runtime for the
  sslip.io hostname; Outputs report the same address via GetAtt.
- Debug access is root SSH with the Lightsail default key (no SSM); infect
  logs at /var/log/agent-box-infect.log.

Validation: cfn-lint passes and the generated configuration.nix parses as
valid Nix; aws-ci.yml now lints both templates. The end-to-end nixos-infect
bootstrap still needs a live deploy-test (documented as a follow-up, along
with S3 publish + a Launch button), so this ships as a draft.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sw7j9FYnbChZJbYArxbhuo
modules/agent-box.nix is now GENERATED from modules/agent-box.nix.in +
modules/src/* by bin/assemble-module.py, which re-embeds assets via
@@include:...@@ markers. The deployed fetch is unchanged — CFN user-data and
agent-box-update.service still builtins.fetchurl the one self-contained
modules/agent-box.nix at a pinned sha256 (issue #51). We only decouple the
*source* layout from that single *fetched artifact*, so linters/editors see
real files instead of Nix strings.

First slice: extract the ~1930-line settings daemon to
modules/src/settings-daemon.py (now a lintable, py_compile-able file). The
generated module is byte-identical to the previous hand-written one except a
4-line GENERATED banner — verified by diff — so no derivation changes.

- bin/assemble-module.py: dependency-free generator; --check mode diffs the
  committed file against a fresh assembly.
- flake: `nix run .#assemble` (regenerate in place) + check
  module-generated-up-to-date (fails on drift); CI runs the check.
- module-single-file check still guards the OUTPUT is sibling-free.
- .gitattributes marks the generated file; AGENTS.md documents the workflow.

Validated: module-single-file, module-generated-up-to-date, multi-user, and
the settings-page VM test (daemon serves + password-change flow) all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sw7j9FYnbChZJbYArxbhuo
@defangdevs
defangdevs merged commit 06da87f into master Jul 22, 2026
2 checks passed
@defangdevs
defangdevs deleted the feat/issue-140-split-source branch July 22, 2026 13:45
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.

1 participant