Skip to content

Commit

Permalink
nixos/froide-govplan: init
Browse files Browse the repository at this point in the history
  • Loading branch information
onny committed Nov 24, 2024
1 parent aea118d commit a82bd32
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2505.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

- [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](option.html#opt-services.kimai).

- [Froide-Govplan](https://github.com/okfde/froide-govplan), a web application government planer. Available as [services.froide-govplan](#opt-services.froide-govplan.enable).

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

## Backward Incompatibilities {#sec-release-25.05-incompatibilities}
Expand Down
195 changes: 195 additions & 0 deletions nixos/modules/services/web-apps/froide-govplan.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
{
config,
lib,
pkgs,
...
}:
let

cfg = config.services.froide-govplan;
format = pkgs.formats.toml { };

# Service hardening
defaultServiceConfig = {
# Secure the services
ReadWritePaths = [ "/var/lib/froide-govplan" ];
CacheDirectory = "froide-govplan";
CapabilityBoundingSet = "";
# ProtectClock adds DeviceAllow=char-rtc r
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateNetwork = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectHome = true;
ProtectHostname = true;
ProtectSystem = "strict";
ProtectControlGroups = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
UMask = "0066";
};

in
{
options.services.froide-govplan = {

enable = lib.mkEnableOption "Gouvernment planer web app Govplan";

package = lib.mkPackageOption pkgs "froide-govplan" { };

address = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Web interface address.";
};

port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Web interface port.";
};

dataDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/froide-govplan";
description = "Directory to store the Froide-Govplan server data.";
};

openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to open ports in the firewall for the server.
'';
};

settings = lib.mkOption {
default = { };
description = ''
IMAP authentication configuration for rspamd-trainer. For supplying
the IMAP password, use the `secrets` option.
'';
type = lib.types.submodule { freeformType = format.type; };
example = lib.literalExpression ''
{
HOST = "localhost";
USERNAME = "spam@example.com";
INBOXPREFIX = "INBOX/";
}
'';
};

secrets = lib.mkOption {
type = with lib.types; listOf path;
description = ''
A list of files containing the various secrets. Should be in the
format expected by systemd's `EnvironmentFile` directory. For the
IMAP account password use `PASSWORD = mypassword`.
'';
default = [ ];
};

};

config = lib.mkIf cfg.enable {

services.postgresql = {
enable = true;
ensureDatabases = [ "govplan" ];
ensureUsers = [
{
name = "govplan";
ensureDBOwnership = true;
}
];
extraPlugins = ps: with ps; [ postgis ];
authentication = ''
host govplan govplan localhost trust
'';
initialScript = pkgs.writeText "backend-initScript" ''
ALTER USER govplan WITH SUPERUSER;
'';
};

systemd = {
services = {

postgresql.serviceConfig.ExecStartPost =
let
sqlFile = pkgs.writeText "immich-pgvectors-setup.sql" ''
ALTER USER govplan WITH SUPERUSER;
#CREATE EXTENSION IF NOT EXISTS postgis;
#ALTER SCHEMA govplan OWNER TO govplan;
#ALTER EXTENSION govplan UPDATE;
'';
in
[
''
${lib.getExe' config.services.postgresql.package "psql"} -d govplan -f "${sqlFile}"
''
];

froide-govplan = {
description = "Gouvernment planer Govplan";
serviceConfig = {
ExecStart = "${pkgs.froide-govplan}/bin/froide-govplan runserver ${cfg.address}:${toString cfg.port}";
WorkingDirectory = "/var/lib/froide-govplan";
StateDirectory = [ "froide-govplan" ];
DynamicUser = true;
EnvironmentFile = [
(format.generate "froide-govplan-env" cfg.settings)
cfg.secrets
];
};
after = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
preStart = ''
# Auto-migrate on first run or if the package has changed
versionFile="/var/lib/froide-govplan/src-version"
version=$(cat "$versionFile" 2>/dev/null || echo 0)
if [[ $version != ${cfg.package.version} ]]; then
${cfg.package}/bin/froide-govplan migrate
${cfg.package}/bin/froide-govplan migrate djangocms_alias
# Parse old version string format for backwards compatibility
version=$(echo "$version" | grep -ohP '[^-]+$')
versionLessThan() {
target=$1
[[ $({ echo "$version"; echo "$target"; } | sort -V | head -1) != "$target" ]]
}
echo ${cfg.package.version} > "$versionFile"
fi
'';
};
};

};

networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};

};

meta.maintainers = with lib.maintainers; [ onny ];

}
109 changes: 109 additions & 0 deletions nixos/tests/froide-govplan.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import ./make-test-python.nix (
{ lib, ... }:
{
name = "paperless";
meta.maintainers = with lib.maintainers; [
leona
SuperSandro2000
erikarvstedt
];

nodes =
let
self = {
simple =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
imagemagick
jq
];
services.paperless = {
enable = true;
passwordFile = builtins.toFile "password" "admin";
};
};
postgres =
{ config, pkgs, ... }:
{
imports = [ self.simple ];
services.postgresql = {
enable = true;
ensureDatabases = [ "paperless" ];
ensureUsers = [
{
name = config.services.paperless.user;
ensureDBOwnership = true;
}
];
};
services.paperless.settings = {
PAPERLESS_DBHOST = "/run/postgresql";
PAPERLESS_OCR_LANGUAGE = "deu";
};
};
};
in
self;

testScript = ''
import json
def test_paperless(node):
node.wait_for_unit("paperless-consumer.service")
with subtest("Add a document via the file system"):
node.succeed(
"convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
"-annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
)
with subtest("Web interface gets ready"):
node.wait_for_unit("paperless-web.service")
# Wait until server accepts connections
node.wait_until_succeeds("curl -fs localhost:28981")
# Required for consuming documents via the web interface
with subtest("Task-queue gets ready"):
node.wait_for_unit("paperless-task-queue.service")
with subtest("Add a png document via the web interface"):
node.succeed(
"convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
"-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png"
)
node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/")
with subtest("Add a txt document via the web interface"):
node.succeed(
"echo 'hello web 16-10-2005' > /tmp/webdoc.txt"
)
node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.txt -fs localhost:28981/api/documents/post_document/")
with subtest("Documents are consumed"):
node.wait_until_succeeds(
"(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 3))"
)
docs = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/"))['results']
assert "2005-10-16" in docs[0]['created']
assert "2005-10-16" in docs[1]['created']
assert "2005-10-16" in docs[2]['created']
# Detects gunicorn issues, see PR #190888
with subtest("Document metadata can be accessed"):
metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/1/metadata/"))
assert "original_checksum" in metadata
metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/2/metadata/"))
assert "original_checksum" in metadata
metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/"))
assert "original_checksum" in metadata
test_paperless(simple)
simple.send_monitor_command("quit")
simple.wait_for_shutdown()
test_paperless(postgres)
'';
}
)

0 comments on commit a82bd32

Please sign in to comment.