From a33c7c7762b04000e3d5b9815bee0308cfae75b3 Mon Sep 17 00:00:00 2001 From: nio17 Date: Tue, 12 May 2026 08:46:17 +0200 Subject: [PATCH] Pro --- project-skeleton.nix | 247 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 project-skeleton.nix diff --git a/project-skeleton.nix b/project-skeleton.nix new file mode 100644 index 0000000..3b5a73c --- /dev/null +++ b/project-skeleton.nix @@ -0,0 +1,247 @@ +# ============================================================================== +# PROJECT SKELETON — NixOS Service Module +# ============================================================================== +# +# USAGE: +# 1. Copy this file into your project repo, e.g. ~/projects/my-project/my-project.nix +# 2. Replace every occurrence of: +# MY_PROJECT → your project name in camelCase (e.g. myRssReader) +# my-project → your project name in kebab-case (e.g. my-rss-reader) +# my-project.example.com → your actual domain +# 127.0.0.1:3000 → the address your app listens on internally +# 3. In /etc/nixos/configuration.nix, add: +# imports = [ /path/to/my-project.nix ]; +# services.MY_PROJECT.enable = true; +# +# ASSUMPTIONS: +# - Nginx global settings (recommendedTlsSettings etc.) and ACME email are +# configured centrally in configuration.nix (see bottom of this file). +# - Your app runs as a systemd service on a local port (reverse proxy setup). +# - You want HTTPS via Let's Encrypt. +# +# ============================================================================== + +{ config, pkgs, lib, ... }: + +let + # The internal port your application listens on. + # Nginx will forward public traffic here. + appPort = 3000; + + # Shorthand so you can reference the merged config of this module cleanly. + cfg = config.services.MY_PROJECT; + +in +{ + # ============================================================================ + # OPTIONS + # Define the knobs that other files (or configuration.nix) can turn. + # Nothing in `config` below runs until `enable` is set to true. + # ============================================================================ + + options.services.MY_PROJECT = { + + enable = lib.mkEnableOption "MY_PROJECT service"; + # Adds a boolean option `services.MY_PROJECT.enable` that defaults to false. + # Set it to true in configuration.nix to activate this module. + + domain = lib.mkOption { + type = lib.types.str; + default = "my-project.example.com"; + description = "The public domain name Nginx will serve and ACME will certify."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = appPort; + description = "Internal port the application process listens on."; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/my-project"; + description = "Directory for persistent application data (database, uploads, etc.)."; + }; + + # Optional: expose an environment file path for secrets. + # The file should contain KEY=VALUE pairs and never be committed to git. + # Example content: + # DATABASE_URL=postgres://user:password@localhost/mydb + # SECRET_KEY=supersecret + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing secret environment variables. + This file is read by systemd at runtime and never stored in the Nix store. + Example: /run/secrets/my-project.env + ''; + }; + + }; + + # ============================================================================ + # CONFIG + # Everything below only takes effect when `services.MY_PROJECT.enable = true`. + # ============================================================================ + + config = lib.mkIf cfg.enable { + + # -------------------------------------------------------------------------- + # Systemd Service + # Runs your application as a background process. + # -------------------------------------------------------------------------- + + systemd.services.my-project = { + description = "MY_PROJECT application service"; + + # Start after the network and (if used) PostgreSQL are ready. + after = [ "network.target" "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + # --- Replace this with your actual start command --- + # Examples: + # Node.js: "${pkgs.nodejs}/bin/node ${cfg.dataDir}/server.js" + # Python: "${pkgs.python3}/bin/python ${cfg.dataDir}/app.py" + # Binary: "${pkgs.my-package}/bin/my-binary" + ExecStart = "${pkgs.nodejs}/bin/node ${cfg.dataDir}/server.js"; + + # Working directory for the process. + WorkingDirectory = cfg.dataDir; + + # DynamicUser: systemd creates an unprivileged user automatically. + # The user has no fixed UID and cannot log in. Good for most services. + # Set to false if you need a persistent user (e.g. for file ownership). + DynamicUser = true; + + # Give the dynamic user write access to the data directory. + StateDirectory = "my-project"; + StateDirectoryMode = "0750"; + + # Load secrets from a file at runtime (optional). + # The contents are injected as environment variables. + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; + + # Hardening — remove lines you don't need, but keep as many as possible. + NoNewPrivileges = true; + PrivateTmp = true; # /tmp is isolated from other services + ProtectSystem = "strict"; + ProtectHome = true; + RestrictSUIDSGID = true; + LockPersonality = true; + RestrictNamespaces = true; + + # Restart automatically if the process crashes. + Restart = "on-failure"; + RestartSec = "5s"; + }; + + # Environment variables that are safe to be in the Nix store. + # For secrets, use environmentFile above instead. + environment = { + NODE_ENV = "production"; + PORT = toString cfg.port; + DATA_DIR = cfg.dataDir; + }; + }; + + # -------------------------------------------------------------------------- + # Nginx Virtual Host + # Terminates TLS and proxies requests to the local application port. + # -------------------------------------------------------------------------- + + services.nginx = { + # Nginx is enabled automatically when virtualHosts is non-empty, + # but being explicit is clearer. + enable = true; + + virtualHosts.${cfg.domain} = { + + # Redirect all plain HTTP to HTTPS. + forceSSL = true; + + # Let NixOS manage the Let's Encrypt certificate automatically. + # Renewal is handled by a systemd timer — no manual cron needed. + # Requires: security.acme.acceptTerms = true and defaults.email set + # in your central configuration.nix. + enableACME = true; + + # --- Main reverse proxy location --- + locations."/" = { + proxyPass = "http://127.0.0.1:${toString cfg.port}"; + + # Needed for apps that use WebSockets (e.g. live reload, chat). + # Remove these two lines if your app does not use WebSockets. + proxyWebsockets = true; + extraConfig = '' + proxy_read_timeout 60s; + ''; + }; + + # --- Optional: serve static files directly from Nginx --- + # Uncomment and adjust if your app has a public assets directory. + # Serving static files from Nginx is faster than going through Node/Python. + # + # locations."/static/" = { + # alias = "${cfg.dataDir}/static/"; + # extraConfig = '' + # expires 30d; + # add_header Cache-Control "public, immutable"; + # ''; + # }; + + # --- Optional: block access to sensitive paths --- + # locations."~ /\\." = { + # extraConfig = "deny all;"; + # }; + }; + }; + + # -------------------------------------------------------------------------- + # PostgreSQL Database (optional) + # Uncomment this block if your project needs a database. + # -------------------------------------------------------------------------- + + # services.postgresql = { + # enable = true; + # + # # Creates the database and user if they don't exist yet. + # ensureDatabases = [ "my-project" ]; + # ensureUsers = [{ + # name = "my-project"; + # ensureDBOwnership = true; + # }]; + # }; + + # -------------------------------------------------------------------------- + # Firewall + # Ports 80 and 443 must be open for Nginx and Let's Encrypt to work. + # If these are already opened in configuration.nix, remove this block. + # -------------------------------------------------------------------------- + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + }; + + # ============================================================================ + # CENTRAL CONFIGURATION.NIX REQUIREMENTS + # + # Make sure these are set somewhere in your configuration.nix (not here, + # so they are shared across all project modules without duplication): + # + # services.nginx = { + # recommendedGzipSettings = true; + # recommendedOptimisation = true; + # recommendedTlsSettings = true; + # recommendedProxySettings = true; + # }; + # + # security.acme = { + # acceptTerms = true; + # defaults.email = "your@email.com"; + # }; + # + # ============================================================================ + +}