Initial Bitpoll Nix package and service
This commit is contained in:
commit
0b3e086c03
5 changed files with 898 additions and 0 deletions
231
README.md
Normal file
231
README.md
Normal file
|
@ -0,0 +1,231 @@
|
|||
# Bitpoll Nix Package
|
||||
|
||||
This repository contains a Nix flake for packaging [Bitpoll](https://github.com/fsinfuhh/Bitpoll), a web application for scheduling meetings and general polling.
|
||||
|
||||
## Features
|
||||
|
||||
- **Complete Nix Package**: Bitpoll packaged as a Nix derivation with all Python dependencies
|
||||
- **NixOS Service Module**: Ready-to-use systemd service with PostgreSQL integration
|
||||
- **Security Hardened**: Runs with minimal privileges and security restrictions
|
||||
- **Configurable**: All major settings exposed as NixOS options
|
||||
- **Production Ready**: Uses uWSGI with proper process management
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Add to your NixOS configuration
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
bitpoll.url = "github:your-username/bitpoll-nix";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, bitpoll }: {
|
||||
nixosConfigurations.your-host = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
bitpoll.nixosModules.default
|
||||
{
|
||||
services.bitpoll = {
|
||||
enable = true;
|
||||
secretKey = "your-secret-key-here";
|
||||
encryptionKey = "your-encryption-key-here";
|
||||
allowedHosts = [ "your-domain.com" ];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Generate required keys
|
||||
|
||||
```bash
|
||||
# Generate Django secret key
|
||||
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
|
||||
|
||||
# Generate field encryption key (32 bytes, base64 encoded)
|
||||
python -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"
|
||||
```
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch --flake .#your-host
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```nix
|
||||
services.bitpoll = {
|
||||
enable = true;
|
||||
|
||||
# Required security keys
|
||||
secretKey = "your-django-secret-key";
|
||||
encryptionKey = "your-field-encryption-key";
|
||||
|
||||
# Network settings
|
||||
listenAddress = "127.0.0.1";
|
||||
port = 3008; # uWSGI socket
|
||||
httpPort = 3009; # HTTP port (null to disable)
|
||||
|
||||
# Django settings
|
||||
debug = false;
|
||||
allowedHosts = [ "your-domain.com" ];
|
||||
language = "en-us";
|
||||
timezone = "Europe/Berlin";
|
||||
};
|
||||
```
|
||||
|
||||
### Database Configuration
|
||||
|
||||
```nix
|
||||
services.bitpoll = {
|
||||
# PostgreSQL is enabled by default
|
||||
enablePostgreSQL = true;
|
||||
|
||||
database = {
|
||||
name = "bitpoll";
|
||||
user = "bitpoll";
|
||||
password = ""; # Leave empty for peer authentication
|
||||
host = "localhost";
|
||||
port = 5432;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
```nix
|
||||
services.bitpoll = {
|
||||
# uWSGI process management
|
||||
processes = 8; # Max processes
|
||||
threads = 4; # Threads per process
|
||||
cheaperProcesses = 2; # Min processes
|
||||
|
||||
# Additional uWSGI configuration
|
||||
extraUwsgiConfig = ''
|
||||
max-requests = 1000
|
||||
reload-on-rss = 512
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
```nix
|
||||
services.bitpoll = {
|
||||
# Additional Django settings
|
||||
extraSettings = {
|
||||
PIPELINE_LOCAL = {
|
||||
JS_COMPRESSOR = "pipeline.compressors.uglifyjs.UglifyJSCompressor";
|
||||
CSS_COMPRESSOR = "pipeline.compressors.cssmin.CSSMinCompressor";
|
||||
};
|
||||
CSP_ADDITIONAL_SCRIPT_SRC = [ "your-analytics-domain.com" ];
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Reverse Proxy Setup
|
||||
|
||||
### Nginx Example
|
||||
|
||||
```nix
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."your-domain.com" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:3009";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
'';
|
||||
};
|
||||
"/static/" = {
|
||||
alias = "/var/lib/bitpoll/static/";
|
||||
extraConfig = ''
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Data Storage
|
||||
|
||||
All persistent data is stored in `/var/lib/bitpoll/`:
|
||||
- `media/` - User uploaded files
|
||||
- `static/` - Collected static files
|
||||
- Database data (if using PostgreSQL, stored in PostgreSQL data directory)
|
||||
|
||||
## Security
|
||||
|
||||
The service runs with extensive security hardening:
|
||||
- Dedicated user account (`bitpoll`)
|
||||
- Restricted filesystem access
|
||||
- No network access except required ports
|
||||
- Memory execution protection
|
||||
- System call filtering
|
||||
|
||||
## Development
|
||||
|
||||
### Building the package
|
||||
|
||||
```bash
|
||||
nix build .#bitpoll
|
||||
```
|
||||
|
||||
### Development shell
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
### Testing the module
|
||||
|
||||
```bash
|
||||
nixos-rebuild build-vm --flake .#test-vm
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check service status
|
||||
|
||||
```bash
|
||||
systemctl status bitpoll
|
||||
journalctl -u bitpoll -f
|
||||
```
|
||||
|
||||
### Database issues
|
||||
|
||||
```bash
|
||||
# Check PostgreSQL status
|
||||
systemctl status postgresql
|
||||
|
||||
# Connect to database
|
||||
sudo -u postgres psql bitpoll
|
||||
```
|
||||
|
||||
### Permission issues
|
||||
|
||||
```bash
|
||||
# Fix data directory permissions
|
||||
sudo chown -R bitpoll:bitpoll /var/lib/bitpoll
|
||||
sudo chmod -R u=rwX,g=rX,o= /var/lib/bitpoll
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This packaging is released under the same license as Bitpoll (GPL-3.0).
|
160
example-configuration.nix
Normal file
160
example-configuration.nix
Normal file
|
@ -0,0 +1,160 @@
|
|||
# Example NixOS configuration for Bitpoll
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
# Import the Bitpoll module
|
||||
./module.nix
|
||||
];
|
||||
|
||||
# Enable Bitpoll service
|
||||
services.bitpoll = {
|
||||
enable = true;
|
||||
|
||||
# Required security keys (generate these!)
|
||||
secretKey = "CHANGE-ME-django-secret-key-here";
|
||||
encryptionKey = "CHANGE-ME-field-encryption-key-here";
|
||||
|
||||
# Network configuration
|
||||
listenAddress = "127.0.0.1";
|
||||
port = 3008; # uWSGI socket port
|
||||
httpPort = 3009; # HTTP port for direct access
|
||||
|
||||
# Django settings
|
||||
debug = false;
|
||||
allowedHosts = [ "localhost" "bitpoll.example.com" ];
|
||||
language = "en-us";
|
||||
timezone = "Europe/Berlin";
|
||||
|
||||
# Database configuration (PostgreSQL is auto-configured)
|
||||
database = {
|
||||
name = "bitpoll";
|
||||
user = "bitpoll";
|
||||
password = ""; # Empty for peer authentication
|
||||
host = "localhost";
|
||||
port = 5432;
|
||||
};
|
||||
|
||||
# Performance settings
|
||||
processes = 4; # Adjust based on your server
|
||||
threads = 2;
|
||||
cheaperProcesses = 1;
|
||||
|
||||
# Additional Django settings
|
||||
extraSettings = {
|
||||
# Pipeline configuration for asset compression
|
||||
PIPELINE_LOCAL = {
|
||||
JS_COMPRESSOR = "pipeline.compressors.uglifyjs.UglifyJSCompressor";
|
||||
CSS_COMPRESSOR = "pipeline.compressors.cssmin.CSSMinCompressor";
|
||||
};
|
||||
|
||||
# Content Security Policy
|
||||
CSP_ADDITIONAL_SCRIPT_SRC = [ ];
|
||||
|
||||
# Additional installed apps (if needed)
|
||||
INSTALLED_APPS_LOCAL = [ ];
|
||||
};
|
||||
|
||||
# Additional uWSGI configuration
|
||||
extraUwsgiConfig = ''
|
||||
# Reload workers after 1000 requests to prevent memory leaks
|
||||
max-requests = 1000
|
||||
|
||||
# Reload if memory usage exceeds 512MB
|
||||
reload-on-rss = 512
|
||||
|
||||
# Enable stats server (optional, for monitoring)
|
||||
# stats = 127.0.0.1:9191
|
||||
'';
|
||||
};
|
||||
|
||||
# Nginx reverse proxy configuration
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts."bitpoll.example.com" = {
|
||||
# Enable HTTPS with Let's Encrypt
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations = {
|
||||
# Proxy all requests to Bitpoll
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:3009";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Increase timeouts for long-running requests
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
'';
|
||||
};
|
||||
|
||||
# Serve static files directly from Nginx for better performance
|
||||
"/static/" = {
|
||||
alias = "/var/lib/bitpoll/static/";
|
||||
extraConfig = ''
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
gzip on;
|
||||
gzip_types text/css application/javascript application/json;
|
||||
'';
|
||||
};
|
||||
|
||||
# Serve media files (user uploads)
|
||||
"/media/" = {
|
||||
alias = "/var/lib/bitpoll/media/";
|
||||
extraConfig = ''
|
||||
expires 1d;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# ACME configuration for Let's Encrypt
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "admin@example.com";
|
||||
};
|
||||
|
||||
# Firewall configuration
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 80 443 ];
|
||||
};
|
||||
|
||||
# Optional: Backup configuration
|
||||
services.restic.backups.bitpoll = {
|
||||
initialize = true;
|
||||
repository = "/backup/bitpoll";
|
||||
passwordFile = "/etc/nixos/secrets/restic-password";
|
||||
paths = [ "/var/lib/bitpoll" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "daily";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Optional: Log rotation
|
||||
services.logrotate = {
|
||||
enable = true;
|
||||
settings = {
|
||||
"/var/log/bitpoll/*.log" = {
|
||||
frequency = "daily";
|
||||
rotate = 30;
|
||||
compress = true;
|
||||
delaycompress = true;
|
||||
missingok = true;
|
||||
notifempty = true;
|
||||
create = "644 bitpoll bitpoll";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
42
flake.nix
Normal file
42
flake.nix
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
description = "Bitpoll - A web application for scheduling meetings and general polling";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
bitpoll = pkgs.callPackage ./package.nix { };
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = bitpoll;
|
||||
bitpoll = bitpoll;
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
python3
|
||||
python3Packages.pip
|
||||
python3Packages.virtualenv
|
||||
postgresql
|
||||
uwsgi
|
||||
];
|
||||
shellHook = ''
|
||||
echo "Bitpoll development environment"
|
||||
echo "Run 'nix build' to build the package"
|
||||
echo "Run 'nixos-rebuild switch --flake .#' to deploy the service"
|
||||
'';
|
||||
};
|
||||
}
|
||||
) // {
|
||||
nixosModules = {
|
||||
default = import ./module.nix;
|
||||
bitpoll = import ./module.nix;
|
||||
};
|
||||
};
|
||||
}
|
349
module.nix
Normal file
349
module.nix
Normal file
|
@ -0,0 +1,349 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.bitpoll;
|
||||
|
||||
# Persistent data directory
|
||||
dataDir = "/var/lib/bitpoll";
|
||||
|
||||
# Generate Django settings file
|
||||
settingsFile = pkgs.writeText "bitpoll-settings.py" ''
|
||||
# Bitpoll NixOS Configuration
|
||||
import os
|
||||
from bitpoll.settings.production import *
|
||||
|
||||
# Security settings
|
||||
SECRET_KEY = '${cfg.secretKey}'
|
||||
FIELD_ENCRYPTION_KEY = '${cfg.encryptionKey}'
|
||||
DEBUG = ${boolToString cfg.debug}
|
||||
ALLOWED_HOSTS = ${builtins.toJSON cfg.allowedHosts}
|
||||
|
||||
# Localization
|
||||
LANGUAGE_CODE = '${cfg.language}'
|
||||
TIME_ZONE = '${cfg.timezone}'
|
||||
|
||||
# Database configuration
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': '${cfg.database.name}',
|
||||
'USER': '${cfg.database.user}',
|
||||
'PASSWORD': '${cfg.database.password}',
|
||||
'HOST': '${cfg.database.host}',
|
||||
'PORT': '${toString cfg.database.port}',
|
||||
}
|
||||
}
|
||||
|
||||
# File storage paths
|
||||
MEDIA_ROOT = '${dataDir}/media'
|
||||
STATIC_ROOT = '${dataDir}/static'
|
||||
|
||||
# Additional settings
|
||||
${concatStringsSep "\n" (mapAttrsToList (k: v: "${k} = ${builtins.toJSON v}") cfg.extraSettings)}
|
||||
'';
|
||||
|
||||
# Generate uWSGI configuration
|
||||
uwsgiConfig = pkgs.writeText "uwsgi.ini" ''
|
||||
[uwsgi]
|
||||
procname-master = uwsgi bitpoll
|
||||
master = true
|
||||
socket = ${cfg.listenAddress}:${toString cfg.port}
|
||||
${optionalString (cfg.httpPort != null) "http-socket = ${cfg.listenAddress}:${toString cfg.httpPort}"}
|
||||
|
||||
plugins = python3
|
||||
|
||||
chdir = ${dataDir}
|
||||
virtualenv = ${cfg.package}
|
||||
pythonpath = ${cfg.package}/lib/python*/site-packages
|
||||
|
||||
module = bitpoll.wsgi:application
|
||||
env = DJANGO_SETTINGS_MODULE=bitpoll.settings
|
||||
env = BITPOLL_SETTINGS_FILE=${settingsFile}
|
||||
env = LANG=C.UTF-8
|
||||
env = LC_ALL=C.UTF-8
|
||||
|
||||
# Process management
|
||||
uid = ${cfg.user}
|
||||
gid = ${cfg.group}
|
||||
umask = 027
|
||||
|
||||
processes = ${toString cfg.processes}
|
||||
threads = ${toString cfg.threads}
|
||||
cheaper = ${toString cfg.cheaperProcesses}
|
||||
|
||||
# Logging
|
||||
disable-logging = ${boolToString cfg.disableLogging}
|
||||
|
||||
# Static files
|
||||
static-map = /static=${dataDir}/static
|
||||
|
||||
# Additional uWSGI options
|
||||
${cfg.extraUwsgiConfig}
|
||||
'';
|
||||
|
||||
in {
|
||||
options.services.bitpoll = {
|
||||
enable = mkEnableOption "Bitpoll polling application";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.bitpoll or (pkgs.callPackage ./package.nix { });
|
||||
description = "The Bitpoll package to use";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bitpoll";
|
||||
description = "User account under which Bitpoll runs";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "bitpoll";
|
||||
description = "Group under which Bitpoll runs";
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Address to listen on";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3008;
|
||||
description = "Port for uWSGI socket";
|
||||
};
|
||||
|
||||
httpPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = 3009;
|
||||
description = "Port for HTTP socket (null to disable)";
|
||||
};
|
||||
|
||||
# Django settings
|
||||
secretKey = mkOption {
|
||||
type = types.str;
|
||||
description = "Django secret key";
|
||||
example = "your-secret-key-here";
|
||||
};
|
||||
|
||||
encryptionKey = mkOption {
|
||||
type = types.str;
|
||||
description = "Field encryption key";
|
||||
example = "BnEAJ5eEXb4HfYbaCPuW5RKQSoO02Uhz1RH93eQz0GM=";
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable Django debug mode";
|
||||
};
|
||||
|
||||
allowedHosts = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "*" ];
|
||||
description = "List of allowed hosts";
|
||||
};
|
||||
|
||||
language = mkOption {
|
||||
type = types.str;
|
||||
default = "en-us";
|
||||
description = "Language code";
|
||||
};
|
||||
|
||||
timezone = mkOption {
|
||||
type = types.str;
|
||||
default = "Europe/Berlin";
|
||||
description = "Time zone";
|
||||
};
|
||||
|
||||
# Database settings
|
||||
database = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "bitpoll";
|
||||
description = "Database name";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bitpoll";
|
||||
description = "Database user";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Database password";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Database host";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 5432;
|
||||
description = "Database port";
|
||||
};
|
||||
};
|
||||
|
||||
# uWSGI settings
|
||||
processes = mkOption {
|
||||
type = types.int;
|
||||
default = 8;
|
||||
description = "Number of uWSGI processes";
|
||||
};
|
||||
|
||||
threads = mkOption {
|
||||
type = types.int;
|
||||
default = 4;
|
||||
description = "Number of threads per process";
|
||||
};
|
||||
|
||||
cheaperProcesses = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = "Minimum number of processes";
|
||||
};
|
||||
|
||||
disableLogging = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Disable uWSGI request logging";
|
||||
};
|
||||
|
||||
extraUwsgiConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Additional uWSGI configuration";
|
||||
};
|
||||
|
||||
extraSettings = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = { };
|
||||
description = "Additional Django settings";
|
||||
};
|
||||
|
||||
# PostgreSQL integration
|
||||
enablePostgreSQL = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable and configure PostgreSQL";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# PostgreSQL setup
|
||||
services.postgresql = mkIf cfg.enablePostgreSQL {
|
||||
enable = true;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [{
|
||||
name = cfg.database.user;
|
||||
ensureDBOwnership = true;
|
||||
}];
|
||||
};
|
||||
|
||||
# Create user and group
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = dataDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = { };
|
||||
|
||||
# Create data directories
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${dataDir} 0750 ${cfg.user} ${cfg.group} -"
|
||||
"d ${dataDir}/media 0750 ${cfg.user} ${cfg.group} -"
|
||||
"d ${dataDir}/static 0750 ${cfg.user} ${cfg.group} -"
|
||||
];
|
||||
|
||||
# Bitpoll service
|
||||
systemd.services.bitpoll = {
|
||||
description = "Bitpoll polling application";
|
||||
after = [ "network.target" ] ++ optional cfg.enablePostgreSQL "postgresql.service";
|
||||
wants = optional cfg.enablePostgreSQL "postgresql.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
DJANGO_SETTINGS_MODULE = "bitpoll.settings";
|
||||
BITPOLL_SETTINGS_FILE = "${settingsFile}";
|
||||
PYTHONPATH = "${cfg.package}/lib/python*/site-packages";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = dataDir;
|
||||
ExecStart = "${pkgs.uwsgi}/bin/uwsgi --ini ${uwsgiConfig}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
KillMode = "mixed";
|
||||
KillSignal = "SIGINT";
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ReadWritePaths = [ dataDir ];
|
||||
NoNewPrivileges = true;
|
||||
|
||||
# Security hardening
|
||||
CapabilityBoundingSet = "";
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged @resources" ];
|
||||
UMask = "0027";
|
||||
};
|
||||
|
||||
# Pre-start script for migrations and static files
|
||||
preStart = ''
|
||||
# Wait for database to be ready
|
||||
${optionalString cfg.enablePostgreSQL ''
|
||||
while ! ${pkgs.postgresql}/bin/pg_isready -h ${cfg.database.host} -p ${toString cfg.database.port} -U ${cfg.database.user} -d ${cfg.database.name}; do
|
||||
echo "Waiting for PostgreSQL..."
|
||||
sleep 2
|
||||
done
|
||||
''}
|
||||
|
||||
# Run migrations
|
||||
cd ${dataDir}
|
||||
export DJANGO_SETTINGS_MODULE=bitpoll.settings
|
||||
export BITPOLL_SETTINGS_FILE=${settingsFile}
|
||||
export PYTHONPATH=${cfg.package}/lib/python*/site-packages
|
||||
|
||||
${cfg.package}/bin/python ${cfg.package}/manage.py migrate --noinput
|
||||
${cfg.package}/bin/python ${cfg.package}/manage.py collectstatic --noinput --clear
|
||||
|
||||
# Set proper permissions
|
||||
chown -R ${cfg.user}:${cfg.group} ${dataDir}
|
||||
chmod -R u=rwX,g=rX,o= ${dataDir}
|
||||
'';
|
||||
};
|
||||
|
||||
# Open firewall ports if needed
|
||||
networking.firewall.allowedTCPPorts = mkIf (cfg.httpPort != null) [ cfg.httpPort ];
|
||||
};
|
||||
}
|
116
package.nix
Normal file
116
package.nix
Normal file
|
@ -0,0 +1,116 @@
|
|||
{ lib
|
||||
, buildPythonApplication
|
||||
, fetchFromGitHub
|
||||
, python3Packages
|
||||
, gettext
|
||||
, libsass
|
||||
, pkg-config
|
||||
, postgresql
|
||||
, uwsgi
|
||||
}:
|
||||
|
||||
buildPythonApplication rec {
|
||||
pname = "bitpoll";
|
||||
version = "unstable-2024-11-23";
|
||||
format = "setuptools";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "fsinfuhh";
|
||||
repo = "Bitpoll";
|
||||
rev = "4a3e6a5e3500308a428a6c7644f50d423adca6fc";
|
||||
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
gettext
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
libsass
|
||||
postgresql
|
||||
];
|
||||
|
||||
propagatedBuildInputs = with python3Packages; [
|
||||
# Core Django dependencies
|
||||
django
|
||||
django-auth-ldap
|
||||
django-encrypted-model-fields
|
||||
django-friendly-tag-loader
|
||||
django-markdownify
|
||||
django-pipeline
|
||||
django-token-bucket
|
||||
django-widget-tweaks
|
||||
|
||||
# Database
|
||||
psycopg2
|
||||
|
||||
# Calendar and date handling
|
||||
caldav
|
||||
icalendar
|
||||
python-dateutil
|
||||
pytz
|
||||
recurring-ical-events
|
||||
x-wr-timezone
|
||||
|
||||
# Authentication and security
|
||||
simple-openid-connect
|
||||
cryptography
|
||||
cryptojwt
|
||||
|
||||
# Utilities
|
||||
bleach
|
||||
furl
|
||||
lxml
|
||||
markdown
|
||||
requests
|
||||
sentry-sdk
|
||||
|
||||
# SASS compilation
|
||||
libsasscompiler
|
||||
|
||||
# Other dependencies
|
||||
pydantic
|
||||
six
|
||||
vobject
|
||||
];
|
||||
|
||||
# Create a setup.py since the project doesn't have one
|
||||
preBuild = ''
|
||||
cat > setup.py << EOF
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='bitpoll',
|
||||
version='${version}',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[],
|
||||
scripts=['manage.py'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'bitpoll-manage=manage:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
EOF
|
||||
'';
|
||||
|
||||
# Compile messages and collect static files
|
||||
postBuild = ''
|
||||
export DJANGO_SETTINGS_MODULE=bitpoll.settings.production
|
||||
python manage.py compilemessages
|
||||
python manage.py collectstatic --noinput --clear
|
||||
'';
|
||||
|
||||
# Skip tests for now as they require additional setup
|
||||
doCheck = false;
|
||||
|
||||
meta = with lib; {
|
||||
description = "A web application for scheduling meetings and general polling";
|
||||
homepage = "https://github.com/fsinfuhh/Bitpoll";
|
||||
license = licenses.gpl3Only;
|
||||
maintainers = [ ];
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue