bitpoll-nix/flake.nix

331 lines
9.8 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 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
'';
bitpoll = pkgs.python3Packages.buildPythonApplication rec {
pname = "bitpoll";
version = "master-${builtins.substring 0 7 bitpollSrc.rev}";
src = bitpollSrc;
format = "other";
propagatedBuildInputs = with pkgs.python3Packages; [
# Core Django dependencies
django
# Calendar and date handling
caldav
icalendar
python-dateutil
pytz
vobject
# Crypto and security
cryptography
# Web and HTTP
requests
# Markup and styling
markdown
bleach
# Data handling
pydantic
# Database
psycopg2
# Utilities
six
lxml
# Additional dependencies
setuptools
wheel
] ++ [
# System dependencies
pkgs.gettext
];
nativeBuildInputs = with pkgs; [
gettext
];
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 ${pkgs.python3}/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 ${pkgs.python3}/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 ];
};
};
};
}