Add etherpad, update codex

This commit is contained in:
Hunter Haugen 2026-03-03 21:50:04 -08:00
parent 176d2a7723
commit aa2b38d3bf
Signed by: hunner
GPG key ID: EF99694AA599DDAD
8 changed files with 206 additions and 14 deletions

View file

@ -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

View file

@ -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";
};

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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(),

View file

@ -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<MapArrayType<PackageInfo>> = await axios.get(`${settings.updateServer}/plugins.json`, {headers})
- availablePlugins = pluginsLoaded.data;
+ const pluginsLoaded: AxiosResponse<MapArrayType<PackageInfo>> = await axios.get(`${settings.updateServer}/plugins.json`, {headers});
+ const normalizedPlugins: MapArrayType<PackageInfo> = {};
+ 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;

View file

@ -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).