From 6bf1d003fef9273922e91f594c79c7cee720eaaf Mon Sep 17 00:00:00 2001 From: Thary Date: Fri, 7 Nov 2025 15:16:45 +0300 Subject: [PATCH] Initial commit --- config-paths-needed.json | 11 +++ flake.nix | 36 +++++++++ icon.svg | 20 +++++ module.nix | 160 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 config-paths-needed.json create mode 100644 flake.nix create mode 100644 icon.svg create mode 100644 module.nix diff --git a/config-paths-needed.json b/config-paths-needed.json new file mode 100644 index 0000000..0b9c963 --- /dev/null +++ b/config-paths-needed.json @@ -0,0 +1,11 @@ +[ + [ "selfprivacy", "domain" ], + [ "selfprivacy", "modules", "auth", "enable" ], + [ "selfprivacy", "modules", "hedgedoc" ], + [ "selfprivacy", "passthru", "auth", "mkOAuth2ClientSecretFP" ], + [ "selfprivacy", "passthru", "auth", "oauth2-discovery-url" ], + [ "selfprivacy", "passthru", "auth", "oauth2-provider-name" ], + [ "selfprivacy", "sso", "enable" ], + [ "selfprivacy", "useBinds" ], + [ "services", "kanidm", "serverSettings", "origin" ] +] diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4c3e90f --- /dev/null +++ b/flake.nix @@ -0,0 +1,36 @@ +{ + description = "HedgeDoc module"; + + outputs = { ... }: + { + nixosModules.default = import ./module.nix; + configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json); + meta = + { lib, ... }: + { + spModuleSchemaVersion = 1; + id = "writefreely"; + name = "WriteFreely"; + description = "An open source platform for building a writing space on the web."; + svgIcon = builtins.readFile ./icon.svg; + isMovable = true; + isRequired = false; + backupDescription = "Your articles and attachments."; + systemdServices = [ + "writefreely.service" + ]; + folders = [ + "/var/lib/writefreely" + ]; + license = [ + lib.licenses.agpl3Only + ]; + homepage = "https://writefreely.org"; + sourcePage = "https://github.com/writefreely/writefreely"; + supportLevel = "normal"; + sso = { + userGroup = "sp.writefreely.users"; + }; + }; + }; +} diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..6f5677a --- /dev/null +++ b/icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..c69fe23 --- /dev/null +++ b/module.nix @@ -0,0 +1,160 @@ +{ + config, + lib, + pkgs, + ... +}: +let + sp = config.selfprivacy; + cfg = sp.modules.writefreely; + + oauthClientID = "writefreely"; + auth-passthru = config.selfprivacy.passthru.auth; + oauth2-provider-origin = config.services.kanidm.serverSettings.origin; + usersGroup = "sp.writefreely.users"; + oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP oauthClientID; +in +{ + options.selfprivacy.modules.writefreely = { + enable = + (lib.mkOption { + default = false; + type = lib.types.bool; + description = "Enable WriteFreely"; + }) + // { + meta = { + type = "enable"; + }; + }; + location = + (lib.mkOption { + type = lib.types.str; + description = "WriteFreely location"; + }) + // { + meta = { + type = "location"; + }; + }; + subdomain = + (lib.mkOption { + default = "writefreely"; + type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]"; + description = "Subdomain (changing subdomain after setting up will cause breakage of the federation!)"; + }) + // { + meta = { + widget = "subdomain"; + type = "string"; + regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]"; + weight = 0; + }; + }; + enableFederation = + (lib.mkOption { + default = false; + type = lib.types.bool; + description = "Enable the ActivityPub federation."; + }) + // { + meta = { + type = "bool"; + weight = 1; + }; + }; + title = + (lib.mkOption { + default = "WriteFreely"; + type = lib.types.str; + description = "Name of the WriteFreely instance."; + }) + // { + meta = { + type = "str"; + weight = 2; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = sp.sso.enable; + message = "WriteFreely cannot be enabled when SSO is disabled."; + } + ]; + + fileSystems = lib.mkIf sp.useBinds { + "/var/lib/writefreely" = { + device = "/volumes/${cfg.location}/writefreely"; + options = [ "bind" ]; + }; + }; + + services.writefreely = { + enable = true; + database.type = "sqlite3"; + host = "${cfg.subdomain}.${sp.domain}"; + settings = { + server.port = 8081; + app = { + site_name = cfg.title; + single_user = false; + federation = cfg.enableFederation; + disable_password_auth = true; + open_registration = false; + }; + + "oauth.generic" = { + client_id = oauthClientID; + host = oauth2-provider-origin; + display_name = "SSO"; + token_endpoint = "/oauth2/token"; + inspect_endpoint = "/oauth2/openid/${oauthClientID}/userinfo"; + auth_endpoint = "/ui/oauth2"; + }; + }; + }; + + systemd = { + services.writefreely = { + requeres = [ "writefreely-secrets.service" ]; + unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/writefreely"; + serviceConfig.Slice = "writefreely.slice"; + }; + + services.writefreely-secrets = let inherit (config.services.writefreely) stateDir; + in { + wantedBy = [ "multi-user.target" ]; + requiredBy = [ "writefreely.service" ]; + + serviceConfig = { + Type = "oneshot"; + Slice = "writefreely.slice"; + }; + + script = let crudini = lib.getExe pkgs.crudini; + in '' + ${crudini} --set ${stateDir}/config.ini oauth.generic client_secret '$(cat ${oauthClientSecretFP})' + ''; + + }; + + slices.writefreely = { + description = "WriteFreely service slice"; + }; + }; + + selfprivacy.auth.clients.${oauthClientID} = { + inherit usersGroup; + subdomain = cfg.subdomain; + originLanding = "https://${cfg.subdomain}.${sp.domain}/"; + originUrl = "https://${cfg.subdomain}.${sp.domain}/auth/oauth2/callback"; + clientSystemdUnits = [ "writefreely.service" ]; + enablePkce = false; + linuxUserOfClient = "writefreely"; + linuxGroupOfClient = "writefreely"; + }; + }; +}