Initial Commit
slip slop
This commit is contained in:
commit
064e672ab7
61 changed files with 3504 additions and 0 deletions
200
nixos/module.nix
Normal file
200
nixos/module.nix
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# NixOS module for repertory-api
|
||||
#
|
||||
# This module fetches the application source directly from a self-hosted Forgejo instance at a pinned
|
||||
# tag/commit, builds a Python environment, runs Alembic migrations on start,
|
||||
# and manages the Flask service under systemd.
|
||||
#
|
||||
# ── Typical usage in configuration.nix ───────────────────────────────────────
|
||||
#
|
||||
# imports = [ (fetchTarball {
|
||||
# url = "https://git.yourdomain.com/yourusername/repertory-api/archive/v1.0.0.tar.gz";
|
||||
# sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
# } + "/nixos/module.nix") ];
|
||||
#
|
||||
# ── Secrets ──────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# The module expects two sops-nix secrets to exist. Add to configuration.nix:
|
||||
#
|
||||
# sops.secrets."repertory-api-key" = {};
|
||||
# sops.secrets."repertory-db-url" = {};
|
||||
#
|
||||
# ── Minimal configuration.nix example ────────────────────────────────────────
|
||||
#
|
||||
# services.repertory-api = {
|
||||
# enable = true;
|
||||
# domain = "git.yourdomain.com";
|
||||
# owner = "yourusername";
|
||||
# version = "v1.0.0";
|
||||
# rev = "abc123..."; # git commit SHA for the tag
|
||||
# sha256 = "sha256-..."; # `nix-prefetch-url --unpack <tarball url>`
|
||||
# };
|
||||
#
|
||||
# ── Updating ─────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# 1. Push a new tag to your Forgejo instance (e.g. v1.1.0).
|
||||
# 2. Get the new sha256:
|
||||
# nix-prefetch-url --unpack \
|
||||
# https://git.yourdomain.com/yourusername/repertory-api/archive/v1.1.0.tar.gz
|
||||
# 3. Update `version`, `rev`, and `sha256` in your configuration.nix.
|
||||
# 4. nixos-rebuild switch
|
||||
# → Nix fetches the new source, rebuilds the Python env, restarts the
|
||||
# service, and runs migrations automatically in preStart.
|
||||
#
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.repertory-api;
|
||||
|
||||
# Fetch the application source from the self-hosted Forgejo instance at the
|
||||
# pinned rev. Nix verifies the sha256 hash, so this is fully reproducible.
|
||||
appSrc = pkgs.fetchFromGitea {
|
||||
domain = cfg.domain;
|
||||
owner = cfg.owner;
|
||||
repo = "repertory-api";
|
||||
rev = cfg.rev;
|
||||
sha256 = cfg.sha256;
|
||||
};
|
||||
|
||||
# Build the Python environment with all runtime dependencies.
|
||||
# This mirrors pyproject.toml [project.dependencies].
|
||||
# uv is a local dev tool — on NixOS, Nix itself plays the role of
|
||||
# dependency resolver and the Python env is built at evaluation time.
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
flask
|
||||
flask-sqlalchemy
|
||||
sqlalchemy
|
||||
psycopg2
|
||||
python-dotenv
|
||||
alembic
|
||||
]);
|
||||
|
||||
in {
|
||||
|
||||
options.services.repertory-api = {
|
||||
enable = mkEnableOption "Repertory API";
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = "Hostname of your self-hosted Forgejo instance.";
|
||||
example = "git.yourdomain.com";
|
||||
};
|
||||
|
||||
owner = mkOption {
|
||||
type = types.str;
|
||||
description = "Forgejo username / org that owns the repertory-api repo.";
|
||||
example = "yourusername";
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
type = types.str;
|
||||
description = "Human-readable version label (used only for documentation/logging).";
|
||||
example = "v1.0.0";
|
||||
};
|
||||
|
||||
rev = mkOption {
|
||||
type = types.str;
|
||||
description = "Git commit SHA for the tag you want to deploy.";
|
||||
example = "abc1234def5678";
|
||||
};
|
||||
|
||||
sha256 = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
sha256 hash of the fetched source tarball.
|
||||
Obtain with:
|
||||
nix-prefetch-url --unpack \
|
||||
https://git.yourdomain.com/<owner>/repertory-api/archive/<rev>.tar.gz
|
||||
'';
|
||||
example = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 5000;
|
||||
description = "Port the Flask app listens on.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "repertory";
|
||||
description = "Unix user to run the service as.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "repertory";
|
||||
description = "Unix group to run the service as.";
|
||||
};
|
||||
|
||||
apiKeySecret = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/secrets/repertory-api-key";
|
||||
description = "Path to the SOPS-decrypted file containing the raw API key.";
|
||||
};
|
||||
|
||||
dbUrlSecret = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/secrets/repertory-db-url";
|
||||
description = "Path to the SOPS-decrypted file containing the DATABASE_URL string.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = "/var/lib/repertory-api";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
|
||||
systemd.services.repertory-api = {
|
||||
description = "Repertory API (Flask) — ${cfg.version}";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "postgresql.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = appSrc;
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
|
||||
ExecStart = "${pythonEnv}/bin/python -m flask --app wsgi:app run --host=0.0.0.0 --port=${toString cfg.port}";
|
||||
|
||||
# Secrets are injected via the env file written in preStart.
|
||||
EnvironmentFiles = [ "/run/repertory-api/env" ];
|
||||
|
||||
# Security hardening
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
# /run/repertory-api is on tmpfs and holds only the mode-600 env file.
|
||||
ReadWritePaths = [ "/run/repertory-api" ];
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
# Write secrets into a tmpfs env file so nothing is on-disk in plaintext.
|
||||
mkdir -p /run/repertory-api
|
||||
chmod 700 /run/repertory-api
|
||||
printf 'API_KEY=%s\n' "$(cat ${cfg.apiKeySecret})" > /run/repertory-api/env
|
||||
printf 'DATABASE_URL=%s\n' "$(cat ${cfg.dbUrlSecret})" >> /run/repertory-api/env
|
||||
chmod 600 /run/repertory-api/env
|
||||
|
||||
# Run any pending Alembic migrations before the server starts.
|
||||
# On a fresh database this creates all tables; on subsequent deploys
|
||||
# it applies only new migration files.
|
||||
export DATABASE_URL="$(cat ${cfg.dbUrlSecret})"
|
||||
cd ${appSrc}
|
||||
${pythonEnv}/bin/python -m alembic upgrade head
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue