nixos/gancio: init module

This commit is contained in:
Jean-Baptiste Giraudeau 2024-05-03 10:35:19 +02:00
parent 0e639f2cc0
commit f5e44554c4
No known key found for this signature in database
GPG Key ID: 7CEF8C9CC2D9933B
5 changed files with 371 additions and 0 deletions

View File

@ -44,6 +44,8 @@
- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service.
- [Gancio](https://gancio.org/), a shared agenda for local communities. Available as [services.gancio](#opt-services.gancio.enable).
- [Goatcounter](https://www.goatcounter.com/), Easy web analytics. No tracking of personal data. Available as [services.goatcounter](options.html#opt-services.goatcocunter.enable).
- [UWSM](https://github.com/Vladimir-csp/uwsm), a wayland session manager to wrap Wayland Compositors into useful systemd units such as `graphical-session.target`. Available as [programs.uwsm](#opt-programs.uwsm.enable).

View File

@ -1411,6 +1411,7 @@
./services/web-apps/fluidd.nix
./services/web-apps/freshrss.nix
./services/web-apps/galene.nix
./services/web-apps/gancio.nix
./services/web-apps/gerrit.nix
./services/web-apps/glance.nix
./services/web-apps/gotify-server.nix

View File

@ -0,0 +1,280 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.gancio;
settingsFormat = pkgs.formats.json { };
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
types
literalExpression
mkIf
optional
mapAttrsToList
concatStringsSep
concatMapStringsSep
getExe
mkMerge
mkDefault
;
in
{
options.services.gancio = {
enable = mkEnableOption "Gancio, a shared agenda for local communities";
package = mkPackageOption pkgs "gancio" { };
plugins = mkOption {
type = with types; listOf package;
default = [ ];
example = literalExpression "[ pkgs.gancioPlugins.telegram-bridge ]";
description = ''
Paths of gancio plugins to activate (linked under $WorkingDirectory/plugins/).
'';
};
user = mkOption {
type = types.str;
description = "The user (and PostgreSQL database name) used to run the gancio server";
default = "gancio";
};
settings = mkOption rec {
type = types.submodule {
freeformType = settingsFormat.type;
options = {
hostname = mkOption {
type = types.str;
description = "The domain name under which the server is reachable.";
};
baseurl = mkOption {
type = types.str;
default = "";
example = "/gancio";
description = "The URL path under which the server is reachable.";
};
server = {
host = mkOption {
type = types.str;
default = "localhost";
example = "::";
description = ''
The address (IPv4, IPv6 or DNS) for the gancio server to listen on.
'';
};
port = mkOption {
type = types.port;
default = 13120;
description = ''
Port number of the gancio server to listen on.
'';
};
};
db = {
dialect = mkOption {
type = types.enum [
"sqlite"
"postgres"
];
default = "sqlite";
description = ''
The database dialect to use
'';
};
storage = mkOption {
description = ''
Location for the SQLite database.
'';
readOnly = true;
type = types.nullOr types.str;
default = if cfg.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null;
defaultText = ''
if cfg.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null
'';
};
host = mkOption {
description = ''
Connection string for the PostgreSQL database
'';
readOnly = true;
type = types.nullOr types.str;
default = if cfg.settings.db.dialect == "postgres" then "/run/postgresql" else null;
defaultText = ''
if cfg.settings.db.dialect == "postgres" then "/run/postgresql" else null
'';
};
database = mkOption {
description = ''
Name of the PostgreSQL database
'';
readOnly = true;
type = types.nullOr types.str;
default = if cfg.settings.db.dialect == "postgres" then cfg.user else null;
defaultText = ''
if cfg.settings.db.dialect == "postgres" then cfg.user else null
'';
};
};
log_level = mkOption {
description = "Gancio log level.";
type = types.enum [
"debug"
"info"
"warning"
"error"
];
default = "info";
};
# FIXME upstream proper journald logging
log_path = mkOption {
description = "Directory Gancio logs into";
readOnly = true;
type = types.str;
default = "/var/log/gancio";
};
};
};
description = ''
Configuration for Gancio, see <https://gancio.org/install/config> for supported values.
'';
};
userLocale = mkOption {
type = with types; attrsOf (attrsOf (attrsOf str));
default = { };
example = {
en.register.description = "My new registration page description";
};
description = ''
Override default locales within gancio.
See [https://framagit.org/les/gancio/tree/master/locales](default languages and locales).
'';
};
nginx = mkOption {
type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
default = { };
example = {
enableACME = true;
forceSSL = true;
};
description = "Extra configuration for the nginx virtual host of gancio.";
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
users.users.gancio = lib.mkIf (cfg.user == "gancio") {
isSystemUser = true;
group = cfg.user;
home = "/var/lib/gancio";
};
users.groups.gancio = lib.mkIf (cfg.user == "gancio") { };
systemd.tmpfiles.settings."10-gancio" =
let
rules = {
mode = "0755";
user = cfg.user;
group = config.users.users.${cfg.user}.group;
};
in
{
"/var/lib/gancio/user_locale".d = rules;
"/var/lib/gancio/plugins".d = rules;
};
systemd.services.gancio =
let
configFile = settingsFormat.generate "gancio-config.json" cfg.settings;
in
{
description = "Gancio server";
documentation = [ "https://gancio.org/" ];
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service";
environment = {
NODE_ENV = "production";
};
preStart = ''
# We need this so the gancio executable run by the user finds the right settings.
ln -sf ${configFile} config.json
rm -f user_locale/*
${concatStringsSep "\n" (
mapAttrsToList (
l: c: "ln -sf ${settingsFormat.generate "gancio-${l}-locale.json" c} user_locale/${l}.json"
) cfg.userLocale
)}
rm -f plugins/*
${concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/") cfg.plugins}
'';
serviceConfig = {
ExecStart = "${getExe cfg.package} start ${configFile}";
StateDirectory = "gancio";
WorkingDirectory = "/var/lib/gancio";
LogsDirectory = "gancio";
User = cfg.user;
# hardening
RestrictRealtime = true;
RestrictNamespaces = true;
LockPersonality = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
ProtectClock = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
CapabilityBoundingSet = "";
ProtectProc = "invisible";
};
};
services.postgresql = mkIf (cfg.settings.db.dialect == "postgres") {
enable = true;
ensureDatabases = [ cfg.user ];
ensureUsers = [
{
name = cfg.user;
ensureDBOwnership = true;
}
];
};
services.nginx = {
enable = true;
virtualHosts."${cfg.settings.hostname}" = mkMerge [
cfg.nginx
{
enableACME = mkDefault true;
forceSSL = mkDefault true;
locations = {
"/" = {
index = "index.html";
tryFiles = "$uri $uri @proxy";
};
"@proxy" = {
proxyWebsockets = true;
proxyPass = "http://${cfg.settings.server.host}:${toString cfg.settings.server.port}";
recommendedProxySettings = true;
};
};
}
];
};
};
}

View File

@ -365,6 +365,7 @@ in {
ft2-clone = handleTest ./ft2-clone.nix {};
legit = handleTest ./legit.nix {};
mimir = handleTest ./mimir.nix {};
gancio = handleTest ./gancio.nix {};
garage = handleTest ./garage {};
gemstash = handleTest ./gemstash.nix {};
geoserver = runTest ./geoserver.nix;

87
nixos/tests/gancio.nix Normal file
View File

@ -0,0 +1,87 @@
import ./make-test-python.nix (
{ pkgs, ... }:
let
extraHosts = ''
192.168.13.12 agenda.example.com
'';
in
{
name = "gancio";
meta.maintainers = with pkgs.lib.maintainers; [ jbgi ];
nodes = {
server =
{ pkgs, ... }:
{
networking = {
interfaces.eth1 = {
ipv4.addresses = [
{
address = "192.168.13.12";
prefixLength = 24;
}
];
};
inherit extraHosts;
firewall.allowedTCPPorts = [ 80 ];
};
environment.systemPackages = [ pkgs.gancio ];
services.gancio = {
enable = true;
settings = {
hostname = "agenda.example.com";
db.dialect = "postgres";
};
plugins = [ pkgs.gancioPlugins.telegram-bridge ];
userLocale = {
en = {
register = {
description = "My new registration page description";
};
};
};
nginx = {
enableACME = false;
forceSSL = false;
};
};
};
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.jq ];
networking = {
interfaces.eth1 = {
ipv4.addresses = [
{
address = "192.168.13.1";
prefixLength = 24;
}
];
};
inherit extraHosts;
};
};
};
testScript = ''
start_all()
server.wait_for_unit("postgresql")
server.wait_for_unit("gancio")
server.wait_for_unit("nginx")
server.wait_for_open_port(13120)
server.wait_for_open_port(80)
# Check can create user via cli
server.succeed("cd /var/lib/gancio && sudo -u gancio gancio users create admin dummy admin")
# Check event list is returned
client.wait_until_succeeds("curl --verbose --fail-with-body http://agenda.example.com/api/events", timeout=30)
server.shutdown()
client.shutdown()
'';
}
)