Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

froide-govplan: init at 0-unstable-2024-09-19 #349750

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -22,6 +22,8 @@

- [Amazon CloudWatch Agent](https://github.com/aws/amazon-cloudwatch-agent), the official telemetry collector for AWS CloudWatch and AWS X-Ray. Available as [services.amazon-cloudwatch-agent](options.html#opt-services.amazon-cloudwatch-agent.enable).

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

- [agorakit](https://github.com/agorakit/agorakit), an organization tool for citizens' collectives. Available with [services.agorakit](options.html#opt-services.agorakit.enable).

- [mqtt-exporter](https://github.com/kpetremann/mqtt-exporter/), a Prometheus exporter for exposing messages from MQTT. Available as [services.prometheus.exporters.mqtt](#opt-services.prometheus.exporters.mqtt.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,7 @@
./services/web-apps/flarum.nix
./services/web-apps/fluidd.nix
./services/web-apps/freshrss.nix
./services/web-apps/froide-govplan.nix
./services/web-apps/galene.nix
./services/web-apps/gancio.nix
./services/web-apps/gerrit.nix
Expand Down
229 changes: 229 additions & 0 deletions nixos/modules/services/web-apps/froide-govplan.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
{
config,
lib,
pkgs,
...
}:
let

cfg = config.services.froide-govplan;
pythonFmt = pkgs.formats.pythonVars { };
settingsFile = pythonFmt.generate "extra_settings.py" cfg.settings;

pkg = cfg.package.overridePythonAttrs (old: {
postInstall =
old.postInstall
+ ''
ln -s ${settingsFile} $out/${pkg.python.sitePackages}/froide_govplan/project/extra_settings.py
'';
});

# Service hardening
defaultServiceConfig = {
# Secure the services
ReadWritePaths = [ cfg.dataDir ];
CacheDirectory = "froide-govplan";
CapabilityBoundingSet = "";
# ProtectClock adds DeviceAllow=char-rtc r
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
# Disable this if you want to connect to an external database
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" { };

listenAddress = lib.mkOption {
type = lib.types.str;
default = "[::1]";
description = ''
Address the server will listen on.
'';
};

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.
'';
};

secretKeyFile = lib.mkOption {
type = lib.types.path;
description = ''
Path to a file containing the secret key.
'';
};

settings = lib.mkOption {
description = ''
Configuration options to set in `extra_settings.py`.
'';

default = { };

type = lib.types.submodule {
freeformType = pythonFmt.type;

options = {
ALLOWED_HOSTS = lib.mkOption {
type = with lib.types; listOf str;
default = [ "*" ];
description = ''
A list of valid fully-qualified domain names (FQDNs) and/or IP
addresses that can be used to reach the Froide-Govplan service.
'';
};
};
};
};

};

config = lib.mkIf cfg.enable {

services.froide-govplan = {
settings = {
STATIC_ROOT = "${cfg.dataDir}/static";
DEBUG = false;
DATABASES.default = {
ENGINE = "django.contrib.gis.db.backends.postgis";
NAME = "govplan";
USER = "govplan";
HOST = "/run/postgresql";
};
};
};

services.postgresql = {
enable = true;
ensureDatabases = [ "govplan" ];
ensureUsers = [
{
name = "govplan";
ensureDBOwnership = true;
}
];
extensions = ps: with ps; [ postgis ];
};

systemd = {
services = {

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

froide-govplan = {
description = "Gouvernment planer Govplan";
serviceConfig = defaultServiceConfig // {
WorkingDirectory = cfg.dataDir;
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/froide-govplan") "froide-govplan";
User = "govplan";
Group = "govplan";
};
after = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
environment = {
PYTHONPATH = pkg.pythonPath;
GDAL_LIBRARY_PATH = "${pkgs.gdal}/lib/libgdal.so";
GEOS_LIBRARY_PATH = "${pkgs.geos}/lib/libgeos_c.so";
};
preStart = ''
# Auto-migrate on first run or if the package has changed
versionFile="${cfg.dataDir}/src-version"
version=$(cat "$versionFile" 2>/dev/null || echo 0)

if [[ $version != ${pkg.version} ]]; then
${lib.getExe pkg} migrate --no-input
${lib.getExe pkg} collectstatic --no-input --clear
echo ${pkg.version} > "$versionFile"
fi
'';
script =
let
python = pkg.python;
networking = "--host ${cfg.listenAddress} --port ${toString cfg.port}";
in ''
${python.pkgs.uvicorn}/bin/uvicorn ${networking} \
--app-dir ${pkg}/${pkg.python.sitePackages} \
project.asgi:application
'';
};
};

};

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

environment.systemPackages = [ pkg ];

users.users.govplan = {
home = "${cfg.dataDir}";
isSystemUser = true;
group = "govplan";
};
users.groups.govplan = { };

};

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)
'';
}
)
15 changes: 15 additions & 0 deletions pkgs/by-name/fr/froide-govplan/load_extra_settings.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/project/settings.py b/project/settings.py
index dd282ac..64ff265 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -202,3 +202,10 @@ CMS_CONFIRM_VERSION4 = True

GOVPLAN_NAME = "GovPlan"
GOVPLAN_ENABLE_FOIREQUEST = False
+
+EXTRA_SETTINGS_PATH = os.path.join(PROJECT_DIR, 'extra_settings.py')
+
+if os.path.exists(EXTRA_SETTINGS_PATH):
+ with open(EXTRA_SETTINGS_PATH) as f:
+ code = compile(f.read(), EXTRA_SETTINGS_PATH, 'exec')
+ exec(code)
Loading
Loading