-
-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
{ | ||
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; | ||
# FIXME: Depends on database configuration, socket vs address | ||
#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. | ||
''; | ||
}; | ||
|
||
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; | ||
}; | ||
}; | ||
|
||
services.postgresql = { | ||
enable = true; | ||
ensureDatabases = [ "govplan" ]; | ||
ensureUsers = [ | ||
{ | ||
name = "govplan"; | ||
ensureDBOwnership = true; | ||
} | ||
]; | ||
extensions = 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 = defaultServiceConfig // { | ||
WorkingDirectory = cfg.dataDir; | ||
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/froide-govplan") "froide-govplan"; | ||
DynamicUser = true; | ||
}; | ||
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 ]; | ||
|
||
}; | ||
|
||
meta.maintainers = with lib.maintainers; [ onny ]; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
''; | ||
} | ||
) |