From 4d4bc1988dfc7ce77dd7a740a4539150c75ba139 Mon Sep 17 00:00:00 2001 From: Andre Date: Sun, 19 May 2024 14:33:15 -0400 Subject: [PATCH] Migrate Haven's services from K3s to Nix --- README.md | 17 ++- flake.lock | 14 +-- hosts/Haven/default.nix | 220 +++++++++++++++++++++++++++++---- hosts/Haven/start-haven.sh | 23 ++-- hosts/Shura/default.nix | 3 + nix-secrets | 2 +- packages/airsonic-advanced.nix | 34 +++++ 7 files changed, 266 insertions(+), 47 deletions(-) create mode 100644 packages/airsonic-advanced.nix diff --git a/README.md b/README.md index 90823c8..31e83a5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,14 @@ There are a few different actions for handling the update: #### Using Remote builds -You can build any Nix or NixOS expression on a remote system before copying it over, as long as the root user on the local system has SSH access to the build target. +Nix can create builds for or on remote systems, and transfer them via SSH. + +##### Generating a build on a remote system + +You can run a build on a remote server, then pull it down to the local system. This is called a `distributedBuild`. + +> [!NOTE] +> For distributed builds, the root user on the local system needs SSH access to the build target. This is done automatically. To enable root builds on a host, add this to its config: @@ -65,6 +72,14 @@ nix.distributedBuilds = true; For hosts where `nix.distributedBuilds` is true, this repo automatically gives the local root user SSH access to an unprivileged user on the build systems. This is configured in `nix-secrets`, but the build systems are defined in [`modules/base/nix.nix`](https://github.com/8bitbuddhist/nix-configuration/blob/b816d821636f9d30be905af80fe578c25ce74b92/modules/base/nix.nix#L41). +##### Pushing a build to a remote system + +Conversely, you can run a build on the local host, then push it to a remote system. + +```sh +NIX_SSHOPTS="-o RequestTTY=force" nixos-rebuild --target-host user@example.com --use-remote-sudo switch +``` + ### Testing without modifying the system If you want to test without doing a whole build, or without modifying the current system, there are a couple additional tools to try. diff --git a/flake.lock b/flake.lock index 7b39100..c5abf43 100644 --- a/flake.lock +++ b/flake.lock @@ -250,11 +250,11 @@ "nix-secrets": { "flake": false, "locked": { - "lastModified": 1715904475, - "narHash": "sha256-5PyOjPdOhzX5qHq3ywwSsYCQT5OmWv870DlSYyuJBh4=", + "lastModified": 1716069971, + "narHash": "sha256-0YWdnb+RiMHW8vQ4siDIqYEo5OUdWvYq7xzQw7blBwE=", "ref": "refs/heads/main", - "rev": "0bc545bf36759ca1ab67e2718bc5771eca72d02f", - "revCount": 23, + "rev": "b5c77dd4718a64ce7c8aef3752beed1144ea2693", + "revCount": 27, "type": "git", "url": "file:///home/aires/Development/nix-configuration/nix-secrets" }, @@ -313,11 +313,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1715787315, - "narHash": "sha256-cYApT0NXJfqBkKcci7D9Kr4CBYZKOQKDYA23q8XNuWg=", + "lastModified": 1715961556, + "narHash": "sha256-+NpbZRCRisUHKQJZF3CT+xn14ZZQO+KjxIIanH3Pvn4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "33d1e753c82ffc557b4a585c77de43d4c922ebb5", + "rev": "4a6b83b05df1a8bd7d99095ec4b4d271f2956b64", "type": "github" }, "original": { diff --git a/hosts/Haven/default.nix b/hosts/Haven/default.nix index fdf81b6..c69d203 100644 --- a/hosts/Haven/default.nix +++ b/hosts/Haven/default.nix @@ -6,7 +6,25 @@ ... }: let + cfg = config.services.forgejo; + forgejo-cli = pkgs.writeScriptBin "forgejo-cli" '' + #!${pkgs.runtimeShell} + cd ${cfg.stateDir} + sudo=exec + if [[ "$USER" != forgejo ]]; then + sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} -g ${cfg.group} --preserve-env=GITEA_WORK_DIR --preserve-env=GITEA_CUSTOM' + fi + # Note that these variable names will change + export GITEA_WORK_DIR=${cfg.stateDir} + export GITEA_CUSTOM=${cfg.customDir} + $sudo ${lib.getExe cfg.package} "$@" + ''; start-haven = pkgs.writeShellScriptBin "start-haven" (builtins.readFile ./start-haven.sh); + + subdomains = map (subdomain: subdomain + ".${config.secrets.networking.primaryDomain}") [ + "code" + "music" + ]; in { imports = [ ./hardware-configuration.nix ]; @@ -24,10 +42,6 @@ in autostart = false; environment = "${config.users.users.aires.home}"; }; - k3s = { - enable = true; - role = "server"; - }; msmtp.enable = true; }; users = { @@ -44,33 +58,191 @@ in }; }; - # Enable BOINC (distributed research computing) - services.boinc = { - enable = true; - dataDir = "/var/lib/boinc"; + # TLS certificate renewal via Let's Encrypt + security.acme = { + acceptTerms = true; + defaults = { + email = "${config.secrets.users.aires.email}"; + }; + + certs."${config.secrets.networking.primaryDomain}" = { + dnsProvider = "namecheap"; + extraDomainNames = subdomains; + webroot = null; # Prevents an assertion error + credentialFiles = { + "NAMECHEAP_API_USER_FILE" = "${pkgs.writeText "namecheap-api-user" '' + ${config.secrets.networking.namecheap.api.user} + ''}"; + "NAMECHEAP_API_KEY_FILE" = "${pkgs.writeText "namecheap-api-key" '' + ${config.secrets.networking.namecheap.api.key} + ''}"; + }; + }; + }; + # /var/lib/acme/.challenges must be writable by the ACME user + # and readable by the Nginx user. The easiest way to achieve + # this is to add the Nginx user to the ACME group. + users.users.nginx.extraGroups = [ "acme" ]; + users.users.airsonic.extraGroups = [ "media" ]; + + services = { + nginx = { + enable = true; + + # Use recommended settings per https://nixos.wiki/wiki/Nginx#Hardened_setup_with_TLS_and_HSTS_preloading + recommendedGzipSettings = true; + recommendedOptimisation = true; + #recommendedProxySettings = true; # Recommended settings break Airsonic + recommendedTlsSettings = true; + + virtualHosts = { + # Base URL: make sure we've got Let's Encrypt running challenges here, and all other requests going to HTTPS + "${config.secrets.networking.primaryDomain}" = { + # Catchall vhost, will redirect users to HTTPS for all vhosts + default = true; + enableACME = true; + #serverAliases = subdomains; + locations."/" = { + return = "301 https://$host$request_uri"; + }; + }; + + # Forgejo + "code.${config.secrets.networking.primaryDomain}" = { + useACMEHost = "${config.secrets.networking.primaryDomain}"; + forceSSL = true; + listen = [ + { + port = 443; + addr = "0.0.0.0"; + ssl = true; + } + ]; + locations."/" = { + proxyPass = "http://127.0.0.1:3000"; + proxyWebsockets = true; # needed if you need to use WebSocket + extraConfig = + # required when the target is also TLS server with multiple hosts + "proxy_ssl_server_name on;"; + }; + }; + + # Airsonic + "music.${config.secrets.networking.primaryDomain}" = { + useACMEHost = "${config.secrets.networking.primaryDomain}"; + forceSSL = true; + listen = [ + { + port = 443; + addr = "0.0.0.0"; + ssl = true; + } + ]; + locations."/" = { + proxyPass = "http://127.0.0.1:4040"; + proxyWebsockets = true; # needed if you need to use WebSocket + }; + }; + }; + }; + + # Enable Airsonic-Advanced (music streaming) + airsonic = { + enable = true; + war = "${ + (pkgs.callPackage ../../packages/airsonic-advanced.nix { inherit lib; }) + }/webapps/airsonic-advanced.war"; + port = 4040; + jre = pkgs.jdk17_headless; + jvmOptions = [ + "-Dserver.use-forward-headers=true" + "-Xmx4G" + ]; + home = "/storage/services/airsonic-advanced"; + }; + + # Enable BOINC (distributed research computing) + boinc = { + enable = true; + package = pkgs.boinc-headless; + dataDir = "/var/lib/boinc"; + extraEnvPackages = [ pkgs.ocl-icd ]; + }; + + # Enable Forgejo / Gitea (code repository) + forgejo = { + enable = true; + stateDir = "/storage/services/forgejo"; + # Enable support for Git Large File Storage + lfs.enable = true; + settings = { + server = { + DOMAIN = "${config.secrets.networking.primaryDomain}"; + ROOT_URL = "https://code.${config.secrets.networking.primaryDomain}/"; + HTTP_PORT = 3000; + SSH_PORT = config.secrets.services.forgejo.sshPort; + }; + }; + useWizard = true; + }; + + # Enable SSH + openssh = { + enable = true; + ports = [ config.secrets.hosts.haven.ssh.port ]; + + settings = { + # require public key authentication for better security + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + PubkeyAuthentication = true; + + PermitRootLogin = "without-password"; + }; + }; + + # TODO: VPN (Check out Wireguard) }; - # Enable SSH - services.openssh = { - enable = true; - ports = [ 33105 ]; + # Configure services + systemd.services = { + # Airsonic: Disable autostart and set environment variables. Started via start-haven script + airsonic = { + wantedBy = lib.mkForce [ ]; + }; - settings = { - # require public key authentication for better security - PasswordAuthentication = false; - KbdInteractiveAuthentication = false; - PubkeyAuthentication = true; + # Foregejo: Disable autostart. Started via start-haven script + forgejo = { + wantedBy = lib.mkForce [ ]; + }; - PermitRootLogin = "without-password"; + # Nginx: Disable autostart. Started via start-haven script + nginx = { + wantedBy = lib.mkForce [ ]; + wants = [ + "airsonic.service" + "forgejo.service" + ]; }; }; + # Open ports + networking.firewall = { + enable = true; + allowedTCPPorts = [ + 80 + 443 + ]; + }; + + # Add extra packages: + # 1: forgejo CLI tool + # 2: Haven's startup script + environment.systemPackages = [ + forgejo-cli + start-haven + ]; + # Allow Haven to be a build target for other architectures (mainly ARM64) boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; - - # Open port for OpenVPN - networking.firewall.allowedUDPPorts = [ 1194 ]; - - # Add script for booting Haven - environment.systemPackages = [ start-haven ]; } diff --git a/hosts/Haven/start-haven.sh b/hosts/Haven/start-haven.sh index 5b87350..be5a583 100644 --- a/hosts/Haven/start-haven.sh +++ b/hosts/Haven/start-haven.sh @@ -11,25 +11,20 @@ fi set -e # Unlock and mount storage directory if we haven't already -if [ ! -f /dev/mapper/storage ]; then - echo "Unlocking storage partition:" +if [ -e "/dev/mapper/storage" ]; then + echo "Storage partition already mounted." +else + echo "Unlocking storage partition..." cryptsetup luksOpen /dev/md/Sapana storage mount /dev/mapper/storage /storage echo "Storage partition mounted." fi -#echo "Unlocking backup partition:" -# 4 TB HDD, partition #2 -#cryptsetup luksOpen /dev/disk/by-uuid/8dc60329-d27c-4a4a-b76a-861b1e28400e backups --key-file /storage/backups_partition.key -#mount /dev/mapper/backups /backups -#echo "Storage and backup partitions mounted." - -echo "Starting Duplicacy:" -systemctl start duplicacy-web.service -echo "Duplicacy started." - -echo "Starting SyncThing:" +echo "Starting services..." +systemctl restart duplicacy-web.service +systemctl restart airsonic.service forgejo.service systemctl --machine aires@.host --user start syncthing.service -echo "SyncThing started." +systemctl restart nginx.service +echo "Services started. Haven is ready to go!" exit 0 diff --git a/hosts/Shura/default.nix b/hosts/Shura/default.nix index dfb703a..159269b 100644 --- a/hosts/Shura/default.nix +++ b/hosts/Shura/default.nix @@ -63,6 +63,9 @@ in }; }; + # Install additional packages + environment.systemPackages = [ pkgs.boinc ]; + # Move files into target system systemd.tmpfiles.rules = [ # Use gremlin user's monitor config for GDM (defined above) diff --git a/nix-secrets b/nix-secrets index 0bc545b..9b1b742 160000 --- a/nix-secrets +++ b/nix-secrets @@ -1 +1 @@ -Subproject commit 0bc545bf36759ca1ab67e2718bc5771eca72d02f +Subproject commit 9b1b7422848beee7137e74a18bf4f9551a1cf044 diff --git a/packages/airsonic-advanced.nix b/packages/airsonic-advanced.nix new file mode 100644 index 0000000..605b237 --- /dev/null +++ b/packages/airsonic-advanced.nix @@ -0,0 +1,34 @@ +{ + lib, + stdenv, + fetchurl, + nixosTests, +}: + +stdenv.mkDerivation rec { + pname = "airsonic-advanced"; + version = "11.1.4-SNAPSHOT.20240518150716"; + + src = fetchurl { + url = "https://github.com/kagemomiji/airsonic-advanced/releases/download/${version}/airsonic.war"; + sha256 = "f4274fadd0acfe7f21d04e34ebb158238d8aaac06c0c76f6a4bf3d2d5bb41156"; + }; + + buildCommand = '' + mkdir -p "$out/webapps" + cp "$src" "$out/webapps/airsonic-advanced.war" + ''; + + passthru.tests = { + airsonic-starts = nixosTests.airsonic; + }; + + meta = with lib; { + description = "Personal media streamer"; + homepage = "https://airsonic.github.io"; + sourceProvenance = with sourceTypes; [ binaryBytecode ]; + license = lib.licenses.gpl3; + platforms = platforms.all; + maintainers = with maintainers; [ disassembler ]; + }; +}