feat: seed nix-happier with full Nix packaging#1
Conversation
Port all Nix expressions (flake, packages, NixOS module, devshell, prisma-engines-prebuilt) into a standalone repo. The happier monorepo source is now a non-flake input so packages build from the upstream source tree. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone Nix repo doesn't need the nix/ prefix — move modules/, packages/, and scripts/ to the repo root. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Follow Blueprint/sops-nix convention: - modules/nixos/ for exported NixOS modules (future: darwin/, home/) - devshell.nix and packages.nix at root (internal flake-parts wiring) - packages/ for helper derivations (prisma-engines-prebuilt) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. nix fmt uses treefmt which needs --fail-on-change, not --check 2. autoPatchelfHook is Linux-only — skip it on darwin where Prisma ships self-contained Mach-O binaries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch CI to idiomatic `nix flake check`, which builds all packages, runs linting (deadnix, statix), and on Linux runs a NixOS VM integration test for the happier-server light mode. Pin the happier input to a release tag and expose `nix run .#update-happier` as a flake app for updating it. Add automated workflows for happier release tracking (daily) and general input updates via update-flake-lock (weekly).
Happier is still in preview with no stable releases. Revert to following the default branch and remove the automated update workflows and flake app. These are preserved on the ci/auto-update-workflows branch for when official releases begin.
Package the hash updater as `apps.update-prisma-hashes` via writeShellApplication, and add `apps.update` that runs `nix flake update` followed by the hash refresh. Yarn.lock is resolved from the `happier` flake input at eval time instead of assuming a sibling directory. Uses `nix store prefetch-file` for native SRI hashes, replacing legacy `nix-prefetch-url` (base32). - Convert all 6 platform hashes to SRI format - Add `update` command to devshell - Delete scripts/update-prisma-hashes.sh
Move the happier-server light mode config into examples/ so it serves as both runnable documentation and the actual config consumed by CI.
Add README.md covering flake outputs, quick start, module options, examples, development, and repo structure. Remove yarn-based devshell commands and packages that were carried over from the happier monorepo and don't work in this repo.
Provides AI coding agents with repo context, Nix style conventions, key patterns (flake-parts, NixOS module, Prisma engine updates), verification steps, and do's/don'ts. CLAUDE.md symlinks to AGENTS.md so both Claude Code and other tools pick up the same instructions.
Add examples/happier-server-tailscale.nix showing the recommended production setup: light mode + Tailscale + nginx TLS reverse proxy with auto-renewed certs. Add a Secrets section to the README making the HANDY_MASTER_SECRET environment file requirement prominent. Restructure Quick Start to lead with the Tailscale example.
Caddy natively recognizes Tailscale URLs and auto-provisions TLS certs, eliminating the need for manual cert generation, timers, and permission fixups. This reduces the Tailscale example from ~60 lines to ~20.
Replace QEMU user-space emulation with GitHub's native ubuntu-24.04-arm runner. The NixOS VM integration test is skipped on aarch64 since GitHub ARM runners lack KVM; it continues to run in the x86_64-linux job.
Production-ready config showing PostgreSQL + Redis + MinIO with agenix secret management and Tailscale TLS via Caddy reverse proxy.
Add happier-ui-web derivation that builds the Expo web bundle as a separate package (following the lldap/Immich pattern from nixpkgs). The server wrappers set HAPPIER_SERVER_UI_DIR so the web UI is served automatically. The UI is also exposed via passthru.web for overrides.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Nix flake and supporting infra: CI workflow, devshell, checks, package builds (server/CLI and prebuilt Prisma engines), a NixOS module for services.happier-server (full/light), example configurations, and documentation/agent guidance. All changes are new files and integrations. Changes
Sequence Diagram(s)sequenceDiagram
participant Flake as "Flake / Nix build"
participant VM as "NixOS VM / systemd"
participant Postgres as "PostgreSQL"
participant Redis as "Redis"
participant MinIO as "MinIO"
participant Bucket as "MinIO Bucket Init"
participant Migrate as "Migration Service"
participant Server as "Happier Server"
Flake->>VM: Deploy system configuration & artifacts (full mode)
VM->>Postgres: Start DB service (if createLocally)
VM->>Redis: Start cache service (if createLocally)
VM->>MinIO: Start object store (if createLocally)
MinIO-->>VM: Become ready
VM->>Bucket: Run bucket initializer (one-shot)
Bucket->>MinIO: Create required bucket
Bucket-->>VM: Done
VM->>Migrate: Run migration service
Migrate->>Postgres: Apply schema migrations
Migrate-->>VM: Complete
VM->>Server: Start happier-server systemd unit
Server->>Postgres: Connect for runtime
Server->>Redis: Connect for cache
Server->>MinIO: Connect for storage
Server-->>VM: Responds healthy
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (3)
packages.nix (2)
319-320: Avoid masking server typecheck failures in package builds.Swallowing
tsc --noEmitfailures hides regressions in the packaged server path.Stricter default (with optional escape hatch if needed)
- (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit) || echo "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)" + (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 319 - 320, The packaging step currently masks TypeScript typecheck failures by appending '|| echo "WARN: tsc --noEmit had errors..."' to the tsc --noEmit invocation; remove the unconditional swallow and change the line so that tsc --noEmit fails the build by default, but allow an explicit opt-out via an environment flag (e.g., SKIP_SERVER_TYPECHECK=true) which, when set, runs tsc --noEmit and prints a clear warning if it fails; specifically replace the current command string containing "node ../../node_modules/typescript/bin/tsc --noEmit || echo ..." so that failures propagate unless SKIP_SERVER_TYPECHECK is set and handled with an if/else that logs the warning.
164-167: Consider adding--input-type=modulefor explicit ESM evaluation.While Node.js v22+ automatically detects and retries ESM syntax when CommonJS fails, explicitly declaring module mode avoids this retry overhead and clarifies intent.
Suggested improvement
- node -e " + node --input-type=module -e " const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs'); syncBundledWorkspaceDist({ repoRoot: process.cwd() }); "🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 164 - 167, The inline Node invocation that imports './apps/cli/scripts/buildSharedDeps.mjs' and calls syncBundledWorkspaceDist should explicitly run Node in ESM mode; update the node command that currently runs the eval block invoking syncBundledWorkspaceDist to include the flag --input-type=module so the import('./...buildSharedDeps.mjs') is evaluated as ESM (refer to the node -e invocation and the syncBundledWorkspaceDist import/call).flake.nix (1)
116-138: Add a preflight count check before positional hash replacement.The script assumes exactly six hashes; if layout changes, replacements can drift silently.
Preflight guard for deterministic updates
# Read current hashes in order of appearance mapfile -t CURRENT_HASHES < <(grep -oP 'Hash = "\K[^"]+' "$NIX_FILE") + if [ "${`#CURRENT_HASHES`[@]}" -ne 6 ]; then + echo "ERROR: Expected 6 hashes in $NIX_FILE, found ${`#CURRENT_HASHES`[@]}" >&2 + exit 1 + fi @@ for i in "''${!CURRENT_HASHES[@]}"; do old="''${CURRENT_HASHES[$i]}" new="''${ORDERED_NEW[$i]}" + if [ -z "$new" ]; then + echo "ERROR: Missing new hash for index $i" >&2 + exit 1 + fi if [ "$old" != "$new" ]; then🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flake.nix` around lines 116 - 138, Add a preflight guard before the replacement loop to ensure the script has exactly six hashes and the expected NEW_HASHES keys so replacements cannot drift: check the length of CURRENT_HASHES (using ${`#CURRENT_HASHES`[@]}) equals 6 and verify each required NEW_HASHES key exists (aarch64-linux_qe, aarch64-linux_se, aarch64-darwin_qe, aarch64-darwin_se, x86_64-linux_qe, x86_64-linux_se); if the check fails, print a clear error mentioning NIX_FILE and abort (exit 1) instead of proceeding to the loop that uses CURRENT_HASHES, ORDERED_NEW and the for loop over ${!CURRENT_HASHES[@]}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/nix-build.yml:
- Line 20: Update mutable action refs to immutable commit SHAs: replace uses:
actions/checkout@v4 with uses: actions/checkout@<commit-sha> and replace
DeterminateSystems/nix-installer-action@main with
DeterminateSystems/nix-installer-action@<commit-sha>; do this for every
occurrence of those two action refs in the workflow file. Locate the exact
commit SHAs from each action's upstream GitHub repo (releases/tags or the commit
you want to pin), then substitute the tag/branch with that SHA to ensure
reproducible CI.
In `@AGENTS.md`:
- Around line 11-20: The fenced code block in AGENTS.md (the repo layout block
listing flake.nix, packages.nix, etc.) is missing a language tag; update the
opening fence from ``` to ```text so the block becomes a ```text fenced code
block to satisfy markdownlint MD040.
In `@checks.nix`:
- Around line 50-52: The health check currently treats any digit string
(including curl's "000" on transport failure) as success; update the
server.succeed fallback so the curl -w '%{http_code}' output is validated
against a real HTTP status range (e.g. match three-digit codes 100–599) instead
of any digit string. Locate the server.succeed invocation that runs the two-curl
command (the line calling server.succeed with the fallback curl -w
'%{http_code}') and replace the grep pattern to only accept valid HTTP status
codes (for example, use a regex that matches ^[1-5][0-9]{2}$ or ^[1-5][0-9]{2}$
to exclude "000"). Ensure the primary -sf curl remains to succeed on normal
responses and the fallback only treats real HTTP codes as success.
In `@examples/happier-server-full.nix`:
- Around line 24-32: The example is missing the required
services.happier-server.package attribute (modules/nixos/happier-server.nix
defines package with no default); update the example to include a package value
for services.happier-server (or add a comment making it explicit that callers
must supply services.happier-server.package), e.g. reference the appropriate
derivation from your pkgs set or the same package used in the light example so
the module receives a concrete package value.
In `@examples/happier-server-tailscale.nix`:
- Around line 19-26: The example enables services.happier-server but omits the
required package wiring, which will break evaluation; update the snippet to
document and set services.happier-server.package (e.g. point it at a package
from your pkgs like pkgs.happier-server or show using
builtins.callPackage/overlay to provide the happier-server derivation) and
mention that environmentFile must contain HANDY_MASTER_SECRET (or demonstrate
using agenix/sops-nix) so users can copy the example and run it without
evaluation errors.
In `@modules/nixos/happier-server.nix`:
- Around line 179-184: The hardcoded Environment array sets DATABASE_URL to
localhost regardless of database.createLocally; update the logic that builds the
Environment for both ExecStart = "${cfg.package}/bin/happier-server-migrate" and
the runtime service (same pattern around ExecStart/services at lines referenced)
to conditionally construct DATABASE_URL: if cfg.database.createLocally is true,
keep the postgresql://${cfg.database.user}@localhost/${cfg.database.name} form,
otherwise do not inject a localhost URL and instead leave DATABASE_URL unset so
external wiring (env/secrets) can supply it (or use cfg.database.externalUrl
when provided); adjust the code that emits Environment to check
cfg.database.createLocally and/or cfg.database.externalUrl and only include the
DATABASE_URL entry when appropriate.
- Around line 91-94: The module currently allows rootCredentialsFile to default
to null while minio-bucket-init (and the full-mode setup) requires
MINIO_ROOT_USER/PASSWORD, so add an assertion in the nix module to fail fast
when an incompatible config is selected: check that rootCredentialsFile != null
whenever the option that enables minio-bucket-init or the "full" deployment mode
is set (referencing the rootCredentialsFile option and the
minio-bucket-init/full-mode enabling option names found later in the file), and
emit a clear error message instructing the user to provide a credentials file or
disable minio-bucket-init/full-mode; apply the same assertion logic for the
other related option block referenced around lines 135-148.
- Around line 111-115: The PostgreSQL auth block (authentication = lib.mkForce
'') is too permissive — change the pg_hba entries so trust is not granted to all
local users; replace "local all all trust" and the host-wide trust entries with
rules scoped to the configured database and user (e.g. "local <db_name>
<db_user> trust" or better "local <db_name> <db_user> scram-sha-256/md5") and
restrict host entries to the specific DB user and client addresses instead of
"host all all 127.0.0.1/32 trust" and "host all all ::1/128 trust"; update the
authentication block accordingly so only the intended DB/user can use trust (or
use password auth) rather than allowing all.
- Around line 214-217: The script sets DB="%S/..." which leaves %S literal so
the WAL setup may be skipped; update DB construction to use the runtime state
directory environment variable (e.g., $XDG_STATE_HOME or a provided $STATE_DIR)
and fall back to a sensible default if unset, then use that expanded variable in
the sqlite3 invocation (references: DB variable and the sqlite3 "$DB" "PRAGMA
journal_mode=WAL; PRAGMA busy_timeout=5000;"). Ensure the env var is referenced
with a $ prefix and that the resulting path is quoted, and keep the existing -f
check and sqlite3 call but pointing at the correctly expanded path.
In `@packages/prisma-engines-prebuilt.nix`:
- Around line 20-27: The current parsing of yarn.lock is brittle: ensure
engineVersionLines and prismaVersionLines are non-empty and that builtins.match
found a capture group before calling builtins.head and builtins.elemAt; if any
of these checks fail, call builtins.error with a clear message. Specifically,
after computing engineVersionLines and prismaVersionLines, assert
builtins.length engineVersionLines > 0 and builtins.length prismaVersionLines >
0, run builtins.match on the head lines and verify the match result is not null
and has the expected capture (use elemAt ... 1 to extract the first capture
group, not 0), and if any check fails, fail fast with an explicit error
mentioning the problematic pattern and the offending line.
In `@README.md`:
- Around line 177-195: The fenced code block showing the repo tree in README.md
is missing a language specifier; update the opening fence from ``` to ```text
for the repository-structure block so markdownlint MD040 passes (i.e., change
the block that starts with the tree listing to begin with ```text and close with
```). Ensure only that code fence is modified and the tree contents (the lines
with flake.nix, packages.nix, modules/, examples/, .github/, etc.) remain
unchanged.
---
Nitpick comments:
In `@flake.nix`:
- Around line 116-138: Add a preflight guard before the replacement loop to
ensure the script has exactly six hashes and the expected NEW_HASHES keys so
replacements cannot drift: check the length of CURRENT_HASHES (using
${`#CURRENT_HASHES`[@]}) equals 6 and verify each required NEW_HASHES key exists
(aarch64-linux_qe, aarch64-linux_se, aarch64-darwin_qe, aarch64-darwin_se,
x86_64-linux_qe, x86_64-linux_se); if the check fails, print a clear error
mentioning NIX_FILE and abort (exit 1) instead of proceeding to the loop that
uses CURRENT_HASHES, ORDERED_NEW and the for loop over ${!CURRENT_HASHES[@]}.
In `@packages.nix`:
- Around line 319-320: The packaging step currently masks TypeScript typecheck
failures by appending '|| echo "WARN: tsc --noEmit had errors..."' to the tsc
--noEmit invocation; remove the unconditional swallow and change the line so
that tsc --noEmit fails the build by default, but allow an explicit opt-out via
an environment flag (e.g., SKIP_SERVER_TYPECHECK=true) which, when set, runs tsc
--noEmit and prints a clear warning if it fails; specifically replace the
current command string containing "node ../../node_modules/typescript/bin/tsc
--noEmit || echo ..." so that failures propagate unless SKIP_SERVER_TYPECHECK is
set and handled with an if/else that logs the warning.
- Around line 164-167: The inline Node invocation that imports
'./apps/cli/scripts/buildSharedDeps.mjs' and calls syncBundledWorkspaceDist
should explicitly run Node in ESM mode; update the node command that currently
runs the eval block invoking syncBundledWorkspaceDist to include the flag
--input-type=module so the import('./...buildSharedDeps.mjs') is evaluated as
ESM (refer to the node -e invocation and the syncBundledWorkspaceDist
import/call).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 13f3dafc-33fb-4b5d-98e8-3b7772a45901
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
.github/workflows/nix-build.ymlAGENTS.mdCLAUDE.mdREADME.mdchecks.nixdevshell.nixexamples/happier-server-full.nixexamples/happier-server-light.nixexamples/happier-server-tailscale.nixflake.nixmodules/nixos/happier-server.nixpackages.nixpackages/prisma-engines-prebuilt.nixstatix.toml
| timeout-minutes: 60 | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/nix-build.ymlRepository: happier-dev/nix-happier
Length of output: 2212
Pin workflow actions to immutable commit SHAs instead of mutable refs.
Using version tags (@v4) or branch refs (@main) allows upstream changes to alter CI behavior without review. Pin each uses: to a commit SHA for reproducibility and security.
This workflow uses:
actions/checkout@v4(lines 20, 37, 51) — version tag is mutableDeterminateSystems/nix-installer-action@main(lines 23, 40, 54) — branch ref is explicitly mutable
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/nix-build.yml at line 20, Update mutable action refs to
immutable commit SHAs: replace uses: actions/checkout@v4 with uses:
actions/checkout@<commit-sha> and replace
DeterminateSystems/nix-installer-action@main with
DeterminateSystems/nix-installer-action@<commit-sha>; do this for every
occurrence of those two action refs in the workflow file. Locate the exact
commit SHAs from each action's upstream GitHub repo (releases/tags or the commit
you want to pin), then substitute the tag/branch with that SHA to ensure
reproducible CI.
| ``` | ||
| flake.nix # Flake entrypoint (inputs, systems, imports) | ||
| packages.nix # happier-server + happier-cli derivations | ||
| checks.nix # deadnix, statix, NixOS VM integration test | ||
| devshell.nix # Dev shell (fmt, update commands) | ||
| modules/nixos/happier-server.nix # NixOS service module | ||
| packages/prisma-engines-prebuilt.nix # Prebuilt Prisma engine binaries | ||
| examples/happier-server-tailscale.nix # Recommended production setup (Tailscale + Caddy) | ||
| examples/happier-server-light.nix # Minimal config (used by CI VM test) | ||
| ``` |
There was a problem hiding this comment.
Add a language to the fenced code block.
The repo layout block should specify a language (e.g., text) to satisfy markdownlint MD040.
Proposed fix
-```
+```text
flake.nix # Flake entrypoint (inputs, systems, imports)
packages.nix # happier-server + happier-cli derivations
checks.nix # deadnix, statix, NixOS VM integration test
devshell.nix # Dev shell (fmt, update commands)
modules/nixos/happier-server.nix # NixOS service module
packages/prisma-engines-prebuilt.nix # Prebuilt Prisma engine binaries
examples/happier-server-tailscale.nix # Recommended production setup (Tailscale + Caddy)
examples/happier-server-light.nix # Minimal config (used by CI VM test)</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 11-11: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@AGENTS.md` around lines 11 - 20, The fenced code block in AGENTS.md (the repo
layout block listing flake.nix, packages.nix, etc.) is missing a language tag;
update the opening fence from ``` to ```text so the block becomes a ```text
fenced code block to satisfy markdownlint MD040.
| # Verify server responds (any HTTP response = service is up) | ||
| server.succeed("curl -sf http://localhost:3005/ || curl -sf -o /dev/null -w '%{http_code}' http://localhost:3005/ | grep -qE '^[0-9]'") | ||
| ''; |
There was a problem hiding this comment.
Health-check can pass on transport failures (000).
The fallback command accepts any digit string, so non-HTTP failures can still pass the check.
Safer HTTP response check
- server.succeed("curl -sf http://localhost:3005/ || curl -sf -o /dev/null -w '%{http_code}' http://localhost:3005/ | grep -qE '^[0-9]'")
+ server.succeed("curl -s -o /dev/null -w '%{http_code}' http://localhost:3005/ | grep -qE '^[1-5][0-9]{2}$'")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@checks.nix` around lines 50 - 52, The health check currently treats any digit
string (including curl's "000" on transport failure) as success; update the
server.succeed fallback so the curl -w '%{http_code}' output is validated
against a real HTTP status range (e.g. match three-digit codes 100–599) instead
of any digit string. Locate the server.succeed invocation that runs the two-curl
command (the line calling server.succeed with the fallback curl -w
'%{http_code}') and replace the grep pattern to only accept valid HTTP status
codes (for example, use a regex that matches ^[1-5][0-9]{2}$ or ^[1-5][0-9]{2}$
to exclude "000"). Ensure the primary -sf curl remains to succeed on normal
responses and the fallback only treats real HTTP codes as success.
| services.happier-server = { | ||
| enable = true; | ||
| port = 3005; | ||
| mode = "full"; | ||
| # Required — must contain HANDY_MASTER_SECRET at minimum. | ||
| # Use agenix, sops-nix, or a plain file with restricted permissions. | ||
| environmentFile = config.age.secrets.happier-env.path; | ||
| minio.rootCredentialsFile = config.age.secrets.minio-credentials.path; | ||
| }; |
There was a problem hiding this comment.
services.happier-server.package is required but missing in this example.
modules/nixos/happier-server.nix defines package without a default, so this config is incomplete unless callers add it elsewhere. Please set it here or clearly document that it must be provided externally (as done in the light example).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/happier-server-full.nix` around lines 24 - 32, The example is
missing the required services.happier-server.package attribute
(modules/nixos/happier-server.nix defines package with no default); update the
example to include a package value for services.happier-server (or add a comment
making it explicit that callers must supply services.happier-server.package),
e.g. reference the appropriate derivation from your pkgs set or the same package
used in the light example so the module receives a concrete package value.
| services.happier-server = { | ||
| enable = true; | ||
| mode = "light"; | ||
| port = 3005; | ||
| # Required — must contain HANDY_MASTER_SECRET at minimum. | ||
| # Use agenix, sops-nix, or a plain file with restricted permissions. | ||
| environmentFile = "/run/secrets/happier-env"; | ||
| }; |
There was a problem hiding this comment.
Add package wiring guidance so this example is runnable.
This example enables services.happier-server but does not show how to set the required services.happier-server.package, which can cause evaluation failure when copied directly.
Suggested docs tweak in this example
services.happier-server = {
enable = true;
+ # Also set `services.happier-server.package` in your host config, e.g.
+ # inputs.self.packages.${pkgs.system}.happier-server (flake-based setups).
mode = "light";
port = 3005;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| services.happier-server = { | |
| enable = true; | |
| mode = "light"; | |
| port = 3005; | |
| # Required — must contain HANDY_MASTER_SECRET at minimum. | |
| # Use agenix, sops-nix, or a plain file with restricted permissions. | |
| environmentFile = "/run/secrets/happier-env"; | |
| }; | |
| services.happier-server = { | |
| enable = true; | |
| # Also set `services.happier-server.package` in your host config, e.g. | |
| # inputs.self.packages.${pkgs.system}.happier-server (flake-based setups). | |
| mode = "light"; | |
| port = 3005; | |
| # Required — must contain HANDY_MASTER_SECRET at minimum. | |
| # Use agenix, sops-nix, or a plain file with restricted permissions. | |
| environmentFile = "/run/secrets/happier-env"; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/happier-server-tailscale.nix` around lines 19 - 26, The example
enables services.happier-server but omits the required package wiring, which
will break evaluation; update the snippet to document and set
services.happier-server.package (e.g. point it at a package from your pkgs like
pkgs.happier-server or show using builtins.callPackage/overlay to provide the
happier-server derivation) and mention that environmentFile must contain
HANDY_MASTER_SECRET (or demonstrate using agenix/sops-nix) so users can copy the
example and run it without evaluation errors.
| authentication = lib.mkForce '' | ||
| local all all trust | ||
| host all all 127.0.0.1/32 trust | ||
| host all all ::1/128 trust | ||
| ''; |
There was a problem hiding this comment.
PostgreSQL auth is overly broad (trust for all local users).
This weakens local isolation beyond what the service needs.
Scope trust to the configured DB/user at minimum
authentication = lib.mkForce ''
- local all all trust
- host all all 127.0.0.1/32 trust
- host all all ::1/128 trust
+ local ${cfg.database.name} ${cfg.database.user} trust
+ host ${cfg.database.name} ${cfg.database.user} 127.0.0.1/32 trust
+ host ${cfg.database.name} ${cfg.database.user} ::1/128 trust
'';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@modules/nixos/happier-server.nix` around lines 111 - 115, The PostgreSQL auth
block (authentication = lib.mkForce '') is too permissive — change the pg_hba
entries so trust is not granted to all local users; replace "local all all
trust" and the host-wide trust entries with rules scoped to the configured
database and user (e.g. "local <db_name> <db_user> trust" or better "local
<db_name> <db_user> scram-sha-256/md5") and restrict host entries to the
specific DB user and client addresses instead of "host all all 127.0.0.1/32
trust" and "host all all ::1/128 trust"; update the authentication block
accordingly so only the intended DB/user can use trust (or use password auth)
rather than allowing all.
| ExecStart = "${cfg.package}/bin/happier-server-migrate"; | ||
| Environment = [ | ||
| "DATABASE_URL=postgresql://${cfg.database.user}@localhost/${cfg.database.name}" | ||
| "NODE_ENV=production" | ||
| "HOME=%S/happier-server" | ||
| ]; |
There was a problem hiding this comment.
database.createLocally = false is ignored by hardcoded local DATABASE_URL.
Both migration and runtime force localhost Postgres in full mode, preventing external DB wiring via env/secrets.
Respect `createLocally` when setting `DATABASE_URL`
if isFullMode then
{
ExecStart = "${cfg.package}/bin/happier-server-migrate";
- Environment = [
- "DATABASE_URL=postgresql://${cfg.database.user}@localhost/${cfg.database.name}"
- "NODE_ENV=production"
- "HOME=%S/happier-server"
- ];
+ Environment =
+ [
+ "NODE_ENV=production"
+ "HOME=%S/happier-server"
+ ]
+ ++ lib.optional cfg.database.createLocally
+ "DATABASE_URL=postgresql://${cfg.database.user}@localhost/${cfg.database.name}";
}
@@
Environment = [
"NODE_ENV=production"
"PORT=${toString cfg.port}"
"HOME=%S/happier-server"
]
++ lib.optionals isFullMode (
- [
- "DATABASE_URL=postgresql://${cfg.database.user}@localhost/${cfg.database.name}"
- ]
+ lib.optional cfg.database.createLocally
+ "DATABASE_URL=postgresql://${cfg.database.user}@localhost/${cfg.database.name}"
++ lib.optional cfg.redis.createLocally "REDIS_URL=redis://localhost:6379"Also applies to: 272-273
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@modules/nixos/happier-server.nix` around lines 179 - 184, The hardcoded
Environment array sets DATABASE_URL to localhost regardless of
database.createLocally; update the logic that builds the Environment for both
ExecStart = "${cfg.package}/bin/happier-server-migrate" and the runtime service
(same pattern around ExecStart/services at lines referenced) to conditionally
construct DATABASE_URL: if cfg.database.createLocally is true, keep the
postgresql://${cfg.database.user}@localhost/${cfg.database.name} form, otherwise
do not inject a localhost URL and instead leave DATABASE_URL unset so external
wiring (env/secrets) can supply it (or use cfg.database.externalUrl when
provided); adjust the code that emits Environment to check
cfg.database.createLocally and/or cfg.database.externalUrl and only include the
DATABASE_URL entry when appropriate.
| DB="%S/happier-server/.happy/server-light/happier-server-light.sqlite" | ||
| if [ -f "$DB" ]; then | ||
| sqlite3 "$DB" "PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;" | ||
| fi |
There was a problem hiding this comment.
SQLite WAL path uses %S inside a shell script, so it won’t resolve.
In this script context, %S is treated literally, so WAL setup can be skipped unintentionally.
Use the runtime state directory env var
- DB="%S/happier-server/.happy/server-light/happier-server-light.sqlite"
+ DB="$STATE_DIRECTORY/.happy/server-light/happier-server-light.sqlite"
if [ -f "$DB" ]; then
sqlite3 "$DB" "PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;"
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| DB="%S/happier-server/.happy/server-light/happier-server-light.sqlite" | |
| if [ -f "$DB" ]; then | |
| sqlite3 "$DB" "PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;" | |
| fi | |
| DB="$STATE_DIRECTORY/.happy/server-light/happier-server-light.sqlite" | |
| if [ -f "$DB" ]; then | |
| sqlite3 "$DB" "PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@modules/nixos/happier-server.nix` around lines 214 - 217, The script sets
DB="%S/..." which leaves %S literal so the WAL setup may be skipped; update DB
construction to use the runtime state directory environment variable (e.g.,
$XDG_STATE_HOME or a provided $STATE_DIR) and fall back to a sensible default if
unset, then use that expanded variable in the sqlite3 invocation (references: DB
variable and the sqlite3 "$DB" "PRAGMA journal_mode=WAL; PRAGMA
busy_timeout=5000;"). Ensure the env var is referenced with a $ prefix and that
the resulting path is quoted, and keep the existing -f check and sqlite3 call
but pointing at the correctly expanded path.
| engineVersionLines = builtins.filter ( | ||
| l: builtins.match ".*@prisma/engines-version.*" l != null | ||
| ) lines; | ||
| engineHash = builtins.elemAt (builtins.match ".*\\.([a-f0-9]+).*" (builtins.head engineVersionLines)) 0; | ||
|
|
||
| prismaVersionLines = builtins.filter (l: builtins.match "\"@prisma/engines@.*" l != null) lines; | ||
| version = builtins.elemAt (builtins.match "\"@prisma/engines@([^\"]+)\".*" (builtins.head prismaVersionLines)) 0; | ||
|
|
There was a problem hiding this comment.
Fail explicitly when yarn.lock parsing does not match expected Prisma patterns.
This currently relies on builtins.head + permissive regexes; lockfile shape drift can produce hard-to-debug eval failures or wrong commit extraction.
Robust parsing guard
- engineVersionLines = builtins.filter (
- l: builtins.match ".*@prisma/engines-version.*" l != null
- ) lines;
- engineHash = builtins.elemAt (builtins.match ".*\\.([a-f0-9]+).*" (builtins.head engineVersionLines)) 0;
+ engineVersionLines = builtins.filter (l: builtins.match ".*@prisma/engines-version.*" l != null) lines;
+ engineHashMatch =
+ if engineVersionLines == [ ] then null
+ else builtins.match ".*\\.([a-f0-9]{40}).*" (builtins.head engineVersionLines);
+ engineHash =
+ if engineHashMatch == null then
+ throw "prisma-engines-prebuilt: unable to parse 40-char Prisma engine commit hash from yarn.lock"
+ else
+ builtins.elemAt engineHashMatch 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| engineVersionLines = builtins.filter ( | |
| l: builtins.match ".*@prisma/engines-version.*" l != null | |
| ) lines; | |
| engineHash = builtins.elemAt (builtins.match ".*\\.([a-f0-9]+).*" (builtins.head engineVersionLines)) 0; | |
| prismaVersionLines = builtins.filter (l: builtins.match "\"@prisma/engines@.*" l != null) lines; | |
| version = builtins.elemAt (builtins.match "\"@prisma/engines@([^\"]+)\".*" (builtins.head prismaVersionLines)) 0; | |
| engineVersionLines = builtins.filter (l: builtins.match ".*@prisma/engines-version.*" l != null) lines; | |
| engineHashMatch = | |
| if engineVersionLines == [ ] then null | |
| else builtins.match ".*\\.([a-f0-9]{40}).*" (builtins.head engineVersionLines); | |
| engineHash = | |
| if engineHashMatch == null then | |
| throw "prisma-engines-prebuilt: unable to parse 40-char Prisma engine commit hash from yarn.lock" | |
| else | |
| builtins.elemAt engineHashMatch 0; | |
| prismaVersionLines = builtins.filter (l: builtins.match "\"@prisma/engines@.*" l != null) lines; | |
| version = builtins.elemAt (builtins.match "\"@prisma/engines@([^\"]+)\".*" (builtins.head prismaVersionLines)) 0; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/prisma-engines-prebuilt.nix` around lines 20 - 27, The current
parsing of yarn.lock is brittle: ensure engineVersionLines and
prismaVersionLines are non-empty and that builtins.match found a capture group
before calling builtins.head and builtins.elemAt; if any of these checks fail,
call builtins.error with a clear message. Specifically, after computing
engineVersionLines and prismaVersionLines, assert builtins.length
engineVersionLines > 0 and builtins.length prismaVersionLines > 0, run
builtins.match on the head lines and verify the match result is not null and has
the expected capture (use elemAt ... 1 to extract the first capture group, not
0), and if any check fails, fail fast with an explicit error mentioning the
problematic pattern and the offending line.
| ``` | ||
| . | ||
| ├── flake.nix # Flake entrypoint | ||
| ├── flake.lock | ||
| ├── packages.nix # happier-server + happier-cli derivations | ||
| ├── checks.nix # deadnix, statix, NixOS VM test | ||
| ├── devshell.nix # Dev shell with commands | ||
| ├── modules/ | ||
| │ └── nixos/ | ||
| │ └── happier-server.nix # NixOS module | ||
| ├── packages/ | ||
| │ └── prisma-engines-prebuilt.nix # Prebuilt Prisma engine binaries | ||
| ├── examples/ | ||
| │ ├── happier-server-tailscale.nix # Production setup with Tailscale + Caddy | ||
| │ └── happier-server-light.nix # Minimal config (used by CI) | ||
| └── .github/ | ||
| └── workflows/ | ||
| └── nix-build.yml # CI workflow | ||
| ``` |
There was a problem hiding this comment.
Specify a language for the repo-structure code fence.
This block should declare a language (text) to pass markdownlint MD040.
Proposed fix
-```
+```text
.
├── flake.nix # Flake entrypoint
├── flake.lock
├── packages.nix # happier-server + happier-cli derivations
├── checks.nix # deadnix, statix, NixOS VM test
├── devshell.nix # Dev shell with commands
├── modules/
│ └── nixos/
│ └── happier-server.nix # NixOS module
├── packages/
│ └── prisma-engines-prebuilt.nix # Prebuilt Prisma engine binaries
├── examples/
│ ├── happier-server-tailscale.nix # Production setup with Tailscale + Caddy
│ └── happier-server-light.nix # Minimal config (used by CI)
└── .github/
└── workflows/
└── nix-build.yml # CI workflow</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>
[warning] 177-177: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @README.md around lines 177 - 195, The fenced code block showing the repo
tree in README.md is missing a language specifier; update the opening fence from
totext for the repository-structure block so markdownlint MD040 passes
(i.e., change the block that starts with the tree listing to begin with text and close with ). Ensure only that code fence is modified and the tree
contents (the lines with flake.nix, packages.nix, modules/, examples/, .github/,
etc.) remain unchanged.
</details>
<!-- fingerprinting:phantom:poseidon:hawk -->
<!-- This is an auto-generated comment by CodeRabbit -->
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
flake.nix (2)
29-34: Extract the app outputs intoapps.nix.
packages,checks, anddevshellalready live in separate flake-parts modules, so keepingupdateandupdate-prisma-hashesinline makesflake.nixthe odd one out. Moving them into a dedicated module keeps this file focused on wiring and matches the repo's expected layout.Based on learnings, "Per-system outputs (packages, checks, devshell, apps) must be split into separate files and imported in
flake.nixusing flake-parts".Also applies to: 51-163
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flake.nix` around lines 29 - 34, The flake.nix currently inlines the "update" and "update-prisma-hashes" outputs while other per-system outputs live in separate modules; extract the app-related outputs into a new flake-parts module named apps.nix and import it from flake.nix (similar to inputs.devshell.flakeModule, ./devshell.nix, ./packages.nix, ./checks.nix). Move the definitions for the outputs labeled "update" and "update-prisma-hashes" into apps.nix, expose them as per-system outputs (apps), and update flake.nix to remove those inline blocks and add ./apps.nix to the imports list so flake-parts handles apps consistently.
140-160: Keep the scripted update path aligned with the repo's verification contract.These commands change
flake.lockand the Prisma hash file, but they stop after the mutations and only mentionnix build. Please run—or at least print as required next steps—nix fmt,nix flake check, andnix build .#happier-serverhere so the maintenance workflow matches the repo standard.Based on learnings, "Always verify changes by running
nix fmtfor formatting,nix flake checkfor linting and VM integration tests, andnix build .#happier-serverfor package builds".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flake.nix` around lines 140 - 160, The scripted update action (apps.update using pkgs.writeShellApplication named "update" and invoking .#update-prisma-hashes) currently mutates flake.lock and Prisma hashes but stops without performing the repository verification steps; modify the shell script text inside that writeShellApplication to run (or at minimum echo the required next steps) `nix fmt`, `nix flake check`, and `nix build .#happier-server` after the existing `nix run .#update-prisma-hashes` so the workflow enforces formatting, flake checks, and the build verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@flake.nix`:
- Around line 116-137: The loop that rewrites hashes using CURRENT_HASHES,
ORDERED_NEW and NEW_HASHES must validate that the number of CURRENT_HASHES
equals the expected 6 entries (or equals the length of ORDERED_NEW) before
performing any sed edits; add a guard right after mapfile that compares counts,
print a clear error including NIX_FILE and exit non‑zero if they differ to fail
fast, and (longer term) change the replacement strategy to target
queryEngineHash/schemaEngineHash inside each platform block rather than doing a
global raw-hash substitution.
---
Nitpick comments:
In `@flake.nix`:
- Around line 29-34: The flake.nix currently inlines the "update" and
"update-prisma-hashes" outputs while other per-system outputs live in separate
modules; extract the app-related outputs into a new flake-parts module named
apps.nix and import it from flake.nix (similar to inputs.devshell.flakeModule,
./devshell.nix, ./packages.nix, ./checks.nix). Move the definitions for the
outputs labeled "update" and "update-prisma-hashes" into apps.nix, expose them
as per-system outputs (apps), and update flake.nix to remove those inline blocks
and add ./apps.nix to the imports list so flake-parts handles apps consistently.
- Around line 140-160: The scripted update action (apps.update using
pkgs.writeShellApplication named "update" and invoking .#update-prisma-hashes)
currently mutates flake.lock and Prisma hashes but stops without performing the
repository verification steps; modify the shell script text inside that
writeShellApplication to run (or at minimum echo the required next steps) `nix
fmt`, `nix flake check`, and `nix build .#happier-server` after the existing
`nix run .#update-prisma-hashes` so the workflow enforces formatting, flake
checks, and the build verification.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d5cf5662-cce0-4801-8bc5-d39c4158778f
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
flake.nix
| # Read current hashes in order of appearance | ||
| mapfile -t CURRENT_HASHES < <(grep -oP 'Hash = "\K[^"]+' "$NIX_FILE") | ||
|
|
||
| # Order must match nix file: aarch64-linux QE, SE, aarch64-darwin QE, SE, x86_64-linux QE, SE | ||
| ORDERED_NEW=( | ||
| "''${NEW_HASHES[aarch64-linux_qe]}" | ||
| "''${NEW_HASHES[aarch64-linux_se]}" | ||
| "''${NEW_HASHES[aarch64-darwin_qe]}" | ||
| "''${NEW_HASHES[aarch64-darwin_se]}" | ||
| "''${NEW_HASHES[x86_64-linux_qe]}" | ||
| "''${NEW_HASHES[x86_64-linux_se]}" | ||
| ) | ||
|
|
||
| for i in "''${!CURRENT_HASHES[@]}"; do | ||
| old="''${CURRENT_HASHES[$i]}" | ||
| new="''${ORDERED_NEW[$i]}" | ||
| if [ "$old" != "$new" ]; then | ||
| sed -i "s|$old|$new|g" "$NIX_FILE" | ||
| echo " Updated: $old -> $new" | ||
| else | ||
| echo " Unchanged: $old" | ||
| fi |
There was a problem hiding this comment.
Guard the positional rewrite before it edits the source file.
packages/prisma-engines-prebuilt.nix:30-54 currently has six *Hash entries, but this loop will iterate over whatever grep finds. If that file gains another hash field or the block order changes, ORDERED_NEW[$i] is unset and Line 133 can replace a real hash with an empty string; if it matches fewer entries, stale hashes are left behind. Please fail fast on an unexpected count and, longer-term, target queryEngineHash / schemaEngineHash inside each platform block instead of replacing raw hashes globally.
🔒 Minimal guard before mutating the file
# Read current hashes in order of appearance
mapfile -t CURRENT_HASHES < <(grep -oP 'Hash = "\K[^"]+' "$NIX_FILE")
+ if [ "''${`#CURRENT_HASHES`[@]}" -ne 6 ]; then
+ echo "ERROR: Expected 6 Prisma hashes in $NIX_FILE, found ''${`#CURRENT_HASHES`[@]}" >&2
+ exit 1
+ fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flake.nix` around lines 116 - 137, The loop that rewrites hashes using
CURRENT_HASHES, ORDERED_NEW and NEW_HASHES must validate that the number of
CURRENT_HASHES equals the expected 6 entries (or equals the length of
ORDERED_NEW) before performing any sed edits; add a guard right after mapfile
that compares counts, print a clear error including NIX_FILE and exit non‑zero
if they differ to fail fast, and (longer term) change the replacement strategy
to target queryEngineHash/schemaEngineHash inside each platform block rather
than doing a global raw-hash substitution.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages.nix (2)
14-69: Consider extracting common exclusion patterns.Both
filteredSrcanduiFilteredSrcshare many exclusion patterns (.git,node_modules,dist,.pgdata,.minio,.logs,result,.project, and severalapps/andpackages/paths). This duplication could diverge over time.♻️ Optional: Extract shared exclusions
let commonExclusions = relPath: lib.hasPrefix ".git" relPath || relPath == "node_modules" || lib.hasPrefix "node_modules/" relPath || relPath == "dist" || lib.hasPrefix ".pgdata" relPath || lib.hasPrefix ".minio" relPath || lib.hasPrefix ".logs" relPath || lib.hasPrefix "result" relPath || lib.hasPrefix ".project" relPath; filteredSrc = lib.cleanSourceWith { src = happierSrc; filter = path: _type: let relPath = lib.removePrefix (toString happierSrc + "/") (toString path); in !(commonExclusions relPath || lib.hasPrefix "apps/ui" relPath # ... CLI/server-specific exclusions ); }; in ...🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 14 - 69, filteredSrc and uiFilteredSrc duplicate many exclusion checks; extract the shared checks into a single helper (e.g., commonExclusions) and call it from each filter to avoid drift. Create a function commonExclusions(relPath) containing the repeated patterns (".git", "node_modules", "dist", ".pgdata", ".minio", ".logs", "result", ".project", etc.) and then update the filter lambdas in filteredSrc and uiFilteredSrc to return !(commonExclusions relPath || <their-specific hasPrefix checks>), keeping only the unique app/package exclusions per filter.
71-75: Yarn cache hash requires manual update when yarn.lock changes.The
yarnOfflineCachehash is hardcoded and must be manually recalculated whenever the upstreamyarn.lockchanges. Consider documenting this in a comment or adding a helper toapps.update.📝 Add documentation comment
# Offline yarn cache from the root yarn.lock + # NOTE: Update this hash when yarn.lock changes upstream. + # To get new hash: nix build --impure --expr '(import <nixpkgs> {}).fetchYarnDeps { yarnLock = ./path/to/yarn.lock; hash = ""; }' + # then copy the hash from the error message. yarnOfflineCache = pkgs.fetchYarnDeps { yarnLock = "${happierSrc}/yarn.lock"; hash = "sha256-p2eG1eRiy/HjWDZ6lNgdzy9xZEo6NGXCFi7Vj1uaBX0="; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 71 - 75, The hardcoded hash for yarnOfflineCache (the fetchYarnDeps block using yarnOfflineCache and yarnLock) requires manual updates when yarn.lock changes; update the code to either add a clear comment above yarnOfflineCache explaining that the sha256 must be recalculated after any change to "${happierSrc}/yarn.lock" (and include the recommended nix-prefetch-url or nix build command to compute the new hash) or add a small helper in apps.update to automate recalculating the hash; reference the yarnOfflineCache variable and the fetchYarnDeps invocation so the maintainer knows where to add the comment or helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@flake.nix`:
- Around line 168-169: The existing comment in flake.nix incorrectly references
a non-existent script path ./scripts/update-prisma-hashes.sh; update that
comment to point to the correct update mechanism by replacing the script
reference with the nix run invocations (e.g. "nix run .#update-prisma-hashes" or
"nix run .#update") so users know to use the flake-based commands when bumping
`@prisma/client`.
In `@packages.nix`:
- Around line 315-319: The current packages.nix step runs "node ... tsc
--noEmit" and swallows errors with `|| echo`, which hides real type errors;
change it to run the tsc command and capture its exit status and stderr output,
then if it fails: (a) print a clear warning including the full tsc error output,
and (b) only proceed when an explicit escape hatch is provided (e.g., an
environment var like IGNORE_TSC_ERRORS or --ignore-tsc-errors); otherwise fail
the build. Target the tsc invocation (the "tsc --noEmit" command in this
packaging step) and add the conditional logic around that command to print
errors and respect the new ignore flag.
---
Nitpick comments:
In `@packages.nix`:
- Around line 14-69: filteredSrc and uiFilteredSrc duplicate many exclusion
checks; extract the shared checks into a single helper (e.g., commonExclusions)
and call it from each filter to avoid drift. Create a function
commonExclusions(relPath) containing the repeated patterns (".git",
"node_modules", "dist", ".pgdata", ".minio", ".logs", "result", ".project",
etc.) and then update the filter lambdas in filteredSrc and uiFilteredSrc to
return !(commonExclusions relPath || <their-specific hasPrefix checks>), keeping
only the unique app/package exclusions per filter.
- Around line 71-75: The hardcoded hash for yarnOfflineCache (the fetchYarnDeps
block using yarnOfflineCache and yarnLock) requires manual updates when
yarn.lock changes; update the code to either add a clear comment above
yarnOfflineCache explaining that the sha256 must be recalculated after any
change to "${happierSrc}/yarn.lock" (and include the recommended
nix-prefetch-url or nix build command to compute the new hash) or add a small
helper in apps.update to automate recalculating the hash; reference the
yarnOfflineCache variable and the fetchYarnDeps invocation so the maintainer
knows where to add the comment or helper.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a95fadab-b8ac-40c6-acf9-52e476b7b218
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
flake.nixpackages.nix
| # Prebuilt Prisma engines — version auto-derived from yarn.lock. | ||
| # When @prisma/client is bumped, run: ./scripts/update-prisma-hashes.sh |
There was a problem hiding this comment.
Stale comment references non-existent script path.
The comment mentions ./scripts/update-prisma-hashes.sh but the actual update mechanism is nix run .#update-prisma-hashes (or nix run .#update).
📝 Fix the comment
# Prebuilt Prisma engines — version auto-derived from yarn.lock.
- # When `@prisma/client` is bumped, run: ./scripts/update-prisma-hashes.sh
+ # When `@prisma/client` is bumped, run: nix run .#update-prisma-hashes📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Prebuilt Prisma engines — version auto-derived from yarn.lock. | |
| # When @prisma/client is bumped, run: ./scripts/update-prisma-hashes.sh | |
| # Prebuilt Prisma engines — version auto-derived from yarn.lock. | |
| # When `@prisma/client` is bumped, run: nix run .#update-prisma-hashes |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flake.nix` around lines 168 - 169, The existing comment in flake.nix
incorrectly references a non-existent script path
./scripts/update-prisma-hashes.sh; update that comment to point to the correct
update mechanism by replacing the script reference with the nix run invocations
(e.g. "nix run .#update-prisma-hashes" or "nix run .#update") so users know to
use the flake-based commands when bumping `@prisma/client`.
| # Typecheck directly to avoid prebuild re-running buildSharedDeps. | ||
| # Note: prisma-json-types-generator patches @prisma/client types in-place; | ||
| # if the patch silently fails in the sandbox, PrismaJson types won't resolve. | ||
| # The server runs via tsx at runtime so this is a validation-only step. | ||
| (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit) || echo "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)" |
There was a problem hiding this comment.
Silently swallowing tsc errors may hide real type issues.
The || echo "WARN:..." pattern allows the build to succeed even when TypeScript compilation fails. While the comment explains the rationale (tsx runtime), this could mask genuine type errors that indicate runtime bugs.
Consider either:
- Logging the actual error output before the warning
- Failing the build but providing a
--ignore-tsc-errorsescape hatch
📝 Capture and display the error
- (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit) || echo "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)"
+ if ! (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit 2>&1); then
+ echo "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)"
+ echo " Review output above. Server will still run via tsx."
+ fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages.nix` around lines 315 - 319, The current packages.nix step runs
"node ... tsc --noEmit" and swallows errors with `|| echo`, which hides real
type errors; change it to run the tsc command and capture its exit status and
stderr output, then if it fails: (a) print a clear warning including the full
tsc error output, and (b) only proceed when an explicit escape hatch is provided
(e.g., an environment var like IGNORE_TSC_ERRORS or --ignore-tsc-errors);
otherwise fail the build. Target the tsc invocation (the "tsc --noEmit" command
in this packaging step) and add the conditional logic around that command to
print errors and respect the new ignore flag.
The dev branch introduced transfers, connection-supervisor, and release-runtime as dependencies of cli-common and apps/cli. Fix the build order (release-runtime before cli-common) and add build + install steps for the two new packages.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages.nix (1)
333-337:⚠️ Potential issue | 🟡 MinorDon't make server typecheck failures unconditional warnings.
This still lets the package build succeed when
apps/serverno longer typechecks, including the Prisma type-patch case called out in the comment. Please fail by default and only continue behind an explicit escape hatch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 333 - 337, The build currently swallows TypeScript errors by appending `|| echo "WARN: tsc --noEmit had errors..."`; change this so the `(cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit)` failure causes the build to fail by default, and only allow continuing when an explicit escape hatch (e.g., an environment variable like SKIP_SERVER_TYPECHECK=1 or a CI flag) is set; update the shell logic that runs the tsc command to check the escape-hatch variable and exit non-zero on tsc errors unless that variable is present.
🧹 Nitpick comments (3)
packages.nix (3)
398-425: Make the server runtime env contract explicit at the module boundary.
modules/nixos/happier-server.nix:260-298does not modelHAPPIER_SERVER_UI_DIRorLD_LIBRARY_PATH, soservices.happier-server.packagecurrently works because these wrappers hard-set them. That makes package overrides fragile and hides which env is actually required for the service to boot. Consider moving those values into the module, or at least using--set-defaultfor the UI dir so module/user overrides are not silently ignored.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 398 - 425, The wrapper currently hard-sets HAPPIER_SERVER_UI_DIR and LD_LIBRARY_PATH in the makeWrapper invocations (see the makeWrapper for happier-server / happier-server-light and the PRISMA_* envs), which hides the runtime contract from the NixOS module; update the module (modules/nixos/happier-server.nix) to explicitly expose HAPPIER_SERVER_UI_DIR and LD_LIBRARY_PATH as configurable options under services.happier-server.package, and modify the makeWrapper calls to use --set-default for HAPPIER_SERVER_UI_DIR (and avoid hard-forcing LD_LIBRARY_PATH; prefer --prefix or --set-default so user/module overrides take effect) so package overrides are not silently ignored and the service env is modeled at the module boundary.
1-77: Trim the narrating section comments.Most of these headers restate the next expression instead of the packaging constraint behind it. Keeping only the non-obvious rationale would make the file easier to scan. As per coding guidelines, "Comments should explain why, not what — Nix expressions are usually self-documenting."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 1 - 77, The top-of-file narrating comments are redundant and restate what the expressions do; trim or remove those "what" comments and keep only concise "why" rationale where non-obvious (e.g., for perSystem, filteredSrc, uiFilteredSrc, yarnOfflineCache and the pre-built web UI bundle). Locate the large header/comment blocks around the let-bound symbols filteredSrc and uiFilteredSrc and replace them with short notes that explain the reasoning behind the filters (why certain paths are excluded), remove superfluous sentence-style narration for perSystem and yarnOfflineCache, and preserve any comments that document unusual constraints (like the yarn lock hash or offline cache rationale).
154-163: Drive the CLI workspace steps from one list.The same workspace set is encoded in the compile order, the
mkdir -pblock, and the repeated copy sections. The recent dev-branch package additions already required synchronized edits in multiple places; the next workspace change will be easy to miss in one phase.Also applies to: 183-247
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 154 - 163, The compile/mkdir/copy steps duplicate the same workspace package list (see the protocol codegen step generate-embedded-feature-policies.mjs and the repeated node node_modules/typescript/bin/tsc -p packages/* lines), making future workspace changes error-prone; refactor by extracting a single canonical list/array of workspace package paths (e.g., packages/protocol, packages/agents, packages/release-runtime, packages/transfers, packages/connection-supervisor, packages/cli-common) and drive the compile loop, the mkdir -p block, and all copy phases by iterating that list (handle protocol’s special codegen step as a conditional for the protocol entry), so edits to workspace membership are made in one place and reused by build, mkdir, and copy logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages.nix`:
- Around line 166-169: The node -e snippet uses top-level await which fails
outside ESM; replace it with an async IIFE that dynamically imports
'./apps/cli/scripts/buildSharedDeps.mjs', calls syncBundledWorkspaceDist({
repoRoot: process.cwd() }) and catches/logs any error before exiting; locate the
snippet that references syncBundledWorkspaceDist and change it to an
immediately-invoked async function with try/catch to ensure correct module
import and error handling.
---
Duplicate comments:
In `@packages.nix`:
- Around line 333-337: The build currently swallows TypeScript errors by
appending `|| echo "WARN: tsc --noEmit had errors..."`; change this so the `(cd
apps/server && node ../../node_modules/typescript/bin/tsc --noEmit)` failure
causes the build to fail by default, and only allow continuing when an explicit
escape hatch (e.g., an environment variable like SKIP_SERVER_TYPECHECK=1 or a CI
flag) is set; update the shell logic that runs the tsc command to check the
escape-hatch variable and exit non-zero on tsc errors unless that variable is
present.
---
Nitpick comments:
In `@packages.nix`:
- Around line 398-425: The wrapper currently hard-sets HAPPIER_SERVER_UI_DIR and
LD_LIBRARY_PATH in the makeWrapper invocations (see the makeWrapper for
happier-server / happier-server-light and the PRISMA_* envs), which hides the
runtime contract from the NixOS module; update the module
(modules/nixos/happier-server.nix) to explicitly expose HAPPIER_SERVER_UI_DIR
and LD_LIBRARY_PATH as configurable options under
services.happier-server.package, and modify the makeWrapper calls to use
--set-default for HAPPIER_SERVER_UI_DIR (and avoid hard-forcing LD_LIBRARY_PATH;
prefer --prefix or --set-default so user/module overrides take effect) so
package overrides are not silently ignored and the service env is modeled at the
module boundary.
- Around line 1-77: The top-of-file narrating comments are redundant and restate
what the expressions do; trim or remove those "what" comments and keep only
concise "why" rationale where non-obvious (e.g., for perSystem, filteredSrc,
uiFilteredSrc, yarnOfflineCache and the pre-built web UI bundle). Locate the
large header/comment blocks around the let-bound symbols filteredSrc and
uiFilteredSrc and replace them with short notes that explain the reasoning
behind the filters (why certain paths are excluded), remove superfluous
sentence-style narration for perSystem and yarnOfflineCache, and preserve any
comments that document unusual constraints (like the yarn lock hash or offline
cache rationale).
- Around line 154-163: The compile/mkdir/copy steps duplicate the same workspace
package list (see the protocol codegen step
generate-embedded-feature-policies.mjs and the repeated node
node_modules/typescript/bin/tsc -p packages/* lines), making future workspace
changes error-prone; refactor by extracting a single canonical list/array of
workspace package paths (e.g., packages/protocol, packages/agents,
packages/release-runtime, packages/transfers, packages/connection-supervisor,
packages/cli-common) and drive the compile loop, the mkdir -p block, and all
copy phases by iterating that list (handle protocol’s special codegen step as a
conditional for the protocol entry), so edits to workspace membership are made
in one place and reused by build, mkdir, and copy logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| node -e " | ||
| const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs'); | ||
| syncBundledWorkspaceDist({ repoRoot: process.cwd() }); | ||
| " |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Node.js 22, does node -esupport top-levelawaitwithout--input-type=module?
💡 Result:
No. In Node.js 22, code passed via node -e/--eval is not treated as an ES module by default, so top-level await won’t work unless you opt into ESM evaluation (e.g. --input-type=module, or by changing the default type via --experimental-default-type=module). Top-level await is only allowed at the top level of an ECMAScript module. [1] [2]
Avoid top-level await in the node -e snippet.
Code passed to node -e is not treated as an ES module by default. Top-level await only works in ESM contexts, so this will fail with a syntax error before syncBundledWorkspaceDist executes. Wrap the code in an async IIFE with error handling.
🔧 Safer pattern
- node -e "
- const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs');
- syncBundledWorkspaceDist({ repoRoot: process.cwd() });
- "
+ node -e "
+ (async () => {
+ const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs');
+ syncBundledWorkspaceDist({ repoRoot: process.cwd() });
+ })().catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
+ "📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| node -e " | |
| const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs'); | |
| syncBundledWorkspaceDist({ repoRoot: process.cwd() }); | |
| " | |
| node -e " | |
| (async () => { | |
| const { syncBundledWorkspaceDist } = await import('./apps/cli/scripts/buildSharedDeps.mjs'); | |
| syncBundledWorkspaceDist({ repoRoot: process.cwd() }); | |
| })().catch((error) => { | |
| console.error(error); | |
| process.exit(1); | |
| }); | |
| " |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages.nix` around lines 166 - 169, The node -e snippet uses top-level
await which fails outside ESM; replace it with an async IIFE that dynamically
imports './apps/cli/scripts/buildSharedDeps.mjs', calls
syncBundledWorkspaceDist({ repoRoot: process.cwd() }) and catches/logs any error
before exiting; locate the snippet that references syncBundledWorkspaceDist and
change it to an immediately-invoked async function with try/catch to ensure
correct module import and error handling.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages.nix (1)
333-337:⚠️ Potential issue | 🟡 MinorDon’t let server typecheck failures silently pass.
This still converts
tsc --noEmitfailures into a successful build, so packaging can ship TypeScript regressions. If this must remain non-fatal, gate it behind an explicit opt-out and print the captured compiler output.🛠️ Proposed adjustment
- (cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit) || echo "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)" + if ! tsc_output=$(cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit 2>&1); then + printf '%s\n' "$tsc_output" + if [ -z "''${IGNORE_TSC_ERRORS:-}" ]; then + echo "ERROR: tsc --noEmit failed. Set IGNORE_TSC_ERRORS=1 only if this is intentionally non-fatal." + exit 1 + fi + echo "WARN: tsc --noEmit failed, continuing because IGNORE_TSC_ERRORS=1" + fiRun the existing verification before merging:
#!/bin/bash # Description: Verify the Nix package still formats and passes repository checks. set -eu nix fmt -- --check nix flake checkBased on learnings, run
nix fmtafter every change and runnix flake checkbefore considering work done.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages.nix` around lines 333 - 337, The current packaging step masks TypeScript errors by piping the tsc invocation to a harmless echo; change the invocation that runs "(cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit)" so that failures are fatal by default (do not append "|| echo ..."), and if you must keep non-fatal behavior introduce an explicit opt-out environment variable (e.g., SKIP_SERVER_TSC) that when set will run the tsc command but capture and print its stdout/stderr before exiting successfully; update the command reference string and the fallback message "WARN: tsc --noEmit had errors (non-fatal for tsx runtime)" to instead print the captured compiler output when opt-out is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages.nix`:
- Around line 333-337: The current packaging step masks TypeScript errors by
piping the tsc invocation to a harmless echo; change the invocation that runs
"(cd apps/server && node ../../node_modules/typescript/bin/tsc --noEmit)" so
that failures are fatal by default (do not append "|| echo ..."), and if you
must keep non-fatal behavior introduce an explicit opt-out environment variable
(e.g., SKIP_SERVER_TSC) that when set will run the tsc command but capture and
print its stdout/stderr before exiting successfully; update the command
reference string and the fallback message "WARN: tsc --noEmit had errors
(non-fatal for tsx runtime)" to instead print the captured compiler output when
opt-out is used.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c37c6ce3-0cae-48bb-b492-d82cbe5c747f
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
packages.nix
Self-hosted users can now point the CLI at their own server instead of the upstream cloud API. The wrapper bakes HAPPIER_SERVER_URL/WEBAPP_URL env vars into the binary; CLI args (--server-url) still take precedence.
Summary
Initial content for the nix-happier repo — a Nix flake that builds and deploys Happier Server and CLI.
happier-serverandhappier-clibuilt from the happier monorepo sourcehappier-ui-webderivation (Expo static export) bundled with the server viaHAPPIER_SERVER_UI_DIRservices.happier-server) supporting full mode (PostgreSQL + Redis + MinIO) and light mode (SQLite-only)nix run .#update-prisma-hashesfor updatesnix flake checkTest plan
nix fmtpassesnix flake checkpasses (deadnix, statix, VM integration test on x86_64-linux)nix build .#happier-serversucceeds with bundled web UInix build .#happier-clisucceedsSummary by CodeRabbit
Documentation
New Features
CI/CD
Developer tooling
Tests