514 lines
16 KiB
Nix
514 lines
16 KiB
Nix
{
|
|
description = "Bitpoll - A web application for scheduling meetings and general polling";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
|
flake-utils.url = "github:numtide/flake-utils";
|
|
};
|
|
|
|
outputs = { self, nixpkgs, flake-utils }:
|
|
flake-utils.lib.eachDefaultSystem (system:
|
|
let
|
|
pkgs = nixpkgs.legacyPackages.${system};
|
|
|
|
# Pin to current master commit
|
|
bitpollSrc = pkgs.fetchFromGitHub {
|
|
owner = "fsinfuhh";
|
|
repo = "Bitpoll";
|
|
rev = "4a3e6a5e3500308a428a6c7644f50d423adca6fc";
|
|
sha256 = "sha256-R4OwQdSJu9+EAlkhYOEe2ZOrS9oOA1ifg/iY6uzYSpE=";
|
|
};
|
|
|
|
# Create django-simple-csp derivation
|
|
django-simple-csp = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-simple-csp";
|
|
version = "0.4.dev1";
|
|
|
|
src = pkgs.fetchFromGitHub {
|
|
owner = "fsinfuhh";
|
|
repo = "django-simple-csp";
|
|
rev = "207afedec1bf28af5de070da919061474b1f6a21";
|
|
sha256 = "sha256-1rvdplHPjVZ6li831/sQT+lL4rE8ME1eLfeSd0EKHb4=";
|
|
};
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Content Security Policy support";
|
|
homepage = "https://github.com/fsinfuhh/django-simple-csp";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create django-markdownify derivation
|
|
django-markdownify = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-markdownify";
|
|
version = "0.9.5";
|
|
|
|
src = pkgs.fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/6c/33/3abb966e2b238af4c9a5d3ee38a7aa7e51b644b4b20bf8533b6fd1c1bf96/django_markdownify-0.9.5.tar.gz";
|
|
sha256 = "sha256-NMNOukp5coKlxb2XsTzshNakwGc61HzhwdAA103Y1Ks=";
|
|
};
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
markdown
|
|
bleach
|
|
tinycss2
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Markdownify";
|
|
homepage = "https://github.com/erwinmatijsen/django-markdownify";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create django-pipeline derivation
|
|
django-pipeline = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-pipeline";
|
|
version = "3.1.0";
|
|
|
|
src = pkgs.fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/2c/22/24a8fdfb07ebd60ed9af004cfbef4df0ee5af9fb9c06ad1d3e6e1e5b33ac/django_pipeline-3.1.0.tar.gz";
|
|
sha256 = "sha256-NkcK7O1IiWlLiZqZW80u2lEvRbYaxo1QrcWGPIutXuQ=";
|
|
};
|
|
|
|
nativeBuildInputs = with pkgs.python3.pkgs; [
|
|
setuptools-scm
|
|
];
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Pipeline";
|
|
homepage = "https://github.com/jazzband/django-pipeline";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create django-friendly-tag-loader derivation
|
|
django-friendly-tag-loader = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-friendly-tag-loader";
|
|
version = "1.3.1";
|
|
|
|
src = pkgs.fetchPypi {
|
|
inherit pname version;
|
|
sha256 = "sha256-MVkoByRe71G6m3HKf3qqMbP7YYtVmNMCbK6xo4bhKJo=";
|
|
};
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Friendly Tag Loader";
|
|
homepage = "https://github.com/jazzband/django-friendly-tag-loader";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create django-token-bucket derivation
|
|
django-token-bucket = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-token-bucket";
|
|
version = "0.2.4";
|
|
|
|
src = pkgs.fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/68/38/b940b6660f89efa969cce6ef5a43ea96cfa92ba57baf97a11c8dda87e413/django_token_bucket-0.2.4.tar.gz";
|
|
sha256 = "sha256-hkj4Vo2OXI0IR/T6/qzt5ZPPLCFJNnNWyEm9hkervCQ=";
|
|
};
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Token Bucket";
|
|
homepage = "https://github.com/dcwatson/django-token-bucket";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create django-widget-tweaks derivation
|
|
django-widget-tweaks = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "django-widget-tweaks";
|
|
version = "1.5.0";
|
|
|
|
src = pkgs.fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/a5/fe/26eb92fba83844e71bbec0ced7fc2e843e5990020e3cc676925204031654/django-widget-tweaks-1.5.0.tar.gz";
|
|
sha256 = "sha256-HCGAaB67mU6SLHVIBMf/674SRQFHd6xHiXqB9XzGKcc=";
|
|
};
|
|
|
|
nativeBuildInputs = with pkgs.python3.pkgs; [
|
|
setuptools-scm
|
|
];
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
django
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "Django Widget Tweaks";
|
|
homepage = "https://github.com/jazzband/django-widget-tweaks";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create libsasscompiler derivation
|
|
libsasscompiler = pkgs.python3.pkgs.buildPythonPackage rec {
|
|
pname = "libsasscompiler";
|
|
version = "0.1.9";
|
|
|
|
src = pkgs.fetchPypi {
|
|
inherit pname version;
|
|
sha256 = "sha256-OQ9hJ/8tR4U4O4Oj6UlD47Ac9CclH22Ys9Kkhj1RzBQ=";
|
|
};
|
|
|
|
propagatedBuildInputs = with pkgs.python3.pkgs; [
|
|
setuptools
|
|
libsass
|
|
];
|
|
|
|
doCheck = false; # Skip tests during build
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "LibSass Compiler";
|
|
homepage = "https://github.com/sass/libsass-python";
|
|
license = licenses.mit;
|
|
};
|
|
};
|
|
|
|
# Create settings_local.py for production
|
|
settingsLocal = pkgs.writeText "settings_local.py" ''
|
|
import os
|
|
import secrets
|
|
|
|
# Generate secret key if not provided via environment
|
|
SECRET_KEY = os.environ.get('BITPOLL_SECRET_KEY', secrets.token_urlsafe(50))
|
|
|
|
# Generate field encryption key if not provided via environment
|
|
FIELD_ENCRYPTION_KEY = os.environ.get('BITPOLL_FIELD_ENCRYPTION_KEY', secrets.token_urlsafe(32) + "=")
|
|
|
|
DEBUG = False
|
|
|
|
ALLOWED_HOSTS = ['*'] # Configure appropriately for production
|
|
|
|
# SQLite database configuration
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.sqlite3',
|
|
'NAME': '/var/lib/bitpoll/db.sqlite3',
|
|
}
|
|
}
|
|
|
|
# Static files
|
|
STATIC_ROOT = '/var/lib/bitpoll/static'
|
|
|
|
# Media files
|
|
MEDIA_ROOT = '/var/lib/bitpoll/media'
|
|
|
|
# Locale
|
|
LANGUAGE_CODE = 'en-us'
|
|
TIME_ZONE = 'UTC'
|
|
|
|
# Site configuration
|
|
SITE_NAME = 'Bitpoll'
|
|
|
|
# Additional apps
|
|
INSTALLED_APPS_LOCAL = []
|
|
MIDDLEWARE_LOCAL = []
|
|
PIPELINE_LOCAL = {}
|
|
|
|
# Registration enabled by default
|
|
REGISTER_ENABLED = True
|
|
GROUP_MANAGEMENT = True
|
|
'';
|
|
|
|
# Create Python environment with all dependencies
|
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
|
# Core Django dependencies
|
|
django
|
|
django-simple-csp
|
|
django-markdownify
|
|
django-pipeline
|
|
django-friendly-tag-loader
|
|
django-token-bucket
|
|
django-widget-tweaks
|
|
libsasscompiler
|
|
|
|
# Calendar and date handling
|
|
caldav
|
|
icalendar
|
|
python-dateutil
|
|
pytz
|
|
vobject
|
|
recurring-ical-events
|
|
|
|
# Crypto and security
|
|
cryptography
|
|
django-encrypted-model-fields
|
|
|
|
# Web and HTTP
|
|
requests
|
|
|
|
# Markup and styling
|
|
markdown
|
|
bleach
|
|
tinycss2
|
|
|
|
# Data handling
|
|
pydantic
|
|
|
|
# Database
|
|
psycopg2
|
|
|
|
# Utilities
|
|
six
|
|
lxml
|
|
|
|
# Additional dependencies
|
|
setuptools
|
|
wheel
|
|
pip
|
|
]);
|
|
|
|
bitpoll = pkgs.stdenv.mkDerivation rec {
|
|
pname = "bitpoll";
|
|
version = "master-${builtins.substring 0 7 bitpollSrc.rev}";
|
|
|
|
src = bitpollSrc;
|
|
|
|
nativeBuildInputs = with pkgs; [
|
|
gettext
|
|
pythonEnv
|
|
];
|
|
|
|
buildInputs = with pkgs; [
|
|
# LDAP support
|
|
openldap
|
|
cyrus_sasl
|
|
];
|
|
|
|
# Don't run tests during build
|
|
doCheck = false;
|
|
|
|
preBuild = ''
|
|
# Copy our settings file
|
|
cp ${settingsLocal} bitpoll/settings_local.py
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
mkdir -p $out/share/bitpoll
|
|
cp -r . $out/share/bitpoll/
|
|
|
|
# Create wrapper script
|
|
mkdir -p $out/bin
|
|
cat > $out/bin/bitpoll-manage << EOF
|
|
#!/bin/sh
|
|
cd $out/share/bitpoll
|
|
export PYTHONPATH=$out/share/bitpoll:\$PYTHONPATH
|
|
exec ${pythonEnv}/bin/python manage.py "\$@"
|
|
EOF
|
|
chmod +x $out/bin/bitpoll-manage
|
|
|
|
# Create server script
|
|
cat > $out/bin/bitpoll-server << EOF
|
|
#!/bin/sh
|
|
cd $out/share/bitpoll
|
|
export PYTHONPATH=$out/share/bitpoll:\$PYTHONPATH
|
|
exec ${pythonEnv}/bin/python manage.py runserver "\$@"
|
|
EOF
|
|
chmod +x $out/bin/bitpoll-server
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = with pkgs.lib; {
|
|
description = "A web application for scheduling meetings and general polling";
|
|
homepage = "https://github.com/fsinfuhh/Bitpoll";
|
|
license = licenses.gpl3Only;
|
|
maintainers = [ ];
|
|
platforms = platforms.unix;
|
|
};
|
|
};
|
|
|
|
in {
|
|
packages = {
|
|
default = bitpoll;
|
|
bitpoll = bitpoll;
|
|
};
|
|
|
|
apps = {
|
|
default = {
|
|
type = "app";
|
|
program = "${bitpoll}/bin/bitpoll-server";
|
|
meta = {
|
|
description = "Run Bitpoll development server";
|
|
};
|
|
};
|
|
bitpoll-manage = {
|
|
type = "app";
|
|
program = "${bitpoll}/bin/bitpoll-manage";
|
|
meta = {
|
|
description = "Run Bitpoll management commands";
|
|
};
|
|
};
|
|
};
|
|
|
|
devShells.default = pkgs.mkShell {
|
|
buildInputs = with pkgs; [
|
|
python3
|
|
python3Packages.pip
|
|
python3Packages.virtualenv
|
|
gettext
|
|
openldap
|
|
cyrus_sasl
|
|
];
|
|
};
|
|
}
|
|
) // {
|
|
nixosModules.default = self.nixosModules.bitpoll;
|
|
|
|
nixosModules.bitpoll = { config, lib, pkgs, ... }:
|
|
with lib;
|
|
let
|
|
cfg = config.services.bitpoll;
|
|
bitpollPackage = self.packages.${pkgs.system}.bitpoll;
|
|
in {
|
|
options.services.bitpoll = {
|
|
enable = mkEnableOption "Bitpoll service";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = bitpollPackage;
|
|
description = "The Bitpoll package to use";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 8000;
|
|
description = "Port to listen on";
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
description = "Host to bind to";
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/bitpoll";
|
|
description = "Directory to store Bitpoll data";
|
|
};
|
|
|
|
secretKeyFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "File containing the Django secret key";
|
|
};
|
|
|
|
allowedHosts = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "localhost" "127.0.0.1" ];
|
|
description = "List of allowed hosts";
|
|
};
|
|
|
|
extraSettings = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
description = "Extra settings to append to settings_local.py";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
users.users.bitpoll = {
|
|
isSystemUser = true;
|
|
group = "bitpoll";
|
|
home = cfg.dataDir;
|
|
createHome = true;
|
|
};
|
|
|
|
users.groups.bitpoll = {};
|
|
|
|
systemd.services.bitpoll = {
|
|
description = "Bitpoll web application";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
|
|
environment = {
|
|
PYTHONPATH = "${cfg.package}/share/bitpoll";
|
|
BITPOLL_DATA_DIR = cfg.dataDir;
|
|
} // optionalAttrs (cfg.secretKeyFile != null) {
|
|
BITPOLL_SECRET_KEY_FILE = cfg.secretKeyFile;
|
|
};
|
|
|
|
preStart = ''
|
|
# Ensure data directory exists and has correct permissions
|
|
mkdir -p ${cfg.dataDir}/{static,media}
|
|
chown -R bitpoll:bitpoll ${cfg.dataDir}
|
|
chmod 750 ${cfg.dataDir}
|
|
|
|
# Create runtime settings if secret key file is provided
|
|
if [ -n "''${BITPOLL_SECRET_KEY_FILE:-}" ] && [ -f "$BITPOLL_SECRET_KEY_FILE" ]; then
|
|
export BITPOLL_SECRET_KEY="$(cat "$BITPOLL_SECRET_KEY_FILE")"
|
|
fi
|
|
|
|
# Run database migrations
|
|
cd ${cfg.package}/share/bitpoll
|
|
${cfg.package}/bin/bitpoll-manage migrate --noinput
|
|
|
|
# Collect static files
|
|
${cfg.package}/bin/bitpoll-manage collectstatic --noinput --clear
|
|
|
|
# Compile messages
|
|
${cfg.package}/bin/bitpoll-manage compilemessages
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "exec";
|
|
User = "bitpoll";
|
|
Group = "bitpoll";
|
|
WorkingDirectory = "${cfg.package}/share/bitpoll";
|
|
ExecStart = "${cfg.package}/bin/bitpoll-server ${cfg.host}:${toString cfg.port}";
|
|
Restart = "always";
|
|
RestartSec = "10s";
|
|
|
|
# Security settings
|
|
NoNewPrivileges = true;
|
|
PrivateTmp = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
ReadWritePaths = [ cfg.dataDir ];
|
|
PrivateDevices = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
};
|
|
};
|
|
|
|
# Open firewall port if needed
|
|
# networking.firewall.allowedTCPPorts = [ cfg.port ];
|
|
};
|
|
};
|
|
|
|
};
|
|
}
|