dotfiles/nixos/hosts/vps-relay.nix
Danny 83dd92d738 shipyard staging gets a stable URL: b3.dannydannydanny.me
Drop the cloudflared Quick Tunnel (URL changed on every restart →
unworkable for shipyard's apps.json). Move to the same pattern
every other tenant uses:

- vps-relay Caddy: new virtualHost b3.dannydannydanny.me →
  reverse_proxy to sunken-ship's ZT IPv6 :8081.
- sunken-ship: open port 8081 on the zt+ firewall interface
  (was 8080 + 8091, now 8080 + 8081 + 8091).
- fitness-bot-shipyard service: set WEBAPP_URL=https://b3...
  so start.py skips its own tunnel attempt; drop pkgs.cloudflared
  from path now that nothing in the unit needs it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:00:39 +02:00

160 lines
6.2 KiB
Nix

# Hetzner Cloud VPS — public reverse proxy into the clan.
#
# Role: terminates public TLS via Caddy + Let's Encrypt, reverse-proxies
# each declared subdomain over ZeroTier to the appropriate homelab host.
# No navidrome/bbbot data ever hits disk here; this box is a relay.
{ config, lib, pkgs, ... }:
{
imports = [ ../disko-cloud.nix ];
nixpkgs.hostPlatform = "x86_64-linux";
# Hetzner Cloud vServers boot in BIOS mode (confirmed via rescue:
# /sys/firmware/efi doesn't exist, product_name=vServer). systemd-boot
# is UEFI-only, so use GRUB/BIOS. disko's EF02 BIOS boot partition
# already tells GRUB where to embed stage-1.5; we just enable grub +
# set the install device list.
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.loader.grub.enable = lib.mkForce true;
boot.loader.grub.efiSupport = lib.mkForce false;
boot.loader.grub.devices = lib.mkForce [ "/dev/sda" ];
# Ensure no default-set .device slips through and duplicates mirroredBoots.
boot.loader.grub.device = lib.mkForce "nodev";
# Hetzner Cloud cx23 uses QEMU virtio-scsi for the disk and virtio-net
# for the NIC. Without these modules in initrd, the kernel can't find
# the root partition and hangs during boot.
boot.initrd.availableKernelModules = [
"virtio_pci"
"virtio_scsi"
"virtio_net"
"virtio_blk"
"ata_piix"
"sd_mod"
"sr_mod"
];
boot.kernelModules = [ "virtio_pci" "virtio_scsi" "virtio_net" ];
# Cloud provisioners add the initial root SSH key via cloud-init or
# equivalent; we don't run cloud-init. All config is baked at install.
networking.hostName = "vps-relay";
networking.useDHCP = lib.mkDefault true;
time.timeZone = "Europe/Copenhagen";
# --- User + SSH ------------------------------------------------------
users.users.danny = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
# Same pubkey used to reach sunken-ship; set at install via clan.
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship"
];
};
users.users.root.openssh.authorizedKeys.keys =
config.users.users.danny.openssh.authorizedKeys.keys;
security.sudo.wheelNeedsPassword = false;
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "prohibit-password";
};
};
# --- Firewall --------------------------------------------------------
# Public: 22 (SSH), 80 + 443 (Caddy).
# ZT interface: trusted (set in the clan ZT module).
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
# fail2ban — public SSH gets brute-force probed within minutes of any
# cloud VM being created. Ban offending IPs after a few failures.
services.fail2ban = {
enable = true;
bantime = "1h";
bantime-increment = {
enable = true;
multipliers = "1 4 16 64 256"; # 1h, 4h, 16h, ~2.7d, ~10.7d
maxtime = "30d";
};
jails.sshd.settings = {
enabled = true;
maxretry = 5;
findtime = "10m";
};
};
# --- Caddy reverse proxy --------------------------------------------
# Subdomains → clan backends over ZeroTier. IPs are sunken-ship's /
# phantom-ship's ZT IPv6; brackets required in URLs.
services.caddy = {
enable = true;
email = "powerhouseplayer@gmail.com";
# Tell ACME to use Let's Encrypt's production endpoint (Caddy default).
virtualHosts = {
"navidrome.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:4533
'';
"bbbot.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8080
'';
# B3Bot beta — bbbot's staging tenant under shipyard_poc_bot.
# Same backend host as bbbot prod, port 8081.
"b3.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8081
'';
# Shelfish — phantom-ship's ZT IPv6.
"shelfish.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8081
'';
# Scuttle — same backend, different port. WebSocket upgrade is
# transparent under reverse_proxy.
"scuttle.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8082
'';
# Bananasimulator — same backend, port 8083.
"bananasimulator.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083
'';
# KomTolk (formerly translate-platform) — same backend, port 8080.
"komtolk.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080
'';
# Forgejo on phantom-ship — Phase 1 of the de-platform-from-GitHub
# roadmap (vimwiki/diary/2026-05-03.md).
"git.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:3000
'';
# Escape Hormuz — turn-based boat-race Mini App, port 8090.
"escapehormuz.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8090
'';
# bon — receipt scanner Mini App, port 8091. Camera capture in
# the WebView needs HTTPS, which Caddy terminates here.
"bon.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091
'';
# notes — markdown blog (notes.X) + apex landing (X). Same backend
# service on phantom :8092 routes by Host header.
"notes.dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
'';
"dannydannydanny.me".extraConfig = ''
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
'';
};
};
# --- Basic tooling ---------------------------------------------------
environment.systemPackages = with pkgs; [
git
htop
tcpdump
];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
system.stateVersion = "25.11";
}