initial commit
This commit is contained in:
commit
90cff4f16a
59 changed files with 6855 additions and 0 deletions
409
Hetzner/axiomania.nix
Normal file
409
Hetzner/axiomania.nix
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [ ./common.nix ];
|
||||
|
||||
networking.hostName = "axiomania";
|
||||
|
||||
# ============================================================================
|
||||
# Firewall
|
||||
# ============================================================================
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 22 80 443 2222 ];
|
||||
allowedUDPPorts = [ 51820 53 ]; # Wireguard, Adguard DNS
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Forgejo
|
||||
# ============================================================================
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
database.type = "sqlite3";
|
||||
settings = {
|
||||
DEFAULT.APP_NAME = "Axiomania Git";
|
||||
|
||||
server = {
|
||||
DOMAIN = "git.axiomania.org";
|
||||
ROOT_URL = "https://git.axiomania.org/";
|
||||
HTTP_ADDR = "127.0.0.1";
|
||||
HTTP_PORT = 3000;
|
||||
SSH_DOMAIN = "git.axiomania.org";
|
||||
SSH_PORT = 2222;
|
||||
START_SSH_SERVER = true;
|
||||
};
|
||||
|
||||
service = {
|
||||
DISABLE_REGISTRATION = true;
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = true;
|
||||
};
|
||||
|
||||
repository = {
|
||||
DEFAULT_PRIVATE = "private";
|
||||
};
|
||||
|
||||
api = {
|
||||
ENABLE_SWAGGER = false;
|
||||
};
|
||||
|
||||
session = {
|
||||
COOKIE_SECURE = true;
|
||||
};
|
||||
|
||||
actions = {
|
||||
ENABLED = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Forgejo Actions Runner
|
||||
# ============================================================================
|
||||
# Requires a registration token from Forgejo.
|
||||
# Generate one: ssh root@178.104.15.221, then:
|
||||
# forgejo-runner register (follow prompts)
|
||||
# Or via Forgejo web UI: Site Administration > Runners > Create Runner
|
||||
# Then put the token in /var/lib/forgejo-runner/token
|
||||
services.gitea-actions-runner.instances.default = {
|
||||
enable = true;
|
||||
name = "axiomania";
|
||||
url = "https://git.axiomania.org";
|
||||
tokenFile = "/var/lib/forgejo-runner/token";
|
||||
labels = [ "native:host" ];
|
||||
hostPackages = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
git
|
||||
gnused
|
||||
nodejs
|
||||
nix
|
||||
];
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Vaultwarden (Bitwarden-compatible password manager)
|
||||
# ============================================================================
|
||||
services.vaultwarden = {
|
||||
enable = true;
|
||||
config = {
|
||||
DOMAIN = "https://vault.axiomania.org";
|
||||
SIGNUPS_ALLOWED = false;
|
||||
ROCKET_ADDRESS = "127.0.0.1";
|
||||
ROCKET_PORT = 8222;
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Navidrome (music streaming)
|
||||
# ============================================================================
|
||||
services.navidrome = {
|
||||
enable = true;
|
||||
settings = {
|
||||
Address = "127.0.0.1";
|
||||
Port = 4533;
|
||||
MusicFolder = "/var/lib/navidrome/music";
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Syncthing (file sync)
|
||||
# ============================================================================
|
||||
services.syncthing = {
|
||||
enable = true;
|
||||
openDefaultPorts = true;
|
||||
dataDir = "/var/lib/syncthing";
|
||||
settings.gui.insecureSkipHostcheck = true;
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Attic (Nix binary cache)
|
||||
# ============================================================================
|
||||
# Requires a secret generated on the server:
|
||||
# openssl genrsa -traditional 4096 | base64 -w0 > /var/lib/atticd/token-secret
|
||||
# echo "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=$(cat /var/lib/atticd/token-secret)" > /etc/atticd.env
|
||||
services.atticd = {
|
||||
enable = true;
|
||||
environmentFile = "/etc/atticd.env";
|
||||
settings = {
|
||||
listen = "[::]:8080";
|
||||
storage = {
|
||||
type = "local";
|
||||
path = "/var/lib/atticd/storage";
|
||||
};
|
||||
garbage-collection = {
|
||||
default-retention-period = "3 months";
|
||||
};
|
||||
chunking = {
|
||||
nar-size-threshold = 65536;
|
||||
min-size = 16384;
|
||||
avg-size = 65536;
|
||||
max-size = 262144;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Kavita (ebook library)
|
||||
# ============================================================================
|
||||
# Generate token on the server:
|
||||
# openssl rand -hex 128 > /var/lib/kavita/token-key
|
||||
services.kavita = {
|
||||
enable = true;
|
||||
settings.Port = 5000;
|
||||
tokenKeyFile = "/var/lib/kavita/token-key";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Audiobookshelf
|
||||
# ============================================================================
|
||||
services.audiobookshelf = {
|
||||
enable = true;
|
||||
port = 8234;
|
||||
host = "127.0.0.1";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Paperless-ngx (document management)
|
||||
# ============================================================================
|
||||
services.paperless = {
|
||||
enable = true;
|
||||
port = 28981;
|
||||
address = "127.0.0.1";
|
||||
domain = "docs.axiomania.org";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Adguard Home (DNS ad blocker)
|
||||
# ============================================================================
|
||||
# Use as DNS server via Wireguard: set DNS = 10.100.0.1 in client config
|
||||
services.adguardhome = {
|
||||
enable = true;
|
||||
port = 3300;
|
||||
host = "127.0.0.1";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# SearXNG (private search engine)
|
||||
# ============================================================================
|
||||
# Generate secret on the server:
|
||||
# echo "SEARX_SECRET=$(openssl rand -hex 32)" > /etc/searx/secrets.env
|
||||
services.searx = {
|
||||
enable = true;
|
||||
redisCreateLocally = true;
|
||||
settings = {
|
||||
server = {
|
||||
bind_address = "127.0.0.1";
|
||||
port = 8888;
|
||||
secret_key = "$SEARX_SECRET";
|
||||
};
|
||||
};
|
||||
environmentFile = "/etc/searx/secrets.env";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Homepage (services dashboard)
|
||||
# ============================================================================
|
||||
services.homepage-dashboard = {
|
||||
enable = true;
|
||||
listenPort = 8082;
|
||||
allowedHosts = "home.axiomania.org";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Matrix Conduit (encrypted messaging)
|
||||
# ============================================================================
|
||||
services.matrix-conduit = {
|
||||
enable = true;
|
||||
settings.global = {
|
||||
server_name = "axiomania.org";
|
||||
port = 6167;
|
||||
address = "127.0.0.1";
|
||||
allow_registration = false;
|
||||
};
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Gokapi (file sharing) — disabled due to CVEs in stable
|
||||
# ============================================================================
|
||||
# services.gokapi = {
|
||||
# enable = true;
|
||||
# environment = {
|
||||
# GOKAPI_PORT = 53842;
|
||||
# };
|
||||
# };
|
||||
|
||||
# ============================================================================
|
||||
# Wireguard VPN
|
||||
# ============================================================================
|
||||
# Generate keys on the server:
|
||||
# wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
|
||||
networking.wireguard.interfaces.wg0 = {
|
||||
ips = [ "10.100.0.1/24" ];
|
||||
listenPort = 51820;
|
||||
privateKeyFile = "/etc/wireguard/private.key";
|
||||
peers = [
|
||||
{
|
||||
# Minisforum V3 SE (local NixOS machine)
|
||||
publicKey = "rEsHqzJVnv9AoOIEVTrdEF3x4G6rWxXqHLXTzAx88gc=";
|
||||
allowedIPs = [ "10.100.0.2/32" ];
|
||||
}
|
||||
{
|
||||
# Pixel 5
|
||||
publicKey = "jP+rYdc8qKdNY4l3PDFmGiOA31SnJRgcmQIVu6AJ9EM=";
|
||||
allowedIPs = [ "10.100.0.3/32" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# NAT for Wireguard clients to access the internet
|
||||
networking.nat = {
|
||||
enable = true;
|
||||
externalInterface = "ens3";
|
||||
internalInterfaces = [ "wg0" ];
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Nginx reverse proxy + ACME
|
||||
# ============================================================================
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
|
||||
# ── Public services ──
|
||||
virtualHosts."git.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."matrix.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:6167";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."cache.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://[::1]:8080";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
# ── VPN-only services (WireGuard clients only) ──
|
||||
virtualHosts."vault.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8222";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."music.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:4533";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."sync.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8384";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."books.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:5000";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."audio.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8234";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."docs.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:28981";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."search.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8888";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
virtualHosts."home.axiomania.org" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8082";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = "allow 10.100.0.0/24; allow 127.0.0.1; deny all;";
|
||||
};
|
||||
};
|
||||
|
||||
# virtualHosts."share.axiomania.org" = {
|
||||
# enableACME = true;
|
||||
# forceSSL = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:53842";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "gamma.kinematics@gmail.com";
|
||||
};
|
||||
|
||||
# ============================================================================
|
||||
# Packages
|
||||
# ============================================================================
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
vim
|
||||
htop
|
||||
wireguard-tools
|
||||
];
|
||||
}
|
||||
26
Hetzner/builder.nix
Normal file
26
Hetzner/builder.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [ ./common.nix ];
|
||||
|
||||
networking.hostName = "builder";
|
||||
|
||||
nix.settings = {
|
||||
max-jobs = "auto";
|
||||
substituters = [
|
||||
"https://cache.nixos.org"
|
||||
"https://cache.axiomania.org/main"
|
||||
];
|
||||
trusted-public-keys = [
|
||||
"main:Uz5F0MbXItVx2XCmBbEAMmQ0T6+DZDgLaXWalh1k++o="
|
||||
];
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
tmux
|
||||
attic-client
|
||||
vim
|
||||
htop
|
||||
];
|
||||
}
|
||||
34
Hetzner/common.nix
Normal file
34
Hetzner/common.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{ modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
./disk-config.nix
|
||||
];
|
||||
|
||||
system.stateVersion = "25.11";
|
||||
time.timeZone = "Europe/Berlin";
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
boot.loader.grub = {
|
||||
enable = true;
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
};
|
||||
|
||||
services.qemuGuest.enable = true;
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7XcPUNMwpUqXMqhwTlW3YV2BUj0I6efk0LxqFcyYgA gamma.kinematics@gmail.com"
|
||||
];
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitRootLogin = "prohibit-password";
|
||||
};
|
||||
};
|
||||
}
|
||||
34
Hetzner/disk-config.nix
Normal file
34
Hetzner/disk-config.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
disko.devices.disk.main = {
|
||||
device = lib.mkDefault "/dev/sda";
|
||||
type = "disk";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
boot = {
|
||||
size = "1M";
|
||||
type = "EF02"; # BIOS boot partition
|
||||
};
|
||||
esp = {
|
||||
size = "512M";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
mountOptions = [ "umask=0077" ];
|
||||
};
|
||||
};
|
||||
root = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "ext4";
|
||||
mountpoint = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
121
Hetzner/setup.nix
Normal file
121
Hetzner/setup.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
sshKeyPath = "/home/lebowski/.ssh/id_ed25519";
|
||||
sshKeyName = "V3";
|
||||
serverName = "nix-builder";
|
||||
serverType = "ccx43";
|
||||
location = "nbg1";
|
||||
|
||||
sshCmd = "ssh -o StrictHostKeyChecking=no -o BatchMode=yes -i ${sshKeyPath}";
|
||||
|
||||
# Spin up a builder from the NixOS snapshot
|
||||
builder-up = pkgs.writeShellApplication {
|
||||
name = "builder-up";
|
||||
runtimeInputs = with pkgs; [ hcloud openssh ];
|
||||
text = ''
|
||||
SERVER_NAME="${serverName}"
|
||||
SSH_KEY_PATH="${sshKeyPath}"
|
||||
|
||||
# ── Check if server already exists ──
|
||||
if hcloud server describe "$SERVER_NAME" &>/dev/null; then
|
||||
IP=$(hcloud server ip "$SERVER_NAME")
|
||||
echo "Server '$SERVER_NAME' already running at $IP"
|
||||
echo " builder-ssh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Find snapshot ──
|
||||
SNAPSHOT_ID=$(hcloud image list --type snapshot --selector description="${snapshotDesc}" -o noheader -o columns=id 2>/dev/null | head -1)
|
||||
if [ -z "$SNAPSHOT_ID" ]; then
|
||||
# Fallback: search by description
|
||||
SNAPSHOT_ID=$(hcloud image list --type snapshot -o noheader -o columns=id,description | grep "${snapshotDesc}" | awk '{print $1}' | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$SNAPSHOT_ID" ]; then
|
||||
echo "ERROR: No snapshot '${snapshotDesc}' found."
|
||||
echo " Create one with: builder-snapshot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Create server from snapshot ──
|
||||
echo "==> Creating ${serverType} in ${location} from snapshot $SNAPSHOT_ID..."
|
||||
hcloud server create \
|
||||
--name "$SERVER_NAME" \
|
||||
--type "${serverType}" \
|
||||
--image "$SNAPSHOT_ID" \
|
||||
--location "${location}" \
|
||||
--label role=nix-builder \
|
||||
--ssh-key "${sshKeyName}"
|
||||
|
||||
IP=$(hcloud server ip "$SERVER_NAME")
|
||||
|
||||
# ── Wait for SSH ──
|
||||
echo "==> Waiting for SSH..."
|
||||
for i in $(seq 1 30); do
|
||||
if ${sshCmd} "root@''${IP}" true 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
if [ "$i" -eq 30 ]; then
|
||||
echo "ERROR: SSH did not become available after 150s"
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ── Update known_hosts ──
|
||||
ssh-keygen -R "$IP" 2>/dev/null || true
|
||||
ssh-keyscan -H "$IP" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== Builder ready ==="
|
||||
echo " builder-ssh"
|
||||
'';
|
||||
};
|
||||
|
||||
# SSH into the builder
|
||||
builder-ssh = pkgs.writeShellApplication {
|
||||
name = "builder-ssh";
|
||||
runtimeInputs = with pkgs; [ hcloud openssh ];
|
||||
text = ''
|
||||
SERVER_NAME="${serverName}"
|
||||
SSH_KEY_PATH="${sshKeyPath}"
|
||||
|
||||
if ! hcloud server describe "$SERVER_NAME" &>/dev/null; then
|
||||
echo "No server '$SERVER_NAME' found. Run builder-up first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IP=$(hcloud server ip "$SERVER_NAME")
|
||||
ssh -i "$SSH_KEY_PATH" -t "root@$IP" "tmux new-session -A -s build"
|
||||
'';
|
||||
};
|
||||
|
||||
# Tear down the builder
|
||||
builder-down = pkgs.writeShellApplication {
|
||||
name = "builder-down";
|
||||
runtimeInputs = with pkgs; [ hcloud openssh ];
|
||||
text = ''
|
||||
SERVER_NAME="${serverName}"
|
||||
|
||||
if hcloud server describe "$SERVER_NAME" &>/dev/null; then
|
||||
IP=$(hcloud server ip "$SERVER_NAME")
|
||||
echo "==> Deleting '$SERVER_NAME' ($IP)..."
|
||||
hcloud server delete "$SERVER_NAME"
|
||||
ssh-keygen -R "$IP" 2>/dev/null || true
|
||||
echo " Done."
|
||||
else
|
||||
echo "No server '$SERVER_NAME' found."
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
environment.systemPackages = [
|
||||
builder-up
|
||||
builder-ssh
|
||||
builder-down
|
||||
pkgs.hcloud
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue