bitpoll-nix/flake.nix

511 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
];
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
];
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
# 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 ];
};
};
};
}