diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 31e90c30cf17..0963591d59ea 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -44,6 +44,9 @@ - [eris-server](https://codeberg.org/eris/eris-go). [ERIS](https://eris.codeberg.page/) is an encoding for immutable storage and this server provides block exchange as well as content decoding over HTTP and through a FUSE file-system. Available as [services.eris-server](#opt-services.eris-server.enable). +- [Honk](https://humungus.tedunangst.com/r/honk), a complete ActivityPub server with minimal setup and support costs. + Available as [services.honk](#opt-services.honk.enable). + ## Backward Incompatibilities {#sec-release-23.11-incompatibilities} - The `boot.loader.raspberryPi` options have been marked deprecated, with intent for removal for NixOS 24.11. They had a limited use-case, and do not work like people expect. They required either very old installs ([before mid-2019](https://github.com/NixOS/nixpkgs/pull/62462)) or customized builds out of scope of the standard and generic AArch64 support. That option set never supported the Raspberry Pi 4 family of devices. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0fff271c8684..fa897959622b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1223,6 +1223,7 @@ ./services/web-apps/healthchecks.nix ./services/web-apps/hedgedoc.nix ./services/web-apps/hledger-web.nix + ./services/web-apps/honk.nix ./services/web-apps/icingaweb2/icingaweb2.nix ./services/web-apps/icingaweb2/module-monitoring.nix ./services/web-apps/invidious.nix diff --git a/nixos/modules/services/web-apps/honk.md b/nixos/modules/services/web-apps/honk.md new file mode 100644 index 000000000000..f34085f7dc52 --- /dev/null +++ b/nixos/modules/services/web-apps/honk.md @@ -0,0 +1,23 @@ +# Honk {#module-services-honk} + +With Honk on NixOS you can quickly configure a complete ActivityPub server with +minimal setup and support costs. + +## Basic usage {#module-services-honk-basic-usage} + +A minimal configuration looks like this: + +```nix +{ + services.honk = { + enable = true; + host = "0.0.0.0"; + port = 8080; + username = "username"; + passwordFile = "/etc/honk/password.txt"; + servername = "honk.example.com"; + }; + + networking.firewall.allowedTCPPorts = [ 8080 ]; +} +``` diff --git a/nixos/modules/services/web-apps/honk.nix b/nixos/modules/services/web-apps/honk.nix new file mode 100644 index 000000000000..e8718774575b --- /dev/null +++ b/nixos/modules/services/web-apps/honk.nix @@ -0,0 +1,153 @@ +{ config +, lib +, pkgs +, ... +}: +let + cfg = config.services.honk; + + honk-initdb-script = cfg: pkgs.writeShellApplication { + name = "honk-initdb-script"; + + runtimeInputs = with pkgs; [ coreutils ]; + + text = '' + PW=$(cat "$CREDENTIALS_DIRECTORY/honk_passwordFile") + + echo -e "${cfg.username}\n''$PW\n${cfg.host}:${toString cfg.port}\n${cfg.servername}" | ${lib.getExe cfg.package} -datadir "$STATE_DIRECTORY" init + ''; + }; +in +{ + options = { + services.honk = { + enable = lib.mkEnableOption (lib.mdDoc "the Honk server"); + package = lib.mkPackageOptionMD pkgs "honk" { }; + + host = lib.mkOption { + default = "127.0.0.1"; + description = lib.mdDoc '' + The host name or IP address the server should listen to. + ''; + type = lib.types.str; + }; + + port = lib.mkOption { + default = 8080; + description = lib.mdDoc '' + The port the server should listen to. + ''; + type = lib.types.port; + }; + + username = lib.mkOption { + description = lib.mdDoc '' + The admin account username. + ''; + type = lib.types.str; + }; + + passwordFile = lib.mkOption { + description = lib.mdDoc '' + Password for admin account. + NOTE: Should be string not a store path, to prevent the password from being world readable + ''; + type = lib.types.path; + }; + + servername = lib.mkOption { + description = lib.mdDoc '' + The server name. + ''; + type = lib.types.str; + }; + + extraJS = lib.mkOption { + default = null; + description = lib.mdDoc '' + An extra JavaScript file to be loaded by the client. + ''; + type = lib.types.nullOr lib.types.path; + }; + + extraCSS = lib.mkOption { + default = null; + description = lib.mdDoc '' + An extra CSS file to be loaded by the client. + ''; + type = lib.types.nullOr lib.types.path; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.username or "" != ""; + message = '' + You have to define a username for Honk (`services.honk.username`). + ''; + } + { + assertion = cfg.servername or "" != ""; + message = '' + You have to define a servername for Honk (`services.honk.servername`). + ''; + } + ]; + + systemd.services.honk-initdb = { + description = "Honk server database setup"; + requiredBy = [ "honk.service" ]; + before = [ "honk.service" ]; + + serviceConfig = { + LoadCredential = [ + "honk_passwordFile:${cfg.passwordFile}" + ]; + Type = "oneshot"; + StateDirectory = "honk"; + DynamicUser = true; + RemainAfterExit = true; + ExecStart = lib.getExe (honk-initdb-script cfg); + PrivateTmp = true; + }; + + unitConfig = { + ConditionPathExists = [ + # Skip this service if the database already exists + "!$STATE_DIRECTORY/honk.db" + ]; + }; + }; + + systemd.services.honk = { + description = "Honk server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + bindsTo = [ "honk-initdb.service" ]; + preStart = '' + mkdir -p $STATE_DIRECTORY/views + ${lib.optionalString (cfg.extraJS != null) "ln -fs ${cfg.extraJS} $STATE_DIRECTORY/views/local.js"} + ${lib.optionalString (cfg.extraCSS != null) "ln -fs ${cfg.extraCSS} $STATE_DIRECTORY/views/local.css"} + ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk backup $STATE_DIRECTORY/backup + ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk upgrade + ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk cleanup + ''; + serviceConfig = { + ExecStart = '' + ${lib.getExe cfg.package} -datadir $STATE_DIRECTORY -viewdir ${cfg.package}/share/honk + ''; + StateDirectory = "honk"; + DynamicUser = true; + PrivateTmp = "yes"; + Restart = "on-failure"; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ drupol ]; + doc = ./honk.md; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4b338dac69a7..9c7e16c2630e 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -344,6 +344,7 @@ in { hedgedoc = handleTest ./hedgedoc.nix {}; herbstluftwm = handleTest ./herbstluftwm.nix {}; homepage-dashboard = handleTest ./homepage-dashboard.nix {}; + honk = runTest ./honk.nix; installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); invidious = handleTest ./invidious.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; diff --git a/nixos/tests/honk.nix b/nixos/tests/honk.nix new file mode 100644 index 000000000000..71d86a592439 --- /dev/null +++ b/nixos/tests/honk.nix @@ -0,0 +1,32 @@ +{ lib, ... }: + +{ + name = "honk-server"; + + nodes = { + machine = { pkgs, ... }: { + services.honk = { + enable = true; + host = "0.0.0.0"; + port = 8080; + username = "username"; + passwordFile = "${pkgs.writeText "honk-password" "secure"}"; + servername = "servername"; + }; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("honk.service") + machine.wait_for_open_port(8080) + + machine.stop_job("honk") + machine.wait_for_closed_port(8080) + + machine.start_job("honk") + machine.wait_for_open_port(8080) + ''; + + meta.maintainers = [ lib.maintainers.drupol ]; +}