feat: seed nix-happier with full Nix packaging#1
feat: seed nix-happier with full Nix packaging#1das-monki wants to merge 19 commits intohappier-dev:mainfrom
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.
📝 WalkthroughWalkthroughThis PR introduces a comprehensive Nix flake-based infrastructure for the nix-happier project, including CI/CD workflows, a NixOS module for deploying Happier Server with optional services (PostgreSQL, Redis, MinIO), multi-mode example configurations, development environment setup, and packaging scripts for CLI and server components with Prisma integration support. Changes
Sequence Diagram(s)sequenceDiagram
participant NixOS as NixOS System
participant Systemd as systemd
participant Migrate as Migration Service
participant MinIO as MinIO Service
participant BucketInit as Bucket Init Service
participant Server as Happier Server
participant DB as PostgreSQL
participant Redis as Redis
NixOS->>Systemd: Start full mode services
Systemd->>DB: Start PostgreSQL (if createLocally)
Systemd->>Redis: Start Redis (if createLocally)
Systemd->>MinIO: Start MinIO (if createLocally)
MinIO-->>Systemd: Ready signal
Systemd->>BucketInit: Trigger bucket initializer
BucketInit->>MinIO: Create configured bucket
BucketInit-->>Systemd: Complete
Systemd->>Migrate: Run DB migrations
Migrate->>DB: Execute migration scripts
DB-->>Migrate: Schema ready
Migrate-->>Systemd: Complete
Systemd->>Server: Start happier-server (full)
Server->>DB: Connect to PostgreSQL
Server->>Redis: Connect to Redis cache
Server->>MinIO: Connect to object storage
Server-->>Systemd: Running
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 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)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 -->
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
Tests