Initial commit

This commit is contained in:
Tobias Ostner 2026-04-11 11:38:46 +02:00
commit f5345d8f6a
10 changed files with 619 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/result

101
configuration.nix Normal file
View file

@ -0,0 +1,101 @@
{ config, pkgs, ... }:
{
programs = {
gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
};
nix.settings.experimental-features = [ "nix-command" "flakes" ];
networking = {
hostName = "TobServer";
useDHCP = false;
useNetworkd = true;
interfaces.eno1.ipv4.addresses = [
{
address = "162.55.103.218";
prefixLength = 26;
}
];
defaultGateway = {
address = "162.55.103.193";
interface = "eno1";
};
nameservers = [
"185.12.64.1"
"185.12.64.2"
];
firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 50001 ];
};
};
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3PPXoEV1gt1tR4MyfR3nn1ttzZ4kGTiNfj8703MJdm hetzner-nixos"
];
services = {
openssh.enable = true;
openssh.settings.PermitRootLogin = "no";
btrfs.autoScrub.enable = true;
protonmail-bridge = {
enable = true;
};
nginx = {
enable = true;
};
};
security.acme = {
acceptTerms = true;
defaults.email = "tobias.ostner@proton.me";
};
time.timeZone = "Europe/Berlin";
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/nvme0n1";
environment.systemPackages = with pkgs; [
btop
ffmpeg
git
gnupg
jq
pass
vim
];
system.stateVersion = "25.11";
users.users.tobi = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "podman" "bitcoind-mainnet" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3PPXoEV1gt1tR4MyfR3nn1ttzZ4kGTiNfj8703MJdm hetzner-nixos"
];
};
security.sudo.enable = true;
security.sudo.wheelNeedsPassword = false;
virtualisation.podman = {
enable = true;
dockerCompat = true;
defaultNetwork.settings.dns_enabled = true;
};
}

27
flake.lock generated Normal file
View file

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1770136044,
"narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

24
flake.nix Normal file
View file

@ -0,0 +1,24 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.tobserver = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./hardware-configuration.nix
./configuration.nix
./modules/bitcoin.nix
./modules/electrs.nix
./modules/mempool.nix
./modules/nextcloud.nix
./modules/totap.nix
];
};
};
}

View file

@ -0,0 +1,44 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee";
fsType = "btrfs";
options = [ "subvol=@" ];
};
fileSystems."/home" =
{ device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee";
fsType = "btrfs";
options = [ "subvol=@home" ];
};
fileSystems."/nix" =
{ device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee";
fsType = "btrfs";
options = [ "subvol=@nix" ];
};
fileSystems."/var/log" =
{ device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee";
fsType = "btrfs";
options = [ "subvol=@varlog" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

63
modules/bitcoin.nix Normal file
View file

@ -0,0 +1,63 @@
{ config, pkgs, lib, ... }:
{
services = {
bitcoind.mainnet = {
enable = true;
prune = 0;
testnet = false;
dataDir = "/var/lib/bitcoind";
extraConfig = ''
bind=127.0.0.1
bind=[::1]
listen=1
listenonion=1
onlynet=onion
proxy=127.0.0.1:9050
proxyrandomize=1
torcontrol=127.0.0.1:9051
discover=0
txindex=1
disablewallet=0
maxconnections=60
maxuploadtarget=20480
dbcache=8192
bantime=86400
whitelist=download@127.0.0.1
rpccookiefile=/var/lib/bitcoind/.cookie
rpccookieperms=group
rpcbind=127.0.0.1
rpcbind=10.88.0.1
rpcallowip=127.0.0.1
rpcallowip=10.88.0.0/16
'';
};
tor = {
enable = true;
client.enable = true;
settings = {
ControlPort = 9051;
CookieAuthentication = true;
CookieAuthFile = "/run/tor/control.authcookie";
CookieAuthFileGroupReadable = true;
};
};
};
systemd.services.tor.serviceConfig.Group = "tor";
systemd.tmpfiles.rules = [
"d /var/lib/tor 0750 tor tor - -"
];
users.groups.tor = { };
users.users.bitcoind-mainnet.extraGroups = [ "tor" ];
}

30
modules/electrs.nix Normal file
View file

@ -0,0 +1,30 @@
{ config, pkgs, lib, ... }:
{
systemd.services.electrs = {
description ="Electrs Bitcoin Electrum Server";
after = [ "bitcoind-mainnet.service" ];
wants = [ "bitcoind-mainnet.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "bitcoind-mainnet";
Group = "bitcoind-mainnet";
ExecStart = "${pkgs.electrs}/bin/electrs --conf /etc/electrs.toml";
Restart = "on-failure";
StateDirectory = "electrs";
};
};
environment.etc."electrs.toml".text = ''
network = "bitcoin"
db_dir = "/var/lib/electrs/db"
daemon_dir = "/var/lib/bitcoind"
daemon_rpc_addr="127.0.0.1:8332"
daemon_p2p_addr="127.0.0.1:8333"
electrum_rpc_addr = "0.0.0.0:50001"
log_filters = "INFO"
cookie_file = "/var/lib/bitcoind/.cookie"
jsonrpc_timeout = "300s"
'';
}

182
modules/mempool.nix Normal file
View file

@ -0,0 +1,182 @@
{ config, pkgs, lib, ...}:
{
virtualisation.oci-containers = {
backend = "podman";
containers = {
mempool-db = {
image = "mariadb:11";
autoStart = true;
environment = {
MARIADB_DATABASE = "mempool";
MARIADB_USER = "mempool";
MARIADB_PASSWORD = "mempool";
MARIADB_ROOT_PASSWORD = "root";
};
volumes = [
"/srv/mempool/mysql:/var/lib/mysql"
];
};
mempool-backend = {
image = "mempool/backend:latest";
autoStart = true;
dependsOn = [ "mempool-db" ];
ports = [ "127.0.0.1:8999:8999" ];
extraOptions = [
"--add-host=host.containers.internal:host-gateway"
];
environment = {
MEMPOOL_NETWORK = "mainnet";
MEMPOOL_BACKEND = "electrum";
MEMPOOL_HTTP_PORT = "8999";
ELECTRUM_HOST = "host.containers.internal";
ELECTRUM_PORT = "50001";
ELECTRUM_TLS_ENABLED = "false";
CORE_RPC_HOST = "host.containers.internal";
CORE_RPC_PORT = "8332";
CORE_RPC_COOKIE = "true";
CORE_RPC_COOKIE_PATH = "/bitcoin/.cookie";
DATABASE_ENABLED = "true";
DATABASE_HOST = "mempool-db";
DATABASE_PORT = "3306";
DATABASE_DATABASE = "mempool";
DATABASE_USERNAME = "mempool";
DATABASE_PASSWORD = "mempool";
STATISTICS_ENABLED = "true";
};
volumes = [
"/run/mempool/bitcoind.cookie:/bitcoin/.cookie:ro"
];
};
mempool-frontend = {
image = "mempool/frontend:latest";
autoStart = true;
ports = [ "127.0.0.1:8998:8080" ];
environment = {
NGINX_HOSTNAME = "mempool.tobiasostner.de";
NGINX_PORT = "443";
NGINX_PROTOCOL = "https";
};
};
};
};
systemd.tmpfiles.rules = lib.mkAfter [
"d /run/mempool 0755 root root - -"
];
# Copy cookie once
systemd.services.mempool-cookie = {
description = "Expose bitcoind cookie for mempool-backend";
after = [ "bitcoind-mainnet.service" ];
wants = [ "bitcoind-mainnet.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "mempool-cookie" ''
set -euo pipefail
for i in $(seq 1 60); do
if [ -f /var/lib/bitcoind/.cookie ]; then
install -m 0644 -o root -g root /var/lib/bitcoind/.cookie /run/mempool/bitcoind.cookie
exit 0
fi
sleep 1
done
echo "Cookie not found after 60s" >&2
exit 1
'';
};
};
# Re-run the cookie copy whenever bitcoind rotates the cookie
systemd.paths.mempool-cookie = {
description = "Watch bitcoind cookie and refresh mempool copy";
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathChanged = "/var/lib/bitcoind/.cookie";
};
};
# When the path triggers, it starts the service
systemd.services.mempool-cookie.wantedBy = lib.mkForce [ ];
systemd.paths.mempool-cookie.unitConfig = {
Unit = "mempool-cookie.service";
};
systemd.services.podman-mempool-backend = {
after = [ "mempool-cookie.service" ];
wants = [ "mempool-cookie.service" ];
};
services.nginx = {
appendHttpConfig = ''
limit_req_zone $binary_remote_addr zone=mempool_api:10m rate=60r/m;
limit_conn_zone $binary_remote_addr zone=mempool_ws:10m;
'';
virtualHosts."mempool.tobiasostner.de" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8998";
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;
'';
};
locations."/api/" = {
proxyPass = "http://127.0.0.1:8999";
extraConfig = ''
limit_req zone=mempool_api burst=30 nodelay;
proxy_http_version 1.1;
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_connect_timeout 2s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
'';
};
locations."/api/v1/ws" = {
proxyPass = "http://127.0.0.1:8999/api/v1/ws";
proxyWebsockets = true;
extraConfig = ''
limit_conn mempool_ws 3;
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_connect_timeout 2s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
'';
};
};
};
networking.firewall.interfaces."podman0".allowedTCPPorts = [ 8332 50001 ];
}

120
modules/nextcloud.nix Normal file
View file

@ -0,0 +1,120 @@
{ config, pkgs, lib, ... }:
{
services = {
nextcloud = {
enable = true;
hostName = "cloud.tobiasostner.de";
https = true;
database.createLocally = true;
configureRedis = true;
config = {
dbtype = "mysql";
adminuser = "tobi";
adminpassFile = "/var/lib/nextcloud/admin-pass";
};
autoUpdateApps.enable = true;
datadir = "/nextcloud-data";
settings = {
default_phone_region = "DE";
log_type = "file";
logfile = "/var/log/nextcloud/nextcloud.log";
maintenance_window_start = 2;
overwritewebroot = "";
overwrite.cli.url = "https://cloud.tobiasostner.de";
overwritehost = "cloud.tobiasostner.de";
overwriteprotocol = "https";
## Email settings
mail_smtpmode = "smtp";
mail_smtphost = "smtp.protonmail.ch";
mail_smtpport = 587;
mail_smtpauth = true;
mail_from_address = "tobias";
mail_domain = "ostner.name";
enabledPreviewProviders = [
"OC\\Preview\\PNG"
"OC\\Preview\\JPEG"
"OC\\Preview\\GIF"
"OC\\Preview\\Movie"
];
preview_ffmpeg_path = "${pkgs.ffmpeg}/bin/ffmpeg";
preview_ffprobe_path = "${pkgs.ffmpeg}/bin/ffprobe";
};
phpOptions = lib.mkForce {
"memory_limit" = "4096M";
"upload_max_filesize" = "20G";
"post_max_size" = "20G";
"date.timezone" = "Europe/Berlin";
"output_buffering" = "Off";
"opcache.enable" = "1";
"opcache.enable_cli" = "1";
"opcache.memory_consumption" = "512";
"opcache.interned_strings_buffer" = "64";
"opcache.save_comments" = "1";
"opcache.max_accelerated_files" = "30000";
};
secretFile = "/run/secrets/nextcloud-mail.json";
};
restic.backups.nextcloud = {
initialize = true;
repository = "b2:tobcloud-backup:nextcloud";
passwordFile = "/var/lib/nextcloud/restic-password";
environmentFile = "/var/lib/nextcloud/backblaze-env";
paths = [
"/nextcloud-data"
"/var/backup/nextcloud"
];
backupPrepareCommand = ''
set -euo pipefail
install -d -m 0750 -o root -g root /var/backup/nextcloud
${pkgs.mariadb}/bin/mysqldump --single-transaction --databases nextcloud \
> /var/backup/nextcloud/nextcloud.sql
'';
timerConfig = {
OnCalendar = "daily";
};
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 12"
];
};
nginx = {
enable = true;
virtualHosts."cloud.tobiasostner.de" = {
forceSSL = true;
enableACME = true;
};
};
cron.systemCronJobs = [
"0 */6 * * * root nextcloud-occ preview:pre-generate"
];
};
environment.shellAliases = {
occ = "sudo nextcloud-occ";
};
users.users.nextcloud.extraGroups = [ "podman" ];
systemd.services.phpfpm-nextcloud.path = [ pkgs.ffmpeg ];
systemd.tmpfiles.rules = [
"d /var/log/nextcloud 0750 nextcloud nextcloud - -"
"d /var/backup/nextcloud 0750 root root - -"
];
}

27
modules/totap.nix Normal file
View file

@ -0,0 +1,27 @@
{ config, pkgs, lib, ... }:
{
services.nginx.virtualHosts."totap.de" = {
serverAliases = [ "www.totap.de" ];
enableACME = true;
forceSSL = true;
root = "/var/www/totap";
};
users.users.deploy-totap = {
isNormalUser = true;
description = "Deploy user for totap.de";
createHome = true;
home = "/home/deploy-totap";
group = "deploy-totap";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBJeuS1n+MjSy9NuQ4z6Z5jzr59kUVQgj627o0Vuc+Q github-actions-totap-deploy"
];
};
users.groups.deploy-totap = { };
systemd.tmpfiles.rules = [
"d /var/www/totap 0755 deploy-totap deploy-totap -"
];
}