froide-govplan: init at 0-unstable-2025-01-27 (#349750)
This commit is contained in:
@@ -134,6 +134,8 @@
|
||||
|
||||
- [cross-seed](https://www.cross-seed.org), a tool to set-up fully automatic cross-seeding of torrents. Available as [services.cross-seed](#opt-services.cross-seed.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).
|
||||
|
||||
- [vivid](https://github.com/sharkdp/vivid), a generator for LS_COLOR. Available as [programs.vivid](#opt-programs.vivid.enable).
|
||||
|
||||
@@ -1516,6 +1516,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
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
{
|
||||
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
|
||||
'';
|
||||
});
|
||||
|
||||
froide-govplan = pkgs.writeShellApplication {
|
||||
name = "froide-govplan";
|
||||
runtimeInputs = [ pkgs.coreutils ];
|
||||
text = ''
|
||||
SUDO="exec"
|
||||
if [[ "$USER" != govplan ]]; then
|
||||
SUDO="exec /run/wrappers/bin/sudo -u govplan"
|
||||
fi
|
||||
$SUDO env ${lib.getExe pkg} "$@"
|
||||
'';
|
||||
};
|
||||
|
||||
# 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;
|
||||
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" { };
|
||||
|
||||
hostName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
description = "FQDN for the froide-govplan instance.";
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/froide-govplan";
|
||||
description = "Directory to store the Froide-Govplan server data.";
|
||||
};
|
||||
|
||||
secretKeyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
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 ];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = lib.mkDefault true;
|
||||
virtualHosts."${cfg.hostName}".locations = {
|
||||
"/".extraConfig = "proxy_pass http://unix:/run/froide-govplan/froide-govplan.socket;";
|
||||
"/static/".alias = "${cfg.dataDir}/static/";
|
||||
};
|
||||
proxyTimeout = lib.mkDefault "120s";
|
||||
};
|
||||
|
||||
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"
|
||||
"network.target"
|
||||
"systemd-tmpfiles-setup.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";
|
||||
}
|
||||
// lib.optionalAttrs (cfg.secretKeyFile != null) {
|
||||
SECRET_KEY_FILE = cfg.secretKeyFile;
|
||||
};
|
||||
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 = ''
|
||||
${pkg.python.pkgs.uvicorn}/bin/uvicorn --uds /run/froide-govplan/froide-govplan.socket \
|
||||
--app-dir ${pkg}/${pkg.python.sitePackages}/froide_govplan \
|
||||
project.asgi:application
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d /run/froide-govplan - govplan govplan - -" ];
|
||||
|
||||
environment.systemPackages = [ froide-govplan ];
|
||||
|
||||
users.users.govplan = {
|
||||
home = "${cfg.dataDir}";
|
||||
isSystemUser = true;
|
||||
group = "govplan";
|
||||
};
|
||||
users.groups.govplan = { };
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ onny ];
|
||||
|
||||
}
|
||||
@@ -450,6 +450,7 @@ in {
|
||||
freetube = discoverTests (import ./freetube.nix);
|
||||
freshrss = handleTest ./freshrss {};
|
||||
frigate = runTest ./frigate.nix;
|
||||
froide-govplan = runTest ./web-apps/froide-govplan.nix;
|
||||
frp = handleTest ./frp.nix {};
|
||||
frr = handleTest ./frr.nix {};
|
||||
fsck = handleTest ./fsck.nix {};
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
name = "froide-govplan";
|
||||
meta.maintainers = with lib.maintainers; [ onny ];
|
||||
|
||||
nodes = {
|
||||
machine = {
|
||||
virtualisation.memorySize = 2048;
|
||||
services.froide-govplan.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
changePassword = pkgs.writeText "change-password.py" ''
|
||||
from django.contrib.auth.models import User
|
||||
u = User.objects.get(username='govplan')
|
||||
u.set_password('govplan')
|
||||
u.save()
|
||||
'';
|
||||
in
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("froide-govplan.service")
|
||||
machine.wait_for_file("/run/froide-govplan/froide-govplan.socket")
|
||||
|
||||
with subtest("Home screen loads"):
|
||||
machine.succeed(
|
||||
"curl -sSfL http://[::1]:80 | grep '<title>django CMS</title>'"
|
||||
)
|
||||
|
||||
with subtest("Superuser can be created"):
|
||||
machine.succeed(
|
||||
"froide-govplan createsuperuser --noinput --username govplan --email govplan@example.com"
|
||||
)
|
||||
# Django doesn't have a "clean" way of inputting the password from the command line
|
||||
machine.succeed("cat '${changePassword}' | froide-govplan shell")
|
||||
'';
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
lib,
|
||||
python3Packages,
|
||||
fetchFromGitHub,
|
||||
makeBinaryWrapper,
|
||||
froide,
|
||||
gdal,
|
||||
geos,
|
||||
nixosTests,
|
||||
fetchpatch,
|
||||
froide-govplan,
|
||||
gettext,
|
||||
}:
|
||||
let
|
||||
python = python3Packages.python.override {
|
||||
packageOverrides = self: super: {
|
||||
django = super.django.override { withGdal = true; };
|
||||
};
|
||||
};
|
||||
in
|
||||
python.pkgs.buildPythonApplication rec {
|
||||
pname = "froide-govplan";
|
||||
version = "0-unstable-2025-01-27";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "okfde";
|
||||
repo = "froide-govplan";
|
||||
# No tagged release yet
|
||||
# https://github.com/okfde/froide-govplan/issues/15
|
||||
rev = "f1763807614b8c54a9214359a2a1e442ca58cb6d";
|
||||
hash = "sha256-Y7/qjhu3y9E55ZDmDf5HUk9JVcUTvi9KnqavqwNixTM=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# Add missing oauth2_provider app
|
||||
# https://github.com/okfde/froide-govplan/pull/17
|
||||
(fetchpatch {
|
||||
url = "https://github.com/okfde/froide-govplan/commit/bc388b693ebc7656fc7917511048a47b68e119fe.patch";
|
||||
hash = "sha256-StIyN3gFKd/fld14s03fsV6fmWinSRWINUyRRBzqVco=";
|
||||
name = "add_oauth2_provider_app.patch";
|
||||
})
|
||||
# Enable automatic image cropping for filer
|
||||
# https://github.com/okfde/froide-govplan/pull/19
|
||||
(fetchpatch {
|
||||
url = "https://github.com/okfde/froide-govplan/commit/d5db02f3bbc501929a901e964147a0ed2f974f41.patch";
|
||||
hash = "sha256-jf6e84fd2kQIPER2HGmZNkbtl067311xdNJRU+iFVXM=";
|
||||
name = "filer_enable_automatic_cropping.patch";
|
||||
})
|
||||
# Add support for reading SECRET_KEY_FILE
|
||||
# https://github.com/okfde/froide-govplan/pull/21
|
||||
(fetchpatch {
|
||||
url = "https://github.com/okfde/froide-govplan/commit/39dc381ed6c4afcbbaea616f0e8f44d5eeb96ec2.patch";
|
||||
hash = "sha256-PxBtB6VupK4QJiWKKlTmub+7tfi6nAHWe29lxpNgoVk=";
|
||||
name = "add_secret_key_file_support.patch";
|
||||
})
|
||||
|
||||
# Patch settings.py to source additional settings from the NixOS module
|
||||
./load_extra_settings.patch
|
||||
];
|
||||
|
||||
build-system = [ python.pkgs.setuptools ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
gettext
|
||||
makeBinaryWrapper
|
||||
];
|
||||
|
||||
build-inputs = [ gdal ];
|
||||
|
||||
dependencies = with python.pkgs; [
|
||||
bleach
|
||||
django-admin-sortable2
|
||||
django-cms
|
||||
django-filer
|
||||
django-mfa3
|
||||
django-mptt
|
||||
django-oauth-toolkit
|
||||
django-sekizai
|
||||
django-tinymce
|
||||
django-treebeard
|
||||
djangocms-alias
|
||||
# Downgrade to last working version
|
||||
(toPythonModule (
|
||||
froide.overridePythonAttrs (prev: {
|
||||
nativeBuildInputs = [ makeBinaryWrapper ];
|
||||
postBuild = "";
|
||||
doCheck = false;
|
||||
pnpmDeps = null;
|
||||
src = prev.src.override {
|
||||
rev = "a78a4054f9f37b0a5109a6d8cfbbda742f86a8ca";
|
||||
hash = "sha256-gtOssbsVf3nG+pmLPgvh4685vHh2x+jlXiTjU+JhQa8=";
|
||||
};
|
||||
})
|
||||
))
|
||||
psycopg
|
||||
];
|
||||
|
||||
preBuild = "${python.interpreter} -m django compilemessages";
|
||||
|
||||
postInstall = ''
|
||||
cp manage.py $out/${python.sitePackages}/froide_govplan/
|
||||
cp -r project $out/${python.sitePackages}/froide_govplan/
|
||||
cp -r froide_govplan/locale $out/${python.sitePackages}/froide_govplan/
|
||||
makeWrapper $out/${python.sitePackages}/froide_govplan/manage.py $out/bin/froide-govplan \
|
||||
--prefix PYTHONPATH : "$PYTHONPATH" \
|
||||
--set GDAL_LIBRARY_PATH "${gdal}/lib/libgdal.so" \
|
||||
--set GEOS_LIBRARY_PATH "${geos}/lib/libgeos_c.so"
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
tests = {
|
||||
inherit (nixosTests) froide-govplan;
|
||||
};
|
||||
python = python;
|
||||
pythonPath = "${python.pkgs.makePythonPath dependencies}:${froide-govplan}/${python.sitePackages}";
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Government planner and basis of FragDenStaat.de Koalitionstracker";
|
||||
homepage = "https://github.com/okfde/froide-govplan";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = [ lib.maintainers.onny ];
|
||||
mainProgram = "froide-govplan";
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user