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