full reset
This commit is contained in:
parent
0b3e086c03
commit
1b58a0ded8
5 changed files with 0 additions and 898 deletions
231
README.md
231
README.md
|
@ -1,231 +0,0 @@
|
||||||
# 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).
|
|
|
@ -1,160 +0,0 @@
|
||||||
# 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
42
flake.nix
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
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
349
module.nix
|
@ -1,349 +0,0 @@
|
||||||
{ 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
116
package.nix
|
@ -1,116 +0,0 @@
|
||||||
{ 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