Skip to content

Add branch-deployable preview API infrastructure#108

Merged
graphite-app[bot] merged 1 commit into
mainfrom
feature/rai-581-preview-api-infrastructure
May 20, 2026
Merged

Add branch-deployable preview API infrastructure#108
graphite-app[bot] merged 1 commit into
mainfrom
feature/rai-581-preview-api-infrastructure

Conversation

@findolor
Copy link
Copy Markdown
Collaborator

@findolor findolor commented May 15, 2026

Summary

  • add a separate preview NixOS/deploy-rs target for api.preview.st0x.io
  • add additive Terraform resources for a reusable preview droplet, volume, and reserved IP behind preview_enabled
  • add deploy-preview-*, remote-preview, resolve-preview-ip, and preview-create-api-key wrappers
  • add a manual GitHub Actions Deploy Preview workflow that deploys a selected branch/tag/SHA to preview
  • reset preview app/indexer DB state during preview service activation while preserving the private registry artifact
  • install the active service config at /etc/st0x-rest-api/config.toml so preview key management has a stable config path
  • document the preview provisioning, deploy, key creation, and GitHub workflow steps

How to Use

  1. Enable preview infrastructure in encrypted Terraform vars:
nix develop -c tf-edit-vars
# set preview_enabled = true
nix develop -c tf-plan
nix develop -c tf-apply
  1. Bootstrap the preview host:
DEPLOY_ENV=preview nix develop -c bootstrap
  1. Point api.preview.st0x.io to:
nix develop -c resolve-preview-ip
  1. Add/confirm GitHub secrets:
  • SSH_KEY: already used by production deploy; must be valid for the preview host too.
  • PREVIEW_SSH_HOST_KEY: optional but recommended. If absent, the workflow falls back to ssh-keyscan.
  1. Use GitHub Actions > Deploy Preview:
  • ref: branch, tag, or SHA to deploy
  • deploy_scope: service for normal branch deploys, all when the preview NixOS config also changed
  1. After a preview deploy, create a fresh preview key if authenticated checks are needed:
nix develop -c preview-create-api-key "preview-benchmark" "arda@st0x.io"
# or with admin access:
nix develop -c preview-create-api-key "preview-admin" "arda@st0x.io" --admin

Validation

  • nix eval .#deploy.nodes.st0x-rest-api-preview.profiles.rest-api.profilePath
  • nix eval .#nixosConfigurations.st0x-rest-api-preview.config.services.nginx.virtualHosts --apply builtins.attrNames
  • nix eval .#nixosConfigurations.st0x-rest-api-prod.config.services.nginx.virtualHosts --apply builtins.attrNames
  • nix eval .#nixosConfigurations.st0x-rest-api-preview.config.systemd.services.rest-api.serviceConfig.ExecStart
  • nix eval .#nixosConfigurations.st0x-rest-api-preview.config.fileSystems."/mnt/data".device
  • nix eval --impure --expr ... environment.etc."st0x-rest-api/config.toml".source
  • nix build .#deployPreviewService --no-link
  • nix build .#deployPreviewAll --no-link
  • nix build .#remotePreview --no-link
  • nix build .#previewCreateApiKey --no-link
  • nix develop -c terraform -chdir=infra fmt
  • nix develop -c terraform -chdir=infra validate
  • nix develop -c rainix-rs-static
  • git diff --check

rainix-rs-static passed with the existing cache dead-code warnings.

Summary by CodeRabbit

  • New Features

    • Add preview environment: deployable from CI/Workflow Dispatch or tooling with service-only or full-system scope; provisions independent preview infra and reserved host IP.
  • Configuration

    • New preview config with separate paths, DB behavior, and rate-limit settings; deployment profiles support optional DB reset for preview.
  • Documentation

    • Full “Deploy a branch to preview” guide, updated SSH/operational instructions, and post-deploy smoke-test/runbook.

Review Change Stack

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 15, 2026

RAI-581

Copy link
Copy Markdown
Collaborator Author

findolor commented May 15, 2026


How to use the Graphite Merge Queue

Add the label add-to-gt-merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@findolor findolor self-assigned this May 15, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end preview environment support: conditional Terraform preview resources/outputs, NixOS environment parameterization, flake and deploy tooling for preview targets, a GitHub Actions workflow to run preview deployments (service-only or full), preview runtime config, and documentation updates.

Changes

Preview Environment Deployment Infrastructure

Layer / File(s) Summary
Terraform variables and example tfvars
infra/variables.tf, infra/terraform.tfvars.example
Adds preview_enabled, sizing variables, and example preview_enabled to gate preview provisioning.
Terraform outputs for preview
infra/outputs.tf
Expose preview_droplet_id, preview_droplet_ipv4, preview_reserved_ip, and preview_volume_id (null when preview disabled).
Terraform preview provisioning
infra/main.tf, infra/default.nix
Conditionally create preview volume, droplet, attachment, reserved IP and assignment; resolveIp selects preview outputs when DEPLOY_ENV=preview and validates presence.
NixOS module parameterization
os.nix
Introduce st0xEnvenv (name, virtualHost, configFile, dataDir, dataVolumeName); materialize /etc/st0x-rest-api/config.toml; switch hardcoded prod paths/hosts to env-driven values.
Flake utilities and bootstrap behavior
flake.nix
Add mkNixosConfiguration helper and separate st0x-rest-api-prod/-preview outputs; bootstrap-nixos and host-key handling honor DEPLOY_ENV; add resolve-preview-ip, remote-preview, and preview-create-api-key shell apps; include preview tools in devShell.
deploy.nix: preview node & wrappers
deploy.nix
Refactor profile helpers to support resetState/dataDir; add st0x-rest-api-preview node using resetState = true and preview dataDir; add previewDeployPreamble and deploy-preview-* wrapper scripts.
GitHub Actions preview workflow
.github/workflows/deploy-preview.yaml
New workflow_dispatch pipeline with ref and deploy_scope inputs that checks out the requested ref, installs Nix/cache, provisions preview infra (tfPlan/tfApply/TF rekey), resolves/masks preview IP, bootstraps host, ensures SSH trust, runs prep, and conditionally deploys service-only (30m) or full preview (60m).
Preview runtime config and docs
config/preview.toml, DEPLOY.md, docs/ops.md, keys.nix
Add preview TOML config (paths, SQLite URL, registry URL, rate limits); document manual and GitHub preview deploy flows, DB reset behavior, post-deploy checks, and preview SSH guidance; add arda to roles.infra.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • hardyjosh
  • 0xgleb
  • JuaniRios

Poem

🐇 I hop a branch across the trees,
Nix and Terraform whisper in the breeze.
A masked IP hides where previews glow,
Services wake — the staging show.
Rabbit nods: "Deployed, let's go!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add branch-deployable preview API infrastructure' accurately and concisely summarizes the primary change across all modified files, which collectively introduce a complete preview environment infrastructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/rai-581-preview-api-infrastructure

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
.github/workflows/deploy-preview.yaml (2)

70-70: ⚡ Quick win

Document what prep.sh does.

The workflow runs ./prep.sh without context. Consider adding a comment explaining its purpose (e.g., "Build service binary", "Prepare deployment artifacts", etc.) to help future maintainers understand the deployment flow.

📝 Example documentation comment
+      # Prepare deployment artifacts (service binary, etc.)
       - run: ./prep.sh
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-preview.yaml at line 70, Add a short inline comment
above the run step that invokes ./prep.sh in the deploy-preview.yaml workflow
that explains what prep.sh does (for example "Build service binary", "Prepare
deployment artifacts", or "Install dependencies and package release assets");
reference the run step calling ./prep.sh so maintainers can see the intent and
downstream effects without inspecting the script itself and ensure the comment
briefly lists any major side effects (e.g., builds, artifact creation, env
assumptions).

72-78: ⚡ Quick win

Consider adding timeout-minutes to deployment steps.

Long-running deployments could hang indefinitely (up to the job-level default of 360 minutes). Adding explicit timeouts improves failure diagnostics and prevents runaway workflows.

⏱️ Proposed timeout configuration
       - name: Deploy preview service
         if: inputs.deploy_scope == 'service'
+        timeout-minutes: 15
         run: nix run .#deployPreviewService

       - name: Deploy preview system and service
         if: inputs.deploy_scope == 'all'
+        timeout-minutes: 30
         run: nix run .#deployPreviewAll
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-preview.yaml around lines 72 - 78, The deployment
steps "Deploy preview service" and "Deploy preview system and service" currently
can run indefinitely; add an explicit GitHub Actions timeout by adding a
timeout-minutes field to each step that runs the nix commands (the steps named
"Deploy preview service" running "nix run .#deployPreviewService" and "Deploy
preview system and service" running "nix run .#deployPreviewAll"), choosing an
appropriate minute value for your deployment (e.g., 30–60) so long-running runs
fail fast and produce clearer diagnostics.
DEPLOY.md (1)

276-276: ⚡ Quick win

Avoid exposing credentials in shell history.

The example command passes API_KEY and API_SECRET as environment variables on the command line, which will be saved to shell history in plaintext. Consider recommending a safer pattern.

🔐 Safer alternative using environment file or prompt
 After deploy, wait for `/health/detailed` to report a ready raindex state before
 running performance checks:

 ```bash
-API_URL=https://api.preview.st0x.io API_KEY=... API_SECRET=... ./scripts/smoke.sh
+# Option 1: Use .env file (ensure it's gitignored)
+export API_URL=https://api.preview.st0x.io
+export API_KEY=$(cat ~/.config/st0x/api_key)
+export API_SECRET=$(cat ~/.config/st0x/api_secret)
+./scripts/smoke.sh
+
+# Option 2: Prompt for secrets
+read -sp "API_KEY: " API_KEY && export API_KEY
+read -sp "API_SECRET: " API_SECRET && export API_SECRET
+API_URL=https://api.preview.st0x.io ./scripts/smoke.sh
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DEPLOY.md` at line 276, The example in DEPLOY.md exposes API_KEY and
API_SECRET on the command line; update the example to recommend safer patterns
instead: instruct users to load secrets from a gitignored env file or secure
storage (e.g., export API_KEY=$(cat ~/.config/st0x/api_key)) before running
./scripts/smoke.sh, or prompt for secrets interactively using a silent read
(read -sp) and export them, and show using API_URL only on the command
invocation if needed; reference the variables API_KEY, API_SECRET and the script
./scripts/smoke.sh so readers know where to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-preview.yaml:
- Around line 60-68: Add a short inline comment above the
PREVIEW_SSH_HOST_KEY/ssh-keyscan block warning that the ssh-keyscan fallback
(ssh-keyscan -H "$HOST_IP" writing to ~/.ssh/known_hosts) is susceptible to
man-in-the-middle attacks because it accepts the host key without verification,
and strongly recommend setting the PREVIEW_SSH_HOST_KEY secret instead;
reference the PREVIEW_SSH_HOST_KEY env var, the ssh-keyscan fallback branch, and
the known_hosts write so reviewers know exactly where to see the risk.
- Around line 54-58: The "Resolve preview host IP" step captures host_ip from
running .#resolvePreviewIp but doesn't validate it; update that step to verify
the output (host_ip) is a well-formed IP address before exporting to GITHUB_ENV
and masking: after obtaining host_ip from nix run .#resolvePreviewIp, check it
against an IPv4/IPv6 validation (e.g., a regex or use system tools like `ping -c
1`/`ip`/`grep -E` to ensure it's an IP) and fail the job with a clear error if
invalid; ensure you only call echo "::add-mask::$host_ip" and append
"HOST_IP=$host_ip" to GITHUB_ENV when the validation passes so subsequent SSH
steps receive a guaranteed valid IP.

In `@infra/default.nix`:
- Around line 62-64: The prod branch sets host_ip by running jq -r
'.outputs.reserved_ip.value' on tfState without validating it; add the same
null/empty guard used for preview so that after host_ip=$(jq -r
'.outputs.reserved_ip.value' ${tfState}) you check whether host_ip is empty or
"null" and if so log an error and exit (mirroring the preview validation) to
prevent later commands from running with an invalid reserved_ip value.

---

Nitpick comments:
In @.github/workflows/deploy-preview.yaml:
- Line 70: Add a short inline comment above the run step that invokes ./prep.sh
in the deploy-preview.yaml workflow that explains what prep.sh does (for example
"Build service binary", "Prepare deployment artifacts", or "Install dependencies
and package release assets"); reference the run step calling ./prep.sh so
maintainers can see the intent and downstream effects without inspecting the
script itself and ensure the comment briefly lists any major side effects (e.g.,
builds, artifact creation, env assumptions).
- Around line 72-78: The deployment steps "Deploy preview service" and "Deploy
preview system and service" currently can run indefinitely; add an explicit
GitHub Actions timeout by adding a timeout-minutes field to each step that runs
the nix commands (the steps named "Deploy preview service" running "nix run
.#deployPreviewService" and "Deploy preview system and service" running "nix run
.#deployPreviewAll"), choosing an appropriate minute value for your deployment
(e.g., 30–60) so long-running runs fail fast and produce clearer diagnostics.

In `@DEPLOY.md`:
- Line 276: The example in DEPLOY.md exposes API_KEY and API_SECRET on the
command line; update the example to recommend safer patterns instead: instruct
users to load secrets from a gitignored env file or secure storage (e.g., export
API_KEY=$(cat ~/.config/st0x/api_key)) before running ./scripts/smoke.sh, or
prompt for secrets interactively using a silent read (read -sp) and export them,
and show using API_URL only on the command invocation if needed; reference the
variables API_KEY, API_SECRET and the script ./scripts/smoke.sh so readers know
where to apply the change.
🪄 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: b3c5924e-60da-41df-ae60-4b38b772d0ba

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9a7af and 8b41485.

📒 Files selected for processing (12)
  • .github/workflows/deploy-preview.yaml
  • DEPLOY.md
  • config/preview.toml
  • deploy.nix
  • docs/ops.md
  • flake.nix
  • infra/default.nix
  • infra/main.tf
  • infra/outputs.tf
  • infra/terraform.tfvars.example
  • infra/variables.tf
  • os.nix

Comment thread .github/workflows/deploy-preview.yaml
Comment thread .github/workflows/deploy-preview.yaml
Comment thread infra/default.nix
@findolor findolor force-pushed the feature/rai-581-preview-api-infrastructure branch 4 times, most recently from fc1a1bc to a3c1859 Compare May 15, 2026 10:27
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-preview.yaml:
- Around line 66-74: The "Commit encrypted Terraform state" step assumes a
branch checkout and fails on detached HEAD (tag/SHA runs); update that step to
detect detached HEAD (e.g., check if git symbolic-ref --short HEAD succeeds) and
when detached create a temporary branch name (using the run context like
GITHUB_REF_NAME or GITHUB_SHA) or push the current HEAD explicitly to a target
branch (e.g., push HEAD:refs/heads/<branch>) so the commit/push succeeds in
tag/SHA workflows; update the step logic around the git commit/git push commands
in the workflow to perform this detection and conditional branch creation/push.
- Line 32: Replace mutable action references with immutable commit SHAs: update
jlumbroso/free-disk-space@v1.3.1, actions/checkout@v4, and both
DeterminateSystems/* actions currently using `@main` to use their specific commit
SHAs instead of tags/branches; locate the workflow steps that reference
jlumbroso/free-disk-space, actions/checkout, DeterminateSystems/gha-download and
DeterminateSystems/gha-upload (or similarly named DeterminateSystems actions)
and replace each "@<tag|branch>" with the corresponding full 40-character commit
SHA from the upstream repo to ensure immutability.

In `@flake.nix`:
- Around line 247-263: The remote_cmd array is being forwarded as a single
unescaped string to ssh which allows label/owner (and other args) with spaces or
shell metacharacters to be re-split or injected; fix by escaping/quoting the
command before sending: build an escaped command string from remote_cmd (e.g.
via printf '%q' over "${remote_cmd[@]}" so each element is shell-escaped) and
use that escaped string in the exec ssh invocation (preserve handling of admin
via remote_cmd+=(--admin) and keep using identity and host_ip), replacing the
current exec ssh line so the remote command is safe.
🪄 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: 172a0ceb-e131-4788-ae72-a507cd2562e4

📥 Commits

Reviewing files that changed from the base of the PR and between 8b41485 and 440d7c8.

📒 Files selected for processing (13)
  • .github/workflows/deploy-preview.yaml
  • DEPLOY.md
  • config/preview.toml
  • deploy.nix
  • docs/ops.md
  • flake.nix
  • infra/default.nix
  • infra/main.tf
  • infra/outputs.tf
  • infra/terraform.tfvars.example
  • infra/variables.tf
  • keys.nix
  • os.nix
✅ Files skipped from review due to trivial changes (2)
  • docs/ops.md
  • DEPLOY.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • infra/outputs.tf
  • infra/variables.tf
  • infra/terraform.tfvars.example
  • infra/default.nix
  • config/preview.toml
  • infra/main.tf
  • deploy.nix

runs-on: ubuntu-latest
steps:
- name: Free disk space
uses: jlumbroso/free-disk-space@v1.3.1
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/deploy-preview.yaml | sed -n '30,55p'

Repository: ST0x-Technology/st0x.rest.api

Length of output: 983


🏁 Script executed:

cat -n .github/workflows/deploy-preview.yaml | grep -E "uses:"

Repository: ST0x-Technology/st0x.rest.api

Length of output: 302


Pin third-party actions to immutable commit SHAs.

The workflow currently references actions using mutable tags and branches: jlumbroso/free-disk-space@v1.3.1, actions/checkout@v4, and both DeterminateSystems/* actions at @main. These can be updated by upstream maintainers without changes to this repo, which weakens supply-chain guarantees and breaks reproducibility. Replace all four action references with their full commit SHAs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-preview.yaml at line 32, Replace mutable action
references with immutable commit SHAs: update jlumbroso/free-disk-space@v1.3.1,
actions/checkout@v4, and both DeterminateSystems/* actions currently using `@main`
to use their specific commit SHAs instead of tags/branches; locate the workflow
steps that reference jlumbroso/free-disk-space, actions/checkout,
DeterminateSystems/gha-download and DeterminateSystems/gha-upload (or similarly
named DeterminateSystems actions) and replace each "@<tag|branch>" with the
corresponding full 40-character commit SHA from the upstream repo to ensure
immutability.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this out of this PR intentionally. Pinning third-party Actions to immutable SHAs is valid supply-chain hardening, but the repository’s existing workflows still use tag/branch refs as well. This should be handled as a separate repo-wide policy change rather than only pinning the new preview workflow here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@findolor, that's a fair and consistent approach — applying the fix to only one workflow while the rest remain unpinned would create a false sense of security. I'll keep this in mind for future reviews.

Would you like me to open a follow-up GitHub issue to track the repo-wide SHA-pinning effort so it doesn't get lost?


✏️ Learnings added
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 108
File: .github/workflows/deploy-preview.yaml:32-32
Timestamp: 2026-05-18T07:26:39.483Z
Learning: In the ST0x-Technology/st0x.rest.api repository, the team prefers not to pin individual GitHub Actions workflow steps to immutable commit SHAs on a per-PR basis. Pinning third-party action references (e.g. jlumbroso/free-disk-space, actions/checkout, DeterminateSystems/* actions) should be handled as a separate repo-wide policy change rather than flagged incrementally in individual PRs.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread .github/workflows/deploy-preview.yaml
Comment thread flake.nix Outdated
@graphite-app
Copy link
Copy Markdown

graphite-app Bot commented May 20, 2026

Merge activity

## Summary

- add a separate preview NixOS/deploy-rs target for `api.preview.st0x.io`
- add additive Terraform resources for a reusable preview droplet, volume, and reserved IP behind `preview_enabled`
- add `deploy-preview-*`, `remote-preview`, `resolve-preview-ip`, and `preview-create-api-key` wrappers
- add a manual GitHub Actions `Deploy Preview` workflow that deploys a selected branch/tag/SHA to preview
- reset preview app/indexer DB state during preview service activation while preserving the private registry artifact
- install the active service config at `/etc/st0x-rest-api/config.toml` so preview key management has a stable config path
- document the preview provisioning, deploy, key creation, and GitHub workflow steps

## How to Use

1. Enable preview infrastructure in encrypted Terraform vars:

```bash
nix develop -c tf-edit-vars
# set preview_enabled = true
nix develop -c tf-plan
nix develop -c tf-apply
```

2. Bootstrap the preview host:

```bash
DEPLOY_ENV=preview nix develop -c bootstrap
```

3. Point `api.preview.st0x.io` to:

```bash
nix develop -c resolve-preview-ip
```

4. Add/confirm GitHub secrets:

- `SSH_KEY`: already used by production deploy; must be valid for the preview host too.
- `PREVIEW_SSH_HOST_KEY`: optional but recommended. If absent, the workflow falls back to `ssh-keyscan`.

5. Use GitHub Actions > `Deploy Preview`:

- `ref`: branch, tag, or SHA to deploy
- `deploy_scope`: `service` for normal branch deploys, `all` when the preview NixOS config also changed

6. After a preview deploy, create a fresh preview key if authenticated checks are needed:

```bash
nix develop -c preview-create-api-key "preview-benchmark" "arda@st0x.io"
# or with admin access:
nix develop -c preview-create-api-key "preview-admin" "arda@st0x.io" --admin
```

## Validation

- `nix eval .#deploy.nodes.st0x-rest-api-preview.profiles.rest-api.profilePath`
- `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.services.nginx.virtualHosts --apply builtins.attrNames`
- `nix eval .#nixosConfigurations.st0x-rest-api-prod.config.services.nginx.virtualHosts --apply builtins.attrNames`
- `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.systemd.services.rest-api.serviceConfig.ExecStart`
- `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.fileSystems."/mnt/data".device`
- `nix eval --impure --expr ... environment.etc."st0x-rest-api/config.toml".source`
- `nix build .#deployPreviewService --no-link`
- `nix build .#deployPreviewAll --no-link`
- `nix build .#remotePreview --no-link`
- `nix build .#previewCreateApiKey --no-link`
- `nix develop -c terraform -chdir=infra fmt`
- `nix develop -c terraform -chdir=infra validate`
- `nix develop -c rainix-rs-static`
- `git diff --check`

`rainix-rs-static` passed with the existing cache dead-code warnings.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Add preview environment: deployable from the UI/CI with service-only or full-system scope; independent preview infra, host reservation, and deployment paths.

* **Configuration**
  * New preview configuration and deployment profiles; preview-specific rate limits and data paths; tooling to target preview vs production.

* **Documentation**
  * Full “Deploy a branch to preview” guide, updated operational/SSH instructions, and post-deploy smoke-test/runbook.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/ST0x-Technology/st0x.rest.api/pull/108?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@graphite-app graphite-app Bot force-pushed the feature/rai-581-preview-api-infrastructure branch from db8d0c8 to 0875f73 Compare May 20, 2026 10:41
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/deploy-preview.yaml (1)

34-38: 💤 Low value

Consider setting persist-credentials: false to limit credential exposure.

The checkout action persists credentials by default, which leaves a token available in the repository config for subsequent steps. While the workflow permissions are scoped, explicitly disabling credential persistence is a defense-in-depth measure.

🛡️ Suggested hardening
       - uses: actions/checkout@v4
         with:
           ref: ${{ inputs.ref || github.ref }}
           submodules: recursive
           fetch-depth: 0
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-preview.yaml around lines 34 - 38, The checkout
step currently leaves credentials persisted by default; update the
actions/checkout@v4 invocation to add persist-credentials: false so the checkout
action does not leave the GITHUB_TOKEN in the repo config (modify the block
where actions/checkout@v4 is used and add the persist-credentials: false key
alongside ref, submodules, and fetch-depth).
deploy.nix (1)

18-24: 🏗️ Heavy lift

Consider consolidating preview DB reset outside of service activation profiles.

The reset logic in mkServiceProfile (lines 18–24) is included in every service profile. If multiple services are enabled and share the same data directory, each service's activation will delete st0x.db and raindex.db independently. Currently only rest-api is enabled, so there is no issue; however, if more services are added later, a second service activation would delete the database after the first service has already restarted. To future-proof, move the reset to a single operation before all service restarts (or use a per-service data directory if services should not share database files).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy.nix` around lines 18 - 24, The reset logic inside mkServiceProfile
(the resetCommands array that removes ${dataDir}/st0x.db and
${dataDir}/raindex.db) should be moved out of the per-service activation so it
runs once before all services are restarted; update the deploy flow so that
activate.custom calls (or the loop that invokes mkServiceProfile) no longer
include resetCommands and instead run a single conditional step that checks
resetState and deletes the preview DB files once (using the same ${dataDir},
markerFile and name context) before iterating services; alternatively, if
services must not share the DB, change the per-service dataDir used by
mkServiceProfile so each service has its own DB path instead of the shared
files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-preview.yaml:
- Around line 40-54: The workflow is vulnerable because the GitHub expression
${{ inputs.ref }} is injected directly into shell commands when setting
deploy_branch and used in error messages and git operations; instead, write the
input into a safe environment variable (e.g., DEPLOY_REF) via GITHUB_ENV using
the GitHub expression, then use that environment variable inside the shell (read
into deploy_branch, check it, and pass it to git) so no untrusted string is
interpreted by the shell; update the uses of ${{ inputs.ref }} in the
deploy_branch assignment, error messages, and git fetch/checkout to reference
the environment variable (deploy_branch/DEPLOY_REF) and keep the rest of the
validation logic (empty/ref prefix check, git ls-remote, fetch, checkout, and
echo "DEPLOY_BRANCH=...") unchanged.

---

Nitpick comments:
In @.github/workflows/deploy-preview.yaml:
- Around line 34-38: The checkout step currently leaves credentials persisted by
default; update the actions/checkout@v4 invocation to add persist-credentials:
false so the checkout action does not leave the GITHUB_TOKEN in the repo config
(modify the block where actions/checkout@v4 is used and add the
persist-credentials: false key alongside ref, submodules, and fetch-depth).

In `@deploy.nix`:
- Around line 18-24: The reset logic inside mkServiceProfile (the resetCommands
array that removes ${dataDir}/st0x.db and ${dataDir}/raindex.db) should be moved
out of the per-service activation so it runs once before all services are
restarted; update the deploy flow so that activate.custom calls (or the loop
that invokes mkServiceProfile) no longer include resetCommands and instead run a
single conditional step that checks resetState and deletes the preview DB files
once (using the same ${dataDir}, markerFile and name context) before iterating
services; alternatively, if services must not share the DB, change the
per-service dataDir used by mkServiceProfile so each service has its own DB path
instead of the shared files.
🪄 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: 4f985ef6-13a1-46c8-bb5e-b9dec30317e1

📥 Commits

Reviewing files that changed from the base of the PR and between db8d0c8 and 0875f73.

📒 Files selected for processing (13)
  • .github/workflows/deploy-preview.yaml
  • DEPLOY.md
  • config/preview.toml
  • deploy.nix
  • docs/ops.md
  • flake.nix
  • infra/default.nix
  • infra/main.tf
  • infra/outputs.tf
  • infra/terraform.tfvars.example
  • infra/variables.tf
  • keys.nix
  • os.nix
✅ Files skipped from review due to trivial changes (2)
  • docs/ops.md
  • DEPLOY.md

Comment on lines +40 to +54
- name: Verify deploy branch
run: |
deploy_branch="${{ inputs.ref }}"
deploy_branch="${deploy_branch#refs/heads/}"
if [ -z "$deploy_branch" ] || [[ "$deploy_branch" == refs/* ]]; then
echo "Deploy Preview requires a branch ref, got '${{ inputs.ref }}'" >&2
exit 1
fi
if ! git ls-remote --exit-code --heads origin "$deploy_branch" >/dev/null; then
echo "Deploy Preview requires an existing remote branch, got '${{ inputs.ref }}'" >&2
exit 1
fi
git fetch origin "refs/heads/$deploy_branch:refs/remotes/origin/$deploy_branch"
git checkout -B "$deploy_branch" "origin/$deploy_branch"
echo "DEPLOY_BRANCH=$deploy_branch" >> "$GITHUB_ENV"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Template injection vulnerability in shell script.

The ${{ inputs.ref }} expression is expanded directly into shell context on lines 42, 45, and 49. Since workflow_dispatch inputs are user-controlled, a malicious actor with workflow trigger access could inject arbitrary shell commands via a crafted ref value (e.g., "; malicious_command; #).

Use an environment variable to safely pass the input to the shell:

🔐 Proposed fix to prevent injection
       - name: Verify deploy branch
+        env:
+          INPUT_REF: ${{ inputs.ref }}
         run: |
-          deploy_branch="${{ inputs.ref }}"
+          deploy_branch="$INPUT_REF"
           deploy_branch="${deploy_branch#refs/heads/}"
           if [ -z "$deploy_branch" ] || [[ "$deploy_branch" == refs/* ]]; then
-            echo "Deploy Preview requires a branch ref, got '${{ inputs.ref }}'" >&2
+            echo "Deploy Preview requires a branch ref, got '$INPUT_REF'" >&2
             exit 1
           fi
           if ! git ls-remote --exit-code --heads origin "$deploy_branch" >/dev/null; then
-            echo "Deploy Preview requires an existing remote branch, got '${{ inputs.ref }}'" >&2
+            echo "Deploy Preview requires an existing remote branch, got '$INPUT_REF'" >&2
             exit 1
           fi
           git fetch origin "refs/heads/$deploy_branch:refs/remotes/origin/$deploy_branch"
           git checkout -B "$deploy_branch" "origin/$deploy_branch"
           echo "DEPLOY_BRANCH=$deploy_branch" >> "$GITHUB_ENV"
🧰 Tools
🪛 zizmor (1.25.2)

[error] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 45-45: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 49-49: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-preview.yaml around lines 40 - 54, The workflow is
vulnerable because the GitHub expression ${{ inputs.ref }} is injected directly
into shell commands when setting deploy_branch and used in error messages and
git operations; instead, write the input into a safe environment variable (e.g.,
DEPLOY_REF) via GITHUB_ENV using the GitHub expression, then use that
environment variable inside the shell (read into deploy_branch, check it, and
pass it to git) so no untrusted string is interpreted by the shell; update the
uses of ${{ inputs.ref }} in the deploy_branch assignment, error messages, and
git fetch/checkout to reference the environment variable
(deploy_branch/DEPLOY_REF) and keep the rest of the validation logic (empty/ref
prefix check, git ls-remote, fetch, checkout, and echo "DEPLOY_BRANCH=...")
unchanged.

@graphite-app graphite-app Bot merged commit 0875f73 into main May 20, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants