Pro
This commit is contained in:
parent
4ab16345e1
commit
a33c7c7762
1 changed files with 247 additions and 0 deletions
247
project-skeleton.nix
Normal file
247
project-skeleton.nix
Normal file
|
|
@ -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";
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue