diff --git a/flake.nix b/flake.nix index 0b08fba..e372404 100644 --- a/flake.nix +++ b/flake.nix @@ -45,6 +45,61 @@ }; }; + overlay-etherpad-fixes = final: prev: { + unstable = prev.unstable // { + etherpad-lite = prev.unstable.etherpad-lite.overrideAttrs (old: { + patches = + (old.patches or [ ]) + ++ [ + ./pkgs/patches/etherpad-plugin-index-keys.patch + ./pkgs/patches/etherpad-declarative-plugin-packages.patch + ./pkgs/patches/etherpad-plugin-package-bootstrap-path.patch + ]; + postInstall = + (old.postInstall or "") + + '' + # Declaratively include ep_author_hover plugin so Etherpad can load it + # without runtime writes to the Nix store. + mkdir -p $out/lib/etherpad-lite/src/plugin_packages/ep_author_hover + tar -xzf ${ + prev.fetchurl { + url = "https://registry.npmjs.org/ep_author_hover/-/ep_author_hover-1.0.12.tgz"; + hash = "sha256-6/gJTB2GTep/n2ShrNqjzbIW121PYmyTDo/i8LpxjYA="; + } + } \ + --strip-components=1 \ + -C $out/lib/etherpad-lite/src/plugin_packages/ep_author_hover \ + package + + # Declaratively include ep_images_extended plugin so Etherpad can load it + # without runtime writes to the Nix store. + mkdir -p $out/lib/etherpad-lite/src/plugin_packages/ep_images_extended + tar -xzf ${ + prev.fetchurl { + url = "https://registry.npmjs.org/ep_images_extended/-/ep_images_extended-1.1.2.tgz"; + hash = "sha256-0RPnfQbeqepOPoML2dO7y77yySwQfD04anQdEKwyHNg="; + } + } \ + --strip-components=1 \ + -C $out/lib/etherpad-lite/src/plugin_packages/ep_images_extended \ + package + + # ep_images_extended directly requires mime-db at startup. + mkdir -p $out/lib/etherpad-lite/src/node_modules/mime-db + tar -xzf ${ + prev.fetchurl { + url = "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz"; + hash = "sha256-tzFKqRPRjkcLki8Kv8HOvmebNq/5hyaGBR7oye+CqZk="; + } + } \ + --strip-components=1 \ + -C $out/lib/etherpad-lite/src/node_modules/mime-db \ + package + ''; + }); + }; + }; + overlay-local = final: prev: { codex = prev.callPackage ./pkgs/codex/package.nix { }; beads = @@ -122,7 +177,7 @@ inherit openclaw-flake; }; modules = [ - ({ ... }: { nixpkgs.overlays = [ openclaw-flake.overlays.default ]; }) + ({ ... }: { nixpkgs.overlays = [ overlay-unstable overlay-etherpad-fixes openclaw-flake.overlays.default ]; }) home-manager.nixosModules.home-manager ./hosts/ruil/configuration.nix sops-nix.nixosModules.sops diff --git a/hosts/ruil/modules/etherpad-lite.nix b/hosts/ruil/modules/etherpad-lite.nix index 416d112..76f9415 100644 --- a/hosts/ruil/modules/etherpad-lite.nix +++ b/hosts/ruil/modules/etherpad-lite.nix @@ -1,6 +1,11 @@ -{ pkgs, ... }: +{ config, pkgs, ... }: { + sops.secrets.etherpad-env = { + owner = "etherpad"; + mode = "0400"; + }; + users.users.etherpad = { isSystemUser = true; group = "etherpad"; @@ -15,6 +20,12 @@ dbSettings = { filename = "/var/lib/etherpad-lite/rusty.db"; }; + users = { + hunner = { + password = "\${ETHERPAD_ADMIN_PASSWORD}"; + is_admin = true; + }; + }; }; # Etherpad on etherpad.hunner.dev (Cloudflare proxy -> nginx -> localhost:9001). @@ -27,9 +38,10 @@ Type = "simple"; User = "etherpad"; Group = "etherpad"; + EnvironmentFile = [ config.sops.secrets.etherpad-env.path ]; StateDirectory = "etherpad-lite"; WorkingDirectory = "/var/lib/etherpad-lite"; - ExecStart = "${pkgs.etherpad-lite}/bin/etherpad-lite --settings /etc/etherpad-lite/settings.json --sessionkey /var/lib/etherpad-lite/SESSIONKEY.txt --apikey /var/lib/etherpad-lite/APIKEY.txt"; + ExecStart = "${pkgs.unstable.etherpad-lite}/bin/etherpad-lite --settings /etc/etherpad-lite/settings.json --sessionkey /var/lib/etherpad-lite/SESSIONKEY.txt --apikey /var/lib/etherpad-lite/APIKEY.txt"; Restart = "on-failure"; RestartSec = "5s"; }; diff --git a/hosts/ruil/secrets/config.yaml b/hosts/ruil/secrets/config.yaml index 7522db4..cb07e21 100644 --- a/hosts/ruil/secrets/config.yaml +++ b/hosts/ruil/secrets/config.yaml @@ -5,6 +5,7 @@ openclaw-env: ENC[AES256_GCM,data:pJq+HdqlNjx0qeVHhPcnZk9FNm7/eMWm8vZ3ROnQ00qR4l searx-env: ENC[AES256_GCM,data:dJ8JGxTWBdrli340Yjs5bA7X25NjExj5Mxp2T49jVEv/pafTtyMWf7Tvonzv+krCe1k/Zsh7KuWoJxXXOOGjRLPP1eQMMJunoL/P6JXruX+ZkBN5XbYB/UdWdkUrcvdDSyMcofZwgqYdDUy6J5ZlvcnmvuIM,iv:DuzG234PInaT/2CYQp9fzGh0EBYrxA7cto5uI4tGSkQ=,tag:1PNQKboKl6N7SULlUeAcgA==,type:str] searx-nginx-basic-auth: ENC[AES256_GCM,data:v22LhW/PksCnfheQ5dYF4n0pLNdGEe8q/bp0aoP/ZRcUFsSWZSdt6Wuj3BdpW7Hl/vJiWysxDX/0mi1GQ7flZz0+lmIWe29hSroJljAccMrafL6CY7r2awk+IC5Z2hNmbvLxbHzyN9U8mExazeNuWq0=,iv:OpSkH4C0eAF6CrRJRmQRtC9j+0WEKLM1a0rNeGtROaY=,tag:7nbFtk02bgB0glANehZXTw==,type:str] vaultwarden-env: ENC[AES256_GCM,data:C1oXLf+XchounepkJdGskeh3mlIvZYNFOK8Ec7wkPUnysEBXpVjtdfvbWZLkIzlcIn9BxM7pQLGDpn+7vogZA47JA07TkIVef9xrYYytLDYkox6+G/Acd36tuMKrTRWNko/wWX/YQQdHTGLLlBvP56YMQOSQ6mq5w86VK7QDmPFZTeobt3n4neHDIRjxkEWqNQg5x8zVPYbRqeg6rN2ES/hnd9jTzetx2lYH1zU8IncIGnkzw5C/5L8TysmHygWE5cX2CsA+2slkQHMYdQ3cZNFswP793jiAQB2BWXKUE8jyRc7S5XeUzfhFsg6pFdo9m0Om1nF2Hku/sYKaml3U+Fcma5BctuMpaPMAWh20n4wGS9rcIaF3SxwhCTHmk/IFOX/s8eK4,iv:B4DpR2JZQTuDOfCCR9x4uPWH4HyfXVDVYEZ2JZCdDf0=,tag:jwovvzMPLInaQHCxV4fTGw==,type:str] +etherpad-env: ENC[AES256_GCM,data:MVkJbwFJdHOf5MixPQlrobGJTEuscrAvbDjpvxa+cf+ARmqMOoFLh9ectt+hWmZiqw3y6pwjHB7VVBdyRczkL5NgkukUSqIZStU1bDjM5JzA4D93DarVmg==,iv:CT2GtstuEU4Ek9ilYl3g1lx7wpHBj8KFc3EhvF+NAGc=,tag:lNkXC09AdvR9b6HTmJDeEQ==,type:str] sops: age: - recipient: age17sdp0gguexd88qel74fa4zeckxh93gqpkayz366fz6yvjauw7vcq7w6y45 @@ -25,7 +26,7 @@ sops: VHorQzNrMFJLaFpSalZZdjNraXhlSVUKwWLesTzMxsEB45hWWzhZGWc1cDm/gmvF MAytSLiBcieAkRKZoklyk/llbnq7kycvpZCU/sQrjKqmoHkC+TF3BQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-24T23:59:42Z" - mac: ENC[AES256_GCM,data:qnbTTnrl84U55wzKMrp7e/gvxrj5TZCH4LC7X+waPEVEpz7jsJ/10gezCU5H6v6lkXCrfJ9CZgupRrFMI+yrndLVtqXyrdUkMWeq6GehzKd9Li2VbnfVu1zfjF5gRX4xyOxjfa3NDvHhfWQOUrTlXQyd4YIJUs0Q4cLjpaT1DH0=,iv:7CGp7qqWNdRsnFfLyqstkxledpWH7b0PuPdVxvWxcQg=,tag:8t2iyWYshX0YMWtRtccclA==,type:str] + lastmodified: "2026-03-01T04:39:37Z" + mac: ENC[AES256_GCM,data:0DldqSb1rph8SVjvzW9CRg4CCkwaP1ym88cRMoQHcwxrURvPquHoChTiWW8TLm0fhP4XadC9FCbs930x9aKvG2coY7WzTi7hA2C7VJb28dvUGr+etYriDpvIF4BybYMPPAhQXL6cSebt836OrNW4thyk5vEEnClUolQgsBONa50=,iv:7EkBD8BQSQn0ImiNUEq2eR1SjIwWyba8ZE4Eh3oDuFs=,tag:CRjsEOVMhGEPx4356RmMjg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 diff --git a/justfile b/justfile index f2605ea..764d60d 100644 --- a/justfile +++ b/justfile @@ -16,17 +16,21 @@ deploy-sudo host: sudo nixos-rebuild switch \ --flake .#{{host}} -# Shortcut: deploy ruil (remote) +# Shortcut helper: deploy locally when host matches this machine, otherwise deploy remotely. +deploy-auto host target: + if [ "$(hostname -s)" = "{{host}}" ]; then just deploy-sudo {{host}}; else just deploy-remote {{host}} {{target}}; fi + +# Shortcut: deploy ruil deploy-ruil: - just deploy-remote ruil root@ruil.hunnur.com + just deploy-auto ruil root@ruil.hunnur.com -# Shortcut: deploy liminal (local) +# Shortcut: deploy liminal deploy-liminal: - just deploy-sudo liminal + just deploy-auto liminal root@liminal -# Shortcut: deploy zima (local) +# Shortcut: deploy zima deploy-zima: - just deploy-sudo zima + just deploy-auto zima root@zima # Update flake lock file update: diff --git a/pkgs/codex/package.nix b/pkgs/codex/package.nix index 0d67e35..0d52c4a 100644 --- a/pkgs/codex/package.nix +++ b/pkgs/codex/package.nix @@ -19,19 +19,20 @@ }: rustPlatform.buildRustPackage (finalAttrs: { pname = "codex"; - version = "0.104.0"; + version = "0.107.0"; src = fetchFromGitHub { owner = "openai"; repo = "codex"; tag = "rust-v${finalAttrs.version}"; #hash = lib.fakeHash; - hash = "sha256-spWb/msjl9am7E4UkZfEoH0diFbvAfydJKJQM1N1aoI="; + hash = "sha256-FJWEe5uV9jC6vDopmx6XcIcrye2ZwlgpZhWNILXUkHo="; }; sourceRoot = "${finalAttrs.src.name}/codex-rs"; - cargoHash = "sha256-8XNOqkr03+tI+gqJRR65iWYQ0zsqAiDl2V5bwPoWAcA="; + #cargoHash = lib.fakeHash; + cargoHash = "sha256-LbKiGMokzHSPsXumhO8PFqqeWb4E2CPa7Xv+FjTZwlE="; nativeBuildInputs = [ clang diff --git a/pkgs/patches/etherpad-declarative-plugin-packages.patch b/pkgs/patches/etherpad-declarative-plugin-packages.patch new file mode 100644 index 0000000..c08e078 --- /dev/null +++ b/pkgs/patches/etherpad-declarative-plugin-packages.patch @@ -0,0 +1,51 @@ +--- a/src/static/js/pluginfw/plugins.ts ++++ b/src/static/js/pluginfw/plugins.ts +@@ -123,6 +123,8 @@ + })); + }; + ++const declarativePluginDir = path.join(settings.root, 'src', 'plugin_packages'); ++ + exports.getPackages = async () => { + const {linkInstaller} = require("./installer"); + const plugins = await linkInstaller.listPlugins(); +@@ -136,6 +138,39 @@ + newDependencies[plugin.name] = plugin; + } + ++ // Also allow declarative plugins pre-bundled into src/plugin_packages by ++ // Nix overlays (without requiring live-plugin-manager runtime state). ++ try { ++ const entries = await fs.readdir(declarativePluginDir, {withFileTypes: true}); ++ for (const entry of entries) { ++ if (!entry.isDirectory()) continue; ++ ++ const pluginName = entry.name; ++ if (!pluginName.startsWith(exports.prefix) || pluginName === 'ep_etherpad-lite') continue; ++ if (newDependencies[pluginName]) continue; ++ ++ const pluginPath = path.join(declarativePluginDir, pluginName); ++ try { ++ const pkg = JSON.parse(await fs.readFile(path.join(pluginPath, 'package.json'), 'utf8')); ++ await fs.access(path.join(pluginPath, 'ep.json')); ++ ++ newDependencies[pluginName] = { ++ name: pluginName, ++ version: pkg.version || '0.0.0', ++ path: pluginPath, ++ realPath: pluginPath, ++ location: pluginPath, ++ }; ++ } catch (err) { ++ logger.warn( ++ `Skipping declarative plugin ${pluginName}: ${err.message || err.toString()}`, ++ ); ++ } ++ } ++ } catch (err) { ++ // plugin_packages directory is optional in upstream installs. ++ } ++ + newDependencies['ep_etherpad-lite'] = { + name: 'ep_etherpad-lite', + version: getEpVersion(), diff --git a/pkgs/patches/etherpad-plugin-index-keys.patch b/pkgs/patches/etherpad-plugin-index-keys.patch new file mode 100644 index 0000000..d48ef6c --- /dev/null +++ b/pkgs/patches/etherpad-plugin-index-keys.patch @@ -0,0 +1,57 @@ +diff --git a/src/static/js/pluginfw/installer.ts b/src/static/js/pluginfw/installer.ts +index 0fd8949be..22d2d6850 100644 +--- a/src/static/js/pluginfw/installer.ts ++++ b/src/static/js/pluginfw/installer.ts +@@ -175,9 +175,17 @@ export const getAvailablePlugins = async (maxCacheAge: number | false) => { + return availablePlugins; + } + +- const pluginsLoaded: AxiosResponse> = await axios.get(`${settings.updateServer}/plugins.json`, {headers}) +- availablePlugins = pluginsLoaded.data; ++ const pluginsLoaded: AxiosResponse> = await axios.get(`${settings.updateServer}/plugins.json`, {headers}); ++ const normalizedPlugins: MapArrayType = {}; ++ for (const key in pluginsLoaded.data) { ++ const plugin = pluginsLoaded.data[key]; ++ const normalizedKey = plugin?.name || key; ++ normalizedPlugins[normalizedKey] = plugin; ++ } ++ ++ availablePlugins = normalizedPlugins; + cacheTimestamp = nowTimestamp; ++ + return availablePlugins; + }; + +@@ -191,22 +199,25 @@ export const search = (searchTerm: string, maxCacheAge: number) => getAvailable + } + + for (const pluginName in results) { ++ const plugin = results[pluginName]; ++ const pluginIdentifier = plugin?.name || pluginName; ++ + // for every available plugin + // TODO: Also search in keywords here! +- if (pluginName.indexOf(plugins.prefix) !== 0) continue; ++ if (pluginIdentifier.indexOf(plugins.prefix) !== 0) continue; + +- if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) && +- (typeof results[pluginName].description !== 'undefined' && +- !~results[pluginName].description.toLowerCase().indexOf(searchTerm)) ++ if (searchTerm && !~plugin.name.toLowerCase().indexOf(searchTerm) && ++ (typeof plugin.description !== 'undefined' && ++ !~plugin.description.toLowerCase().indexOf(searchTerm)) + ) { +- if (typeof results[pluginName].description === 'undefined') { +- logger.debug(`plugin without Description: ${results[pluginName].name}`); ++ if (typeof plugin.description === 'undefined') { ++ logger.debug(`plugin without Description: ${plugin.name}`); + } + + continue; + } + +- res[pluginName] = results[pluginName]; ++ res[pluginIdentifier] = plugin; + } + + return res; diff --git a/pkgs/patches/etherpad-plugin-package-bootstrap-path.patch b/pkgs/patches/etherpad-plugin-package-bootstrap-path.patch new file mode 100644 index 0000000..91cb2dc --- /dev/null +++ b/pkgs/patches/etherpad-plugin-package-bootstrap-path.patch @@ -0,0 +1,11 @@ +--- a/src/templates/padBootstrap.js ++++ b/src/templates/padBootstrap.js +@@ -31,7 +31,7 @@ + window.plugins.baseURL = basePath; + await window.plugins.update(new Map([ + <% for (const module of pluginModules) { %> +- [<%- JSON.stringify(module) %>, require("../../src/plugin_packages/"+<%- JSON.stringify(module) %>)], ++ [<%- JSON.stringify(module) %>, require("./src/plugin_packages/"+<%- JSON.stringify(module) %>)], + <% } %> + ])); + // Mechanism for tests to register hook functions (install fake plugins).