This commit is contained in:
Philip Peterson 2026-06-09 03:59:35 -07:00
parent f741ce7770
commit dbcc39624f
20 changed files with 730 additions and 178 deletions

24
nixos/aliases.zsh Normal file
View file

@ -0,0 +1,24 @@
alias gco='git checkout'
alias gb='git branch'
alias gs='git status'
alias gpcb='git push origin $(git rev-parse --abbrev-ref HEAD)'
function git() {
if [[ $1 == clone && $# -ge 2 ]]; then
local url=$2
# expand foo/bar -> https://github.com/foo/bar.git (no protocol, no dots in host part)
if [[ $url =~ ^[A-Za-z0-9_-]+/[A-Za-z0-9_.-]+$ ]]; then
url="https://github.com/${url%.git}.git"
echo "Cloning $url" >&2
fi
command git clone "$url" "${@:3}"
else
command git "$@"
fi
}
alias u='cd ..'
alias uu='cd ../..'
alias uuu='cd ../../..'
alias uuuu='cd ../../../..'
alias uuuuu='cd ../../../../..'

View file

@ -0,0 +1,74 @@
{ pkgs, ... }:
{
project.name = "paperless";
networks.paperless.external = false;
services = {
redis = {
service = {
image = "redis:7";
container_name = "paperless-redis";
restart = "unless-stopped";
networks = ["paperless"];
volumes = [
"/var/paperless/redis:/data"
];
};
};
db = {
service = {
image = "postgres:16";
container_name = "paperless-db";
restart = "unless-stopped";
networks = ["paperless"];
volumes = [
"/var/paperless/postgres:/var/lib/postgresql/data"
];
environment = {
POSTGRES_DB = "paperless";
POSTGRES_USER = "paperless";
POSTGRES_PASSWORD = "paperless";
};
healthcheck = {
test = ["CMD-SHELL" "pg_isready -U paperless -d paperless"];
interval = "5s";
timeout = "5s";
retries = 20;
};
};
};
webserver = {
service = {
image = "ghcr.io/paperless-ngx/paperless-ngx:latest";
container_name = "paperless-ngx";
restart = "unless-stopped";
networks = ["paperless"];
depends_on = ["db" "redis"];
ports = ["127.0.0.1:8000:8000"];
volumes = [
"/var/paperless/data:/usr/src/paperless/data"
"/var/paperless/media:/usr/src/paperless/media"
"/var/paperless/export:/usr/src/paperless/export"
"/var/paperless/consume:/usr/src/paperless/consume"
];
environment = {
PAPERLESS_REDIS = "redis://redis:6379";
PAPERLESS_DBHOST = "db";
PAPERLESS_DBNAME = "paperless";
PAPERLESS_DBUSER = "paperless";
PAPERLESS_DBPASS = "paperless";
# FIXME: replace with your actual domain
PAPERLESS_URL = "https://paperless.bobs-home.com";
PAPERLESS_TIME_ZONE = "America/Chicago";
PAPERLESS_OCR_LANGUAGE = "eng";
USERMAP_UID = "1000";
USERMAP_GID = "1000";
};
env_file = ["/run/agenix/paperless"];
};
};
};
}

View file

@ -0,0 +1,3 @@
import <nixpkgs> {
system = "x86_64-linux";
}

View file

@ -0,0 +1,30 @@
{ pkgs, ... }:
{
project.name = "pluto";
networks.pluto.external = false;
services = {
app = {
service = {
image = "julia:1.11";
container_name = "pluto";
restart = "unless-stopped";
networks = ["pluto"];
command = [
"sh"
"-c"
"julia -e 'using Pkg; Pkg.add(\"Pluto\"); import Pluto; Pluto.run(host=\"0.0.0.0\", port=1234, launch_browser=false)'"
];
volumes = [
"/var/pluto/notebooks:/notebooks"
"/var/pluto/julia-depot:/root/.julia"
];
environment = {
JULIA_NUM_THREADS = "auto";
};
ports = ["127.0.0.1:1234:1234"];
};
};
};
}

View file

@ -0,0 +1,3 @@
import <nixpkgs> {
system = "x86_64-linux";
}

View file

@ -0,0 +1,55 @@
{ pkgs, ... }:
{
# Example multi-service web app: WordPress + MySQL.
# Replace the image and env vars for your own application.
project.name = "riverside";
networks.riverside.external = false;
services = {
app = {
service = {
# FIXME: replace with your actual application image
image = "wordpress:6-php8.2-apache";
container_name = "my-webapp";
restart = "unless-stopped";
networks = ["riverside"];
environment = {
WORDPRESS_DB_HOST = "db";
WORDPRESS_DB_NAME = "wordpress";
WORDPRESS_DB_USER = "wordpress";
WORDPRESS_DB_PASSWORD = "wordpress";
};
volumes = [
"/var/riverside/files:/var/www/html/wp-content"
];
ports = ["3011:80"];
depends_on = ["db"];
};
};
db = {
service = {
image = "mysql:8";
container_name = "my-webapp-db";
restart = "unless-stopped";
networks = ["riverside"];
environment = {
MYSQL_DATABASE = "wordpress";
MYSQL_USER = "wordpress";
MYSQL_PASSWORD = "wordpress";
MYSQL_RANDOM_ROOT_PASSWORD = "1";
};
volumes = [
"/var/riverside/db:/var/lib/mysql"
];
healthcheck = {
test = ["CMD-SHELL" "mysqladmin ping -h localhost"];
interval = "5s";
timeout = "5s";
retries = 20;
};
};
};
};
}

View file

@ -0,0 +1,3 @@
import <nixpkgs> {
system = "x86_64-linux";
}

View file

@ -0,0 +1,35 @@
{ pkgs, ... }:
{
project.name = "forgejo";
networks = {
forgejo = {
external = false;
};
};
services = {
server = {
image = {
enableRecommendedContents = true;
};
service = {
image = "codeberg.org/forgejo/forgejo:9";
container_name = "forgejo";
environment = {
USER_UID = "1000";
USER_GID = "1000";
};
restart = "always";
networks = ["forgejo"];
volumes = [
"/var/forge:/data"
"/etc/timezone:/etc/timezone:ro"
"/etc/localtime:/etc/localtime:ro"
];
ports = [
"3000:3000"
"2200:22"
];
};
};
};
}

View file

@ -0,0 +1,10 @@
## This file is the default location for Arion to look for Nixpkgs when
## bootstrapping a deployment configuration.
#import ../ {}
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
import <nixpkgs> {
# We specify the architecture explicitly. Use a Linux remote builder when
# calling arion from other platforms.
system = "x86_64-linux";
}

View file

@ -16,24 +16,22 @@
interval:
interval: 10m
credentials:
private_key: /root/.ssh/id_rsa.pem
private_key_path: true
username: git
private_key:
file: /root/.ssh/id_rsa.pem
'';
};
# FIXME: replace these with your own repositories
configFiles = [
(makeConfigFile
["com" "philippeterson"]
"git@github.com:philip-peterson/philippeterson.com.git"
"master")
["com" "bobs-home"]
"git@github.com:bob-smith/bobs-website.git"
"main")
(makeConfigFile
["com" "quinefoundation" "blog"]
"git@github.com:philip-peterson/blog.git"
"master")
(makeConfigFile
["atcsim"]
"git@github.com:philip-peterson/ATC-Sim.git"
"master")
["com" "bobs-global"]
"git@github.com:bob-smith/landing-page.git"
"main")
];
configDir =
@ -42,7 +40,6 @@
} ''
mkdir -p $out
# Loop over the config files and write each one to $out
${lib.concatStringsSep "\n" (map (cf: ''
echo "${cf.text}" > $out/${cf.name}
chmod 0644 $out/${cf.name}
@ -59,7 +56,7 @@ in {
Restart = "always";
RestartSec = "0";
User = "root";
Group = "root";
Group = "repo-data";
};
};

View file

@ -1,3 +1,21 @@
{pkgs, ...}: {
networking.firewall.allowedTCPPorts = [80 22 443];
networking.firewall.allowedTCPPorts = [
80 # nginx/http
22 # ssh
443 # ssl
2200 # forgejo ssh
3000 # forgejo http
8082 # webdav
];
# Allow DNS from all podman bridge networks (10.89.0.0/16).
# NixOS auto-adds a rule for podman0 but not for networks created by
# docker-compose/arion (podman1, podman2, …).
networking.firewall.extraCommands = ''
iptables -I nixos-fw -s 10.89.0.0/16 -p udp --dport 53 -j nixos-fw-accept
iptables -I nixos-fw -s 10.89.0.0/16 -p tcp --dport 53 -j nixos-fw-accept
iptables -I nixos-fw -s 10.89.0.0/16 -p tcp --dport 5901 -j nixos-fw-accept
'';
}

View file

@ -1,8 +1,7 @@
{
description = "NixOS configuration";
# 24.05
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
inputs.nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
inputs.agenix.url = "github:ryantm/agenix";
@ -13,7 +12,7 @@
inputs.nixpkgs.follows = "nixpkgs";
};
inputs.home-manager.url = "github:nix-community/home-manager/release-23.11";
inputs.home-manager.url = "github:nix-community/home-manager/release-24.11";
inputs.home-manager.inputs.nixpkgs.follows = "nixpkgs";
inputs.nur.url = "github:nix-community/NUR";
@ -24,6 +23,9 @@
inputs.disko.url = "github:nix-community/disko";
inputs.disko.inputs.nixpkgs.follows = "nixpkgs";
inputs.arion.url = "github:hercules-ci/arion";
inputs.arion.inputs.nixpkgs.follows = "nixpkgs";
outputs = inputs:
with inputs; let
system = "x86_64-linux";
@ -37,7 +39,7 @@
};
overlays = [
nur.overlay
nur.overlays.default
(_final: prev: {
# this allows us to reference pkgs.unstable
unstable = import nixpkgs-unstable {
@ -89,18 +91,19 @@
[
(configurationDefaults specialArgs)
home-manager.nixosModules.home-manager
arion.nixosModules.arion
]
++ modules;
};
in {
nixosConfigurations.nixos = mkNixosConfiguration {
hostname = "pw-mainframe";
# FIXME: replace with your server's hostname
hostname = "bobs-server";
username = "ironmagma";
args = {
nixPkgs = import nixpkgs {inherit system overlays;};
};
modules = [
disko.nixosModules.disko
agenix.nixosModules.age
./hetzner.nix
./linux.nix

2
nixos/gitconfig Normal file
View file

@ -0,0 +1,2 @@
[alias]
co = checkout

View file

@ -2,22 +2,46 @@
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disk-config.nix
];
# Fixes iotop
boot.kernel.sysctl = {"kernel.task_delayacct" = 1;};
boot.loader.grub = {
# no need to set devices, disko will add all devices that have a EF02 partition to the list already
# devices = [ ];
efiSupport = true;
efiInstallAsRemovable = true;
enable = true;
device = "/dev/sda";
efiSupport = false;
};
# FIXME: replace with your actual disk UUID (find with: lsblk -f)
fileSystems."/" = {
device = "/dev/disk/by-uuid/YOUR-DISK-UUID-HERE";
fsType = "ext4";
};
networking.useDHCP = false;
systemd.network = {
enable = true;
# FIXME: replace enp1s0 with your actual NIC name (check with: ip link)
networks."10-enp1s0" = {
matchConfig.Name = "enp1s0";
networkConfig.DHCP = "ipv4";
dhcpV4Config = {
UseGateway = false;
UseRoutes = false;
};
routes = [
{routeConfig = {Gateway = "172.31.1.1"; GatewayOnLink = true;};}
];
};
};
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
# FIXME: set to false once your SSH keys are in place
settings.PasswordAuthentication = true;
settings.PermitRootLogin = "yes";
settings.AllowUsers = ["root"];
extraConfig = ''
PrintLastLog no
'';
@ -32,7 +56,7 @@
type = "ed25519";
}
# For secrets
# For secrets decryption (agenix)
{
path = "/root/.ssh/id_rsa_nix";
type = "ed25519";

View file

@ -11,9 +11,9 @@
}: let
ddnsPkg = import ./invoke-ddns {inherit pkgs;};
startSeq = builtins.fromJSON ''"\u001b[7m"''; # Start inverted color
endSeq = builtins.fromJSON ''"\u001b[27m"''; # End inverted color
motd = "${startSeq} Welcome to the Peterson Mainframe! Look, touch, but DO NOT LICK. ${endSeq}";
startSeq = builtins.fromJSON ''""'';
endSeq = builtins.fromJSON ''""'';
motd = "${startSeq} Welcome to Bob's Server! ${endSeq}";
nixPkgs = specialArgs.nixPkgs;
ourRustVersion = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.complete);
@ -33,15 +33,41 @@
in {
imports = [
(import ./cloned_repos {inherit pkgs pullomatic lib;})
(import ./nginx.nix {inherit pkgs lib config;})
(import ./firewall.nix {inherit pkgs;})
(import ./nginx.nix {inherit pkgs lib config;})
(import ./system/users.nix {inherit pkgs config lib nix-index-database;})
];
time.timeZone = "America/Anchorage";
# FIXME: set your timezone
time.timeZone = "America/Chicago";
age.secrets.nearlyfreespeech.file = ./secrets/nearlyfreespeech.age;
age.secrets.nearlyfreespeech.owner = "root";
age.secrets = {
nearlyfreespeech = {
file = ./secrets/nearlyfreespeech.age;
owner = "root";
};
webdav = {
file = ./secrets/webdav.age;
owner = "root";
};
# Optional: only needed if you use claude-code or other Anthropic tools
anthropic-api-key = {
file = ./secrets/anthropic-api-key.age;
owner = "root";
};
forgejo-runner-token = {
file = ./secrets/forgejo-runner-token.age;
owner = "root";
};
paperless = {
file = ./secrets/paperless.age;
owner = "root";
};
};
environment.systemPackages = [
ddnsPkg
@ -53,6 +79,11 @@ in {
pkgs.util-linux
pkgs.iotop
pkgs.rust-bin.stable.latest.default
pkgs.wget
pkgs.tmux
# Uncomment to install Claude Code on the server:
# pkgs.unstable.claude-code
];
swapDevices = [
@ -62,15 +93,81 @@ in {
}
];
virtualisation.arion = {
backend = "podman-socket";
projects.forgejo.settings = import ./arion/arion-compose.nix;
projects.riverside.settings = import ./arion-riverside/arion-compose.nix;
projects.pluto.settings = import ./arion-pluto/arion-compose.nix;
projects.paperless.settings = import ./arion-paperless/arion-compose.nix;
};
# The arion NixOS module sets backend = "podman-socket" but doesn't inject
# DOCKER_HOST into the service units; docker CLI falls back to /var/run/docker.sock
# (no daemon). Point it at the podman-compatible socket instead.
systemd.services.arion-forgejo.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-riverside.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-pluto.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-paperless.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
services.gitea-actions-runner.instances."ubuntu" = {
enable = true;
name = "ubuntu";
# FIXME: point at your Forgejo instance
url = "http://localhost:3000";
tokenFile = config.age.secrets.forgejo-runner-token.path;
labels = [
"ubuntu-latest:host"
"ubuntu-22.04:host"
"ubuntu-20.04:host"
];
};
users.users.gitea-runner = {
isSystemUser = true;
group = "gitea-runner";
extraGroups = ["podman"];
home = "/var/lib/gitea-runner";
createHome = true;
};
users.groups.gitea-runner = {};
systemd.services.gitea-runner-ubuntu = {
after = ["arion-forgejo.service"];
wants = ["arion-forgejo.service"];
environment.PATH = lib.mkForce (
"${pkgs.podman}/bin:${pkgs.git}/bin:${pkgs.nodejs}/bin:/run/current-system/sw/bin:/run/wrappers/bin"
);
serviceConfig = {
DynamicUser = lib.mkForce false;
User = lib.mkForce "gitea-runner";
Group = lib.mkForce "gitea-runner";
};
};
systemd.tmpfiles.rules = [
"d /home/ironmagma/.config 0755 ${username} users"
"d /root/.config 0755 ${username} users"
"d /var/pluto/notebooks 0755 root root"
"d /var/pluto/julia-depot 0755 root root"
"d /var/paperless/data 0755 root root"
"d /var/paperless/media 0755 root root"
"d /var/paperless/export 0755 root root"
"d /var/paperless/consume 0755 root root"
"d /var/paperless/postgres 0755 root root"
"d /var/paperless/redis 0755 root root"
"d /var/riverside/files 0755 root root"
"d /var/riverside/db 0755 root root"
"d /var/lib/gitea-runner/ubuntu 0755 gitea-runner gitea-runner"
];
networking.hostName = "${hostname}";
# FIXME: change your shell here if you don't want zsh
programs.zsh.enable = true;
programs.zsh.shellInit = ''
# Uncomment if you have an anthropic-api-key secret configured:
# export ANTHROPIC_API_KEY=$(cat ${config.age.secrets.anthropic-api-key.path})
'';
environment.pathsToLink = ["/share/zsh"];
environment.shells = [pkgs.zsh];
@ -82,32 +179,26 @@ in {
system.stateVersion = "22.05";
virtualisation.docker = {
virtualisation.podman = {
enable = true;
enableOnBoot = true;
autoPrune.enable = true;
defaultNetwork.settings.dns_enabled = true;
};
# KDE Plasma spawns many threads. Raise machine.slice TasksMax so podman
# scopes don't get capped at the systemd default (~9286).
systemd.slices.machine.sliceConfig.TasksMax = "infinity";
virtualisation.containers.containersConf.settings.containers.pids_limit = 0;
virtualisation.oci-containers = {
backend = "docker";
backend = "podman";
containers = {
"hello" = {
autoStart = true;
image = "nginxdemos/hello";
#user = "root:jellyfin";
volumes = [
];
ports = ["8081:80"];
};
"navidrome" = {
autoStart = true;
environment = {
"TZ" = "America/Anchorage";
"TZ" = "America/Chicago";
"PUID" = "1000";
"PGID" = "100";
"ND_SCANSCHEDULE" = "1h";
"ND_LOGLEVEL" = "info";
"ND_SESSIONTIMEOUT" = "24h";
@ -124,29 +215,41 @@ in {
"webdav" = {
autoStart = true;
image = "dgraziotin/nginx-webdav-nononsense";
#user = "root:jellyfin";
volumes = [
"/mnt/webdav/data:/data"
"/mnt/webdav/config:/config"
];
environment = {
"WEBDAV_USERNAME" = "foo";
# TODO
"WEBDAV_PASSWORD" = "bar";
"TZ" = "America/Anchorage";
"WEBDAV_USERNAME" = "bob";
"TZ" = "America/Chicago";
"PUID" = "60"; # nginx user
"PGID" = "60"; # nginx group
};
environmentFiles = [config.age.secrets.webdav.path];
ports = ["8082:80"];
};
"blog-quine" = {
autoStart = true;
image = "quineglobal/blog-quine:latest";
volumes = [];
environment = {};
ports = ["3010:8080"];
};
"hyper-quineglobal-com" = {
autoStart = true;
image = "forge.quinefoundation.com/ironmagma/hyper-quineglobal-com:latest";
volumes = [];
environment = {};
ports = ["3013:8081"];
};
};
};
nix = {
settings = {
trusted-users = [username];
accept-flake-config = true;
auto-optimise-store = true;
};
@ -163,7 +266,7 @@ in {
"/nix/var/nix/profiles/per-user/root/channels"
];
package = pkgs.nixFlakes;
package = pkgs.nixVersions.stable;
extraOptions = ''experimental-features = nix-command flakes'';
gc = {
@ -172,15 +275,55 @@ in {
};
};
# HTTPS
# HTTPS — DNS challenge via NearlyFreeSpeech for domains that need certs
# before nginx is running (avoids circular systemd dependency).
# FIXME: replace all domain names with your own.
security.acme = {
acceptTerms = true;
defaults.email = "peterson@sent.com";
certs."philippeterson.com" = {
defaults.email = "bob@bobs-home.com";
certs."bobs-home.com" = {
dnsProvider = "nearlyfreespeech";
environmentFile = config.age.secrets."nearlyfreespeech".path;
webroot = null;
};
certs."webdav.bobs-home.com" = {
dnsProvider = "nearlyfreespeech";
environmentFile = config.age.secrets."nearlyfreespeech".path;
webroot = null;
};
certs."pluto.bobs-home.com" = {
dnsProvider = "nearlyfreespeech";
environmentFile = config.age.secrets."nearlyfreespeech".path;
webroot = null;
group = config.services.nginx.group;
};
certs."paperless.bobs-home.com" = {
dnsProvider = "nearlyfreespeech";
environmentFile = config.age.secrets."nearlyfreespeech".path;
webroot = null;
group = config.services.nginx.group;
};
};
# Break the systemd ordering cycle that deadlocks nixos-rebuild switch.
# DNS-challenge certs don't need nginx running; swap the real acme services
# out of nginx's After list for the selfsigned fallback services instead.
# Every domain with enableACME = true in nginx.nix must appear here.
# FIXME: update this list whenever you add or remove ACME-enabled vhosts.
systemd.services.nginx.after = lib.mkForce [
"network.target"
"acme-selfsigned-bobs-home.com.service"
"acme-selfsigned-www.bobs-home.com.service"
"acme-selfsigned-bobs-hobby.com.service"
"acme-selfsigned-www.bobs-hobby.com.service"
"acme-selfsigned-hyper.quineglobal.com.service"
"acme-selfsigned-forge.quinefoundation.com.service"
"acme-selfsigned-webdav.bobs-home.com.service"
"acme-selfsigned-pluto.bobs-home.com.service"
"acme-selfsigned-paperless.bobs-home.com.service"
];
}

View file

@ -7,49 +7,192 @@
services.nginx = {
enable = true;
virtualHosts = {
"_default" = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
serverName = "_";
extraConfig = ''
deny all;
return 444;
'';
virtualHosts = let
# Static site embedded in the Nix store at build time.
# Replace static-site/ with your own files.
hobbyRoot = pkgs.runCommand "bobs-hobby-web" {} ''
mkdir -p $out
cp ${./static-site/index.html} $out/index.html
'';
# Wraps a vhost config to also create a www. redirect.
withWww = domain: cfg: {
"${domain}" = cfg;
"www.${domain}" = {
enableACME = true;
addSSL = cfg.addSSL or true;
forceSSL = cfg.forceSSL or false;
locations."/" = {
extraConfig = ''
return 301 https://${domain}$request_uri;
'';
};
};
};
in
{
# Deny anything that doesn't match a named vhost.
"_default" = {
listen = [
{
addr = "0.0.0.0";
port = 80;
}
{
addr = "[::]";
port = 80;
}
];
serverName = "_";
extraConfig = ''
deny all;
return 444;
'';
};
"philippeterson.com" = {
enableACME = true; # Enable Let's Encrypt certificate for HTTPS
forceSSL = false; # Redirect HTTP to HTTPS?
# Quine Global landing page — served from a git repo pulled by pullomatic.
"quineglobal.com" = {
enableACME = false;
forceSSL = false;
addSSL = false;
locations."/" = {
extraConfig = ''
index index.html index.htm;
root /etc/pullomatic/com_quineglobal;
'';
};
};
# Quine Global blog — proxied to a container on port 3010.
"blog.quineglobal.com" = {
enableACME = false;
forceSSL = false;
addSSL = false;
locations."/" = {
proxyPass = "http://127.0.0.1:3010/";
};
};
# Hyper — proxied to a container on port 3013.
"hyper.quineglobal.com" = {
enableACME = true;
forceSSL = false;
addSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3013/";
};
};
# WebDAV — proxy to the webdav container.
# FIXME: replace domain
"webdav.bobs-home.com" = {
serverName = "webdav.bobs-home.com";
useACMEHost = "webdav.bobs-home.com";
onlySSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8082/";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
client_max_body_size 100M;
proxy_buffering off;
proxy_cache off;
'';
};
};
# Paperless-ngx document management.
# FIXME: replace domain
"paperless.bobs-home.com" = {
useACMEHost = "paperless.bobs-home.com";
onlySSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8000/";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
client_max_body_size 100M;
'';
};
};
# Pluto — Julia notebook server.
# FIXME: replace domain
"pluto.bobs-home.com" = {
useACMEHost = "pluto.bobs-home.com";
onlySSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:1234/";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
'';
};
};
# Forgejo git forge — HTTPS vhost.
"forge.quinefoundation.com-https" = {
serverName = "forge.quinefoundation.com";
enableACME = true;
onlySSL = true;
extraConfig = ''
client_max_body_size 0;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:3000/";
};
};
# Forgejo — HTTP vhost that redirects to HTTPS.
"forge.quinefoundation.com-http" = {
serverName = "forge.quinefoundation.com";
listen = [
{
addr = "0.0.0.0";
port = 80;
}
{
addr = "[::]";
port = 80;
}
];
locations."/" = {
extraConfig = ''
return 302 https://$host$request_uri;
'';
};
};
}
# Personal site with PHP — served from a git repo pulled by pullomatic.
# FIXME: replace domain and adjust URL rewrite rules for your site.
// (withWww "bobs-home.com" {
enableACME = true;
forceSSL = false;
addSSL = true;
root = "/etc/pullomatic/com_philippeterson";
root = "/etc/pullomatic/com_bobs-home";
locations."~ /.git(/.*)$ " = {
extraConfig = ''
deny all;
return 404;
'';
};
locations."~ ^/games/atcsim(/[^/\\s]*)*$" = {
extraConfig = ''
index index.html index.htm;
rewrite ^/games/atcsim/?$ "/index.html" break;
rewrite ^/games/atcsim(?<query>(/[^/\\s]*)*)$ "$query" break;
root /etc/pullomatic/atcsim;
'';
};
locations."~ ^/echo(?<query>((/[^/\\s]*)*))$" = {
extraConfig = ''
add_header Content-Type text/plain;
return 200 "$query";
'';
};
locations."/" = {
extraConfig = ''
try_files $uri $uri.php $uri/ =404;
@ -58,7 +201,6 @@
rewrite ^/resume$ /resume.php last;
'';
};
locations."~ \.php$" = {
extraConfig = ''
include ${pkgs.nginx}/conf/fastcgi.conf;
@ -66,53 +208,16 @@
fastcgi_pass unix:${config.services.phpfpm.pools.main.socket};
'';
};
};
"blog.quinefoundation.com" = {
})
# Static hobby site served directly from the Nix store.
# FIXME: replace domain and put your files in static-site/
// (withWww "bobs-hobby.com" {
enableACME = true;
forceSSL = false;
addSSL = true;
root = hobbyRoot;
});
root = "/etc/pullomatic/com_quinefoundation_blog/markdown-blog";
locations."~ /.git(/.*)$ " = {
extraConfig = ''
deny all;
return 404;
'';
};
locations."~ ^/static(/.*)?$" = {
extraConfig = ''
autoindex on;
root /etc/pullomatic/com_quinefoundation_blog/static;
rewrite ^/static(?<query>(/[^/\\s]*)*)$ "$query" break;
'';
};
locations."/" = {
extraConfig = ''
rewrite ^/?$ /blog-posts-list.php last;
rewrite ^/post/?$ /blog-posts-list.php last;
rewrite ^/about/?$ /about.php last;
rewrite ^/credits/?$ /credits.php last;
rewrite ^/post/([-a-zA-Z0-9]*)$ /blog-page.php?page=$1.md last;
rewrite ^/rss.xml$ /rss.php last;
try_files $uri $uri/ =404;
index index.php index.html index.htm;
'';
};
locations."~ \.php$" = {
extraConfig = ''
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:${config.services.phpfpm.pools.main.socket};
'';
};
};
};
# Optionally configure additional options
recommendedGzipSettings = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
@ -124,8 +229,6 @@
user = "nginx";
group = "nginx";
settings = {
# listen = /run/phpfpm.sock
# "listen.mode = 0660
"listen.owner" = "nginx";
"listen.group" = "nginx";
"pm" = "dynamic";
@ -134,7 +237,6 @@
"pm.min_spare_servers" = 5;
"pm.max_spare_servers" = 20;
"pm.max_requests" = 500;
"php_admin_value[error_log]" = "stderr";
"php_admin_flag[log_errors]" = true;
"catch_workers_output" = true;

View file

@ -1,8 +1,27 @@
{}: let
mainframePublicKey = builtins.toString "../keys/mainframe.pub";
let
mainframePublicKey = builtins.readFile ../keys/mainframe.pub;
in {
# This .age file should contain the following environment variables:
# NEARLYFREESPEECH_API_KEY
# NEARLYFREESPEECH_LOGIN
# NEARLYFREESPEECH_API_KEY, NEARLYFREESPEECH_LOGIN
# Used for DNS-challenge ACME cert provisioning via the NearlyFreeSpeech DNS provider.
"./nearlyfreespeech.age".publicKeys = [mainframePublicKey];
# WEBDAV_PASSWORD
"./webdav.age".publicKeys = [mainframePublicKey];
# ANTHROPIC_API_KEY (optional — only needed if you use claude-code or other Anthropic tools)
"./anthropic-api-key.age".publicKeys = [mainframePublicKey];
# TOKEN=<forgejo runner registration token from Forgejo admin>
"./forgejo-runner-token.age".publicKeys = [mainframePublicKey];
# PAPERLESS_SECRET_KEY=<long random string>
# PAPERLESS_ADMIN_USER=admin
# PAPERLESS_ADMIN_PASSWORD=<password>
# PAPERLESS_ADMIN_EMAIL=<email>
"./paperless.age".publicKeys = [mainframePublicKey];
# Add any app-specific secrets here, e.g.:
# DATABASE_URL=<postgres dsn>
# APP_SECRET=<secret>
# "./my-webapp.age".publicKeys = [mainframePublicKey];
}

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bob's Hobby Site</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 60px auto; padding: 0 20px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Bob's Hobby Site</h1>
<p>
This page is served directly from the Nix store — the HTML is embedded in the
system closure at build time, so there is no external file to manage.
</p>
<p>Replace this file with your own static content.</p>
</body>
</html>

View file

@ -16,6 +16,7 @@
jq
killall
tmux
tree
unzip
vim
wget
@ -27,6 +28,7 @@
go
nodejs
python3
clojure
alejandra # nix formatter
];
@ -36,7 +38,7 @@
joinedString = lib.concatStringsSep " " pubKeys;
in {
imports = [
nix-index-database.hmModules.nix-index
nix-index-database.homeModules.nix-index
];
home = {
@ -47,12 +49,12 @@ in {
sessionVariables.EDITOR = "vim";
sessionVariables.SHELL = "/etc/profiles/per-user/${username}/bin/zsh";
packages = lib.mkDefault (
stable-packages
++ unstable-packages
);
packages = stable-packages ++ unstable-packages;
file.".ssh/known_hosts".text = joinedString;
file.".ssh/known_hosts" = {
text = joinedString;
force = true;
};
};
programs = {
@ -100,8 +102,9 @@ in {
side-by-side = true;
navigate = true;
};
userEmail = "1326208+philip-peterson@users.noreply.github.com";
userName = "philip-peterson";
# FIXME: replace with your own name and email
userEmail = "bob@bobs-home.com";
userName = "bob-smith";
extraConfig = {
push = {
default = "current";
@ -114,15 +117,16 @@ in {
colorMoved = "default";
};
safe = {
directory = "/var/petersweb-infra";
directory = "/var/bobs-infra";
};
};
includes = [{path = toString ../../gitconfig;}];
};
zsh = {
enable = true;
autocd = true;
enableAutosuggestions = true;
autosuggestion.enable = true;
enableCompletion = true;
defaultKeymap = "emacs";
history.size = 10000;
@ -150,17 +154,9 @@ in {
];
shellAliases = {
"u" = "cd ..";
"uu" = "cd ../..";
"uuu" = "cd ../../..";
"uuuu" = "cd ../../../..";
gs = "git status";
gc = "nix-collect-garbage --delete-old";
refresh = "source ~/.zshrc";
show_path = "echo $PATH | tr ':' '\n'";
gst = "git status";
gco = "git checkout";
};
envExtra = ''
@ -168,6 +164,7 @@ in {
'';
initExtra = ''
${builtins.readFile ../../aliases.zsh}
bindkey '^p' history-search-backward
bindkey '^n' history-search-forward
bindkey '^e' end-of-line
@ -177,21 +174,12 @@ in {
bindkey ";5D" backward-word
zstyle ':completion:*:*:*:*:*' menu select
# Complete . and .. special directories
zstyle ':completion:*' special-dirs true
zstyle ':completion:*' list-colors ""
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01'
# disable named-directories autocompletion
zstyle ':completion:*:cd:*' tag-order local-directories directory-stack path-directories
# Use caching so that commands like apt and dpkg complete are useable
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path "$XDG_CACHE_HOME/zsh/.zcompcache"
# Don't complete uninteresting users
zstyle ':completion:*:*:*:users' ignored-patterns \
adm amanda apache at avahi avahi-autoipd beaglidx bin cacti canna \
clamav daemon dbus distcache dnsmasq dovecot fax ftp games gdm \
@ -201,10 +189,7 @@ in {
operator pcap polkitd postfix postgres privoxy pulse pvm quagga radvd \
rpc rpcuser rpm rtkit scard shutdown squid sshd statd svn sync tftp \
usbmux uucp vcsa wwwrun xfs '_*'
# ... unless we really want to.
zstyle '*' single-ignored complete
# https://thevaluable.dev/zsh-completion-guide-examples/
zstyle ':completion:*' completer _extensions _complete _approximate
zstyle ':completion:*:descriptions' format '%F{green}-- %d --%f'
zstyle ':completion:*' group-name ""
@ -212,7 +197,6 @@ in {
zstyle ':completion:*' squeeze-slashes true
zstyle ':completion:*' matcher-list "" 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
# mkcd is equivalent to takedir
function mkcd takedir() {
mkdir -p $@ && cd ''${@:$#}
}
@ -243,8 +227,6 @@ in {
}
WORDCHARS='*?[]~=&;!#$%^(){}<>'
# fixes duplication of commands when using tab-completion
export LANG=C.UTF-8
'';
};

View file

@ -12,10 +12,11 @@
authorizedKeys,
homeConfig ? null,
isNormalUser ? true,
enableHomeManager ? false,
}: {
extraGroups = extraGroups ++ [username];
home-manager.users.${username} = homeConfig;
home-manager.users.${username} = if enableHomeManager then homeConfig else {};
users.users.${username} = {
isNormalUser = isNormalUser;
@ -38,8 +39,9 @@
isNormalUser = false;
username = "root";
home = "/root";
extraGroups = [];
extraGroups = ["podman"];
authorizedKeys = authorizedKeys;
enableHomeManager = true;
homeConfig = import ./home/root.nix {
username = "root";
inherit config pkgs nix-index-database lib;
@ -52,8 +54,10 @@
extraGroups = [
"wheel"
"docker"
"podman"
];
authorizedKeys = authorizedKeys;
enableHomeManager = false;
homeConfig = import ./home/ironmagma.nix {
username = "ironmagma";
inherit config pkgs nix-index-database lib;
@ -66,5 +70,6 @@ in {
};
users.users = rootUser.users.users // ironmagmaUser.users.users // {};
home-manager.users = rootUser.home-manager.users // ironmagmaUser.home-manager.users;
home-manager.users = rootUser.home-manager.users;
home-manager.backupFileExtension = "hm-backup";
}