From 0fd901b01fca44af8023ebecf2ff9d9ddf130583 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Mon, 9 Mar 2026 21:51:56 -0500 Subject: [PATCH] Ready for NixOS builds --- .forgejo/workflows/deploy.yml | 37 ++++++++++++++++ .github/workflows/deploy.yml | 39 ----------------- .vscode/settings.json | 5 --- CLAUDE.md | 11 +++-- Dockerfile | 32 -------------- deploy.md | 12 ++++++ flake.lock | 27 ++++++++++++ flake.nix | 9 ++++ nixos/module.nix | 81 +++++++++++++++++++++++++++++++++++ 9 files changed, 173 insertions(+), 80 deletions(-) create mode 100644 .forgejo/workflows/deploy.yml delete mode 100644 .github/workflows/deploy.yml delete mode 100644 .vscode/settings.json delete mode 100644 Dockerfile create mode 100644 deploy.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nixos/module.nix diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..04be862 --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,37 @@ +name: Deploy + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: self-hosted + defaults: + run: + working-directory: /mnt/volume_nyc3_01/Portal.RhemaWaveTech.com + steps: + - name: Pull latest changes + run: git pull + + - name: Install dependencies + run: npm ci + + - name: Run migrations + run: | + set -a + source .env + set +a + npx prisma migrate deploy + + - name: Build app + run: | + export DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" + export BETTER_AUTH_SECRET="build-time-placeholder-not-used" + npm run build + + - name: Install production dependencies + run: npm ci --omit=dev + + - name: Restart portal + run: sudo systemctl restart portal diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index c377a26..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,39 +0,0 @@ -# .github/workflows/deploy.yml -name: Deploy - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: self-hosted - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: npm ci - - - name: Run migrations - run: | - set -a - source /mnt/volume_nyc3_01/Portal/.env - set +a - export DO_CA_CERTIFICATE=/etc/ssl/certs/do-ca-certificate.crt - npx prisma migrate deploy - - - name: Build image - run: podman build -t portal:latest . - - - name: Stop old container - run: podman stop portal || true && podman rm portal || true - - - name: Start new container - run: | - podman run -d --name portal \ - --replace \ - --restart=always \ - --network=host \ - -v /mnt/volume_nyc3_01/Portal/.env:/app/.env:ro \ - -v /etc/ssl/certs/do-ca-certificate.crt:/etc/ssl/certs/do-ca-certificate.crt:ro \ - portal:latest diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index bc31e15..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "files.associations": { - "*.css": "tailwindcss" - } -} diff --git a/CLAUDE.md b/CLAUDE.md index d1b33a9..bc63993 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,10 +71,13 @@ Global config: `expect: { requireAssertions: true }`. ## Deployment -- Containerized via Dockerfile (multi-stage, Node 22 Alpine) -- CI/CD: GitHub Actions on push to `main` → Podman build → deploy with `--network=host` -- Hosted on DigitalOcean (managed PostgreSQL with SSL, self-hosted runner) -- Production `.env` mounted from `/mnt/volume_nyc3_01/Portal/.env` +- **Server infrastructure:** NixOS, configured declaratively in the `Webserver-Infra` repo +- **Secrets:** Managed via `sops-nix` (no plaintext `.env` files on the server) +- **CI/CD:** Forgejo Actions on push to `main` → `npm ci` → migrate → build → rsync to `/var/lib/portal/current/` → restart +- **Hosting:** DigitalOcean droplet (NixOS), managed PostgreSQL with SSL, self-hosted Forgejo runner +- **Portal path:** `/var/lib/portal/current` (deployed by CI, owned by `portal` user) +- **Reverse proxy:** Caddy with automatic HTTPS at `rhemawavetech.com` +- **Logs:** `journalctl -u portal -f` ## Environment Variables diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1194405..0000000 --- a/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM node:22-alpine AS builder - -WORKDIR /app - -# Install dependencies first (better layer caching) -COPY package.json package-lock.json ./ -RUN npm ci - -# Copy source and build -COPY . . -ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" -ENV BETTER_AUTH_SECRET="build-time-placeholder-not-used" -RUN npm run build - -# Production image -FROM node:22-alpine - -WORKDIR /app - -# Only copy what we need for production -COPY --from=builder /app/build ./build -COPY --from=builder /app/package.json ./ -COPY --from=builder /app/package-lock.json ./ - -# Install production dependencies only (including dotenv) -RUN npm ci --omit=dev - -ENV HOST=0.0.0.0 -ENV PORT=3001 -EXPOSE 3001 - -CMD ["node", "-r", "dotenv/config", "build"] \ No newline at end of file diff --git a/deploy.md b/deploy.md new file mode 100644 index 0000000..0c50fcd --- /dev/null +++ b/deploy.md @@ -0,0 +1,12 @@ +Initial Setup: +1. Add the portal flake input to Webserver-Infra/flake.nix with a Forgejo access token. +- Does this need to be in just the inputs, or the outputs as well? I'm still very much learning nix and these things get confusing. +2. Add services.portal.enable = true; to Webserver-Infra/configuration.nix +3. Remove the existing users.users.portal and groups because they exist in this flake now +4. Clone the repo on the server, place the .env, build, then run nixos-rebuild switch. This is mainly to start the service, I presume. The build is just so that the service has something to + start. + +To update: +1. git pull +2. Apply shell-instance environment variables for placeholder values to then run `npm ci && npm run build && npm ci --omit=dev` +3. Restart the systemctl service (portal.service) to pickup the changes because the service is in charge of the `node build` command. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8048425 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1772956932, + "narHash": "sha256-M0yS4AafhKxPPmOHGqIV0iKxgNO8bHDWdl1kOwGBwRY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "608d0cadfed240589a7eea422407a547ad626a14", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e5d870a --- /dev/null +++ b/flake.nix @@ -0,0 +1,9 @@ +{ + description = "RhemaWave Portal"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = { self, nixpkgs }: { + nixosModules.default = import ./nixos/module.nix; + }; +} diff --git a/nixos/module.nix b/nixos/module.nix new file mode 100644 index 0000000..5978065 --- /dev/null +++ b/nixos/module.nix @@ -0,0 +1,81 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.portal; +in +{ + options.services.portal = { + enable = lib.mkEnableOption "RhemaWave Portal"; + + directory = lib.mkOption { + type = lib.types.path; + default = "/mnt/volume_nyc3_01/Portal.RhemaWaveTech.com"; + description = "Path to the Portal git clone."; + }; + + envFile = lib.mkOption { + type = lib.types.path; + default = "${cfg.directory}/.env"; + defaultText = lib.literalExpression ''"''${cfg.directory}/.env"''; + description = "Path to the .env file containing secrets."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 3001; + description = "Port the Portal listens on."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Host address the Portal binds to."; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.portal = { + isSystemUser = true; + group = "portal"; + home = cfg.directory; + }; + + users.groups.portal = { }; + + systemd.services.portal = { + description = "RhemaWave Portal"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = "portal"; + Group = "portal"; + WorkingDirectory = cfg.directory; + EnvironmentFile = cfg.envFile; + ExecStart = "${pkgs.nodejs_22}/bin/node build"; + Restart = "on-failure"; + RestartSec = 5; + }; + + environment = { + HOST = cfg.host; + PORT = toString cfg.port; + DO_CA_CERTIFICATE = "/var/lib/portal/do-ca-certificate.crt"; + NODE_ENV = "production"; + }; + }; + + security.sudo.extraRules = [ + { + users = [ "forgejo-runner" ]; + commands = [ + { + command = "/run/current-system/sw/bin/systemctl restart portal"; + options = [ "NOPASSWD" ]; + } + ]; + } + ]; + }; +}