initial commit

This commit is contained in:
GammaKinematics 2026-03-30 13:10:42 +07:00
commit 90cff4f16a
59 changed files with 6855 additions and 0 deletions

409
Hetzner/axiomania.nix Normal file
View 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
View 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
View 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
View 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
View 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
];
}