commit b704ff1bab03ef5a41363493e650f6d210483a5a Author: atagen Date: Sat Feb 28 02:37:04 2026 +1100 init diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f52c5ed --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1771848320, + "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..13e75a0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "Airdrome — modern web UI for Subsonic-compatible music servers"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = + { self, nixpkgs }: + let + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + in + { + packages = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + airdrome = pkgs.callPackage ./package.nix { }; + in + { + inherit airdrome; + default = airdrome; + } + ); + + overlays.default = final: prev: { + airdrome = final.callPackage ./package.nix { }; + }; + + nixosModules.default = self.nixosModules.airdrome; + nixosModules.airdrome = + { pkgs, ... }: + { + imports = [ ./module.nix ]; + services.airdrome.package = self.packages.${pkgs.stdenv.hostPlatform.system}.airdrome; + }; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..4f4e091 --- /dev/null +++ b/module.nix @@ -0,0 +1,119 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.airdrome; + + # Base env object built at Nix eval time (no shell quoting needed). + baseEnv = + lib.optionalAttrs (cfg.serverUrl != null) { SERVER_URL = cfg.serverUrl; } + // lib.optionalAttrs (cfg.username != null) { USERNAME = cfg.username; } + // lib.optionalAttrs (cfg.password != null) { PASSWORD = cfg.password; }; + + baseEnvJson = pkgs.writeText "airdrome-env.json" (builtins.toJSON baseEnv); +in +{ + options.services.airdrome = { + enable = lib.mkEnableOption "Airdrome web root assembly"; + + package = lib.mkOption { + type = lib.types.package; + defaultText = lib.literalExpression "pkgs.airdrome"; + description = "Package to use."; + }; + + serverUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Navidrome server URL."; + }; + + username = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Auto-login username."; + }; + + password = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Plaintext password (ends up in the Nix store — use + {option}`passwordFile` instead). + ''; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Runtime path to a file containing the password."; + }; + + webRoot = lib.mkOption { + type = lib.types.str; + default = "/var/lib/airdrome/web"; + readOnly = true; + description = "Assembled web root to point your web server at."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !(cfg.password != null && cfg.passwordFile != null); + message = "services.airdrome.password and services.airdrome.passwordFile are mutually exclusive."; + } + ]; + + systemd.services.airdrome-config = { + description = "Assemble Airdrome web root"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + StateDirectory = "airdrome"; + }; + + path = lib.optionals (cfg.passwordFile != null) [ pkgs.jq ]; + + script = + let + webRoot = cfg.webRoot; + pkg = cfg.package; + in + '' + # 1. Fresh web root + rm -rf ${webRoot} + mkdir -p ${webRoot} + + # 2. Symlink all package files except env.js + for f in ${pkg}/*; do + name="$(basename "$f")" + [ "$name" = "env.js" ] && continue + ln -s "$f" ${webRoot}/"$name" + done + + # 3. Build env JSON + '' + + ( + if cfg.passwordFile != null then + '' + env_json=$(jq --arg pw "$(cat ${cfg.passwordFile})" '. + {PASSWORD: $pw}' ${baseEnvJson}) + '' + else + '' + env_json=$(cat ${baseEnvJson}) + '' + ) + + '' + # 4. Write env.js + printf 'window.env = %s;\n' "$env_json" > ${webRoot}/env.js + ''; + }; + }; +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..2384d84 --- /dev/null +++ b/package.nix @@ -0,0 +1,66 @@ +{ + lib, + buildNpmPackage, + nodejs_22, + fetchFromGitHub, +}: + +buildNpmPackage rec { + pname = "airdrome"; + version = "4.3"; + + src = fetchFromGitHub { + owner = "JPGuillemin"; + repo = "Airdrome"; + rev = version; + hash = "sha256-UGJMbrrX6pBjQJFiQtb1QvECvgVQMk8gDuJJhbFW9HQ="; + }; + + nodejs = nodejs_22; + + npmDepsHash = "sha256-zgKmXSOdCaMbg520IpT93n3e/6KW+wMUQ94wGfyKXz0="; + + postPatch = '' + # Remove vite-plugin-checker — vue-tsc fails in sandbox because + # tsconfig lacks the bootstrap-vue-3 alias. Not needed for prod builds. + sed -i '/import checker/d' vite.config.mjs + sed -i '/checker({/,/}),/d' vite.config.mjs + + # Add resolve alias: bootstrap-vue-3 → bootstrap-vue-next + # (upstream imports use the old name but only bootstrap-vue-next is installed) + sed -i "s|'@': fileURLToPath(new URL('./src', import.meta.url))|'@': fileURLToPath(new URL('./src', import.meta.url)),\n 'bootstrap-vue-3': 'bootstrap-vue-next'|" vite.config.mjs + + # Fix index.html — add missing \n |' index.html + + # Extend Config interface in auth/service.ts with username + password + sed -i 's/serverUrl: string/serverUrl: string\n username: string\n password: string/' src/auth/service.ts + sed -i "s|serverUrl: env?.SERVER_URL.*|serverUrl: env?.SERVER_URL \|\| ${"''"},\n username: env?.USERNAME \|\| ${"''"},\n password: env?.PASSWORD \|\| ${"''"},|" src/auth/service.ts + + # Patch Login.vue — auto-login with hardcoded credentials before autoLogin() + sed -i '/onMounted(async() => {/a\ + if (config.serverUrl \&\& config.username \&\& config.password) {\ + try {\ + await auth.loginWithPassword(config.serverUrl, config.username, config.password)\ + store.setLoginSuccess(auth.username, auth.server)\ + await router.replace(props.returnTo)\ + return\ + } catch {}\ + }' src/auth/Login.vue + ''; + + installPhase = '' + runHook preInstall + mkdir -p "$out" + cp -r dist/. "$out/" + echo 'window.env = {};' > "$out/env.js" + runHook postInstall + ''; + + meta = { + description = "Modern web UI for Subsonic-compatible music servers"; + homepage = "https://github.com/JPGuillemin/Airdrome"; + license = lib.licenses.mit; + platforms = lib.platforms.all; + }; +} diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..bdb9860 --- /dev/null +++ b/update.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p curl jq nurl nix-prefetch-npm-deps cacert +# Update airdrome to the latest release from GitHub. +# +# Usage: ./update.sh # auto-detect latest tag +# ./update.sh 4.3 # pin to a specific tag + +set -euo pipefail + +REPO="JPGuillemin/Airdrome" +PKG_FILE="$(cd "$(dirname "$0")" && pwd)/package.nix" + +# --- resolve version -------------------------------------------------------- +if [[ $# -ge 1 ]]; then + VERSION="$1" +else + VERSION=$(curl -sL "https://api.github.com/repos/$REPO/releases/latest" \ + | jq -r '.tag_name') + if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "error: could not determine latest release" >&2 + exit 1 + fi +fi +echo "version: $VERSION" + +# --- source hash (via nurl) ------------------------------------------------- +SRC_HASH=$(nurl "https://github.com/$REPO" "$VERSION" --hash) +echo "src hash: $SRC_HASH" + +# --- npm deps hash ----------------------------------------------------------- +# Prefetch source, install deps, and hash them +SRC_PATH=$(nix build --no-link --print-out-paths \ + --impure --expr " + (builtins.getFlake \"nixpkgs\").legacyPackages.\${builtins.currentSystem}.fetchFromGitHub { + owner = \"JPGuillemin\"; + repo = \"Airdrome\"; + rev = \"$VERSION\"; + hash = \"$SRC_HASH\"; + } + ") +NPM_DEPS_HASH=$(nix-prefetch-npm-deps "$SRC_PATH/package-lock.json") +echo "npm deps hash: $NPM_DEPS_HASH" + +# --- patch package.nix ------------------------------------------------------- +sed -i -E " + s|(version = \").*(\";)|\1${VERSION}\2| + s|(hash = \").*(\";)|\1${SRC_HASH}\2| + s|(npmDepsHash = \").*(\";)|\1${NPM_DEPS_HASH}\2| +" "$PKG_FILE" + +echo "updated $PKG_FILE"