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