From 8de81ea2ab55028653300293ffaa26de3544b294 Mon Sep 17 00:00:00 2001 From: shokinn Date: Fri, 4 Jul 2025 12:54:08 +0200 Subject: [PATCH] Add initial Bitpoll Nix flake and example configuration --- README.md | 186 +++++++++++++++++++++ example-configuration.nix | 53 ++++++ flake.lock | 61 +++++++ flake.nix | 331 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 631 insertions(+) create mode 100644 README.md create mode 100644 example-configuration.nix create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dec13b --- /dev/null +++ b/README.md @@ -0,0 +1,186 @@ +# Bitpoll Nix Flake + +This repository provides a Nix flake for [Bitpoll](https://github.com/fsinfuhh/Bitpoll), a web application for scheduling meetings and general polling. + +## Features + +- ✅ **Nix Flake**: Uses NixOS 25.05 with pinned dependencies +- ✅ **Bitpoll Package**: Builds Bitpoll from the current master commit (4a3e6a5) +- ✅ **NixOS Service**: Complete systemd service configuration +- ✅ **Data Storage**: All data stored in `/var/lib/bitpoll` as requested +- ✅ **Security**: Proper user isolation and security hardening +- ✅ **Cross-platform**: Works on Linux and macOS + +## Quick Start + +### 1. Using the Package Directly + +```bash +# Run Bitpoll development server +nix run git+https://git.s1q.dev/phg/bitpoll-nix + +# Run management commands +nix run git+https://git.s1q.dev/phg/bitpoll-nix#bitpoll-manage -- migrate +nix run git+https://git.s1q.dev/phg/bitpoll-nix#bitpoll-manage -- createsuperuser +``` + +### 2. Using as a NixOS Service + +Add this flake to your NixOS configuration: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + bitpoll.url = "git+https://git.s1q.dev/phg/bitpoll-nix"; + }; + + outputs = { self, nixpkgs, bitpoll }: { + nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + bitpoll.nixosModules.default + { + services.bitpoll = { + enable = true; + port = 8080; + host = "0.0.0.0"; + allowedHosts = [ + "bitpoll.example.com" + "localhost" + "127.0.0.1" + ]; + }; + + # Open firewall port + networking.firewall.allowedTCPPorts = [ 8080 ]; + } + ]; + }; + }; +} +``` + +Then rebuild your system: + +```bash +sudo nixos-rebuild switch --flake .#myhost +``` + +### 3. Development Environment + +```bash +# Enter development shell +nix develop git+https://git.s1q.dev/phg/bitpoll-nix + +# Or clone and develop locally +git clone https://git.s1q.dev/phg/bitpoll-nix +cd bitpoll-nix +nix develop +``` + +## Configuration Options + +The NixOS service provides the following configuration options: + +```nix +services.bitpoll = { + enable = true; # Enable the service + port = 8000; # Port to listen on (default: 8000) + host = "127.0.0.1"; # Host to bind to (default: 127.0.0.1) + dataDir = "/var/lib/bitpoll"; # Data directory (default: /var/lib/bitpoll) + secretKeyFile = "/path/to/key"; # Optional: File containing Django secret key + allowedHosts = [ "localhost" ]; # List of allowed hosts + extraSettings = ""; # Extra Django settings +}; +``` + +## Data Storage + +All Bitpoll data is stored in `/var/lib/bitpoll` as requested: + +- `/var/lib/bitpoll/db.sqlite3` - SQLite database +- `/var/lib/bitpoll/static/` - Static files (CSS, JS, images) +- `/var/lib/bitpoll/media/` - User uploaded files + +## Security + +The service runs with proper security hardening: + +- Dedicated `bitpoll` user and group +- Restricted filesystem access +- No new privileges +- Private temporary directories +- Protected system directories + +## Production Deployment + +For production use, consider: + +1. **Use a secret key file**: + ```nix + services.bitpoll.secretKeyFile = "/etc/bitpoll/secret-key"; + ``` + +2. **Configure allowed hosts properly**: + ```nix + services.bitpoll.allowedHosts = [ "bitpoll.yourdomain.com" ]; + ``` + +3. **Use a reverse proxy** (nginx, traefik, etc.) for HTTPS termination + +4. **Set up backups** for `/var/lib/bitpoll/` + +## Example Complete Configuration + +See `example-configuration.nix` for a complete NixOS configuration example. + +## Building and Testing + +```bash +# Check flake +nix flake check + +# Build package +nix build + +# Test the service +nix run .#bitpoll-manage -- check +``` + +## Dependencies + +This flake includes all necessary dependencies: + +- Django and related packages +- Calendar handling (caldav, icalendar) +- Database support (SQLite by default, PostgreSQL available) +- Security libraries (cryptography) +- Markup processing (markdown, bleach) +- LDAP support (optional) + +## Version Information + +- **NixOS Version**: 25.05 +- **Bitpoll Version**: master (commit 4a3e6a5) +- **Python Version**: 3.x (from nixpkgs) +- **Django Version**: Latest from nixpkgs + +## Contributing + +1. Fork this repository +2. Make your changes +3. Test with `nix flake check` +4. Submit a pull request + +## License + +This flake is provided under the same license as Bitpoll (GPL-3.0). + +## Support + +For issues with: + +- **This flake**: Open an issue at [this repository](https://git.s1q.dev/phg/bitpoll-nix) +- **Bitpoll itself**: See the [upstream repository](https://github.com/fsinfuhh/Bitpoll) +- **NixOS**: See the [NixOS manual](https://nixos.org/manual/nixos/stable/) diff --git a/example-configuration.nix b/example-configuration.nix new file mode 100644 index 0000000..4fcfcb2 --- /dev/null +++ b/example-configuration.nix @@ -0,0 +1,53 @@ +# Example NixOS configuration using the Bitpoll flake +{ config, pkgs, ... }: + +{ + imports = [ + # Include your hardware configuration + ./hardware-configuration.nix + ]; + + # Enable flakes + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + # Basic system configuration + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + networking.hostName = "bitpoll-server"; + networking.firewall.allowedTCPPorts = [ 8080 22 ]; + + # Enable SSH for remote management + services.openssh.enable = true; + + # Bitpoll service configuration + services.bitpoll = { + enable = true; + port = 8080; + host = "0.0.0.0"; # Listen on all interfaces + allowedHosts = [ + "bitpoll.example.com" + "localhost" + "127.0.0.1" + ]; + + # Optional: Use a secret key file for better security + # secretKeyFile = "/etc/bitpoll/secret-key"; + }; + + # Create a user for administration + users.users.admin = { + isNormalUser = true; + extraGroups = [ "wheel" ]; # Enable 'sudo' for the admin user + openssh.authorizedKeys.keys = [ + # Add your SSH public key here + # "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC..." + ]; + }; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It's perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + system.stateVersion = "25.05"; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..41bcb32 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1751211869, + "narHash": "sha256-1Cu92i1KSPbhPCKxoiVG5qnoRiKTgR5CcGSRyLpOd7Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b43c397f6c213918d6cfe6e3550abfe79b5d1c51", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..03f4512 --- /dev/null +++ b/flake.nix @@ -0,0 +1,331 @@ +{ + description = "Bitpoll - A web application for scheduling meetings and general polling"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + # Pin to current master commit + bitpollSrc = pkgs.fetchFromGitHub { + owner = "fsinfuhh"; + repo = "Bitpoll"; + rev = "4a3e6a5e3500308a428a6c7644f50d423adca6fc"; + sha256 = "sha256-R4OwQdSJu9+EAlkhYOEe2ZOrS9oOA1ifg/iY6uzYSpE="; + }; + + # Create settings_local.py for production + settingsLocal = pkgs.writeText "settings_local.py" '' + import os + import secrets + + # Generate secret key if not provided via environment + SECRET_KEY = os.environ.get('BITPOLL_SECRET_KEY', secrets.token_urlsafe(50)) + + # Generate field encryption key if not provided via environment + FIELD_ENCRYPTION_KEY = os.environ.get('BITPOLL_FIELD_ENCRYPTION_KEY', secrets.token_urlsafe(32) + "=") + + DEBUG = False + + ALLOWED_HOSTS = ['*'] # Configure appropriately for production + + # SQLite database configuration + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '/var/lib/bitpoll/db.sqlite3', + } + } + + # Static files + STATIC_ROOT = '/var/lib/bitpoll/static' + + # Media files + MEDIA_ROOT = '/var/lib/bitpoll/media' + + # Locale + LANGUAGE_CODE = 'en-us' + TIME_ZONE = 'UTC' + + # Site configuration + SITE_NAME = 'Bitpoll' + + # Additional apps + INSTALLED_APPS_LOCAL = [] + MIDDLEWARE_LOCAL = [] + PIPELINE_LOCAL = {} + + # Registration enabled by default + REGISTER_ENABLED = True + GROUP_MANAGEMENT = True + ''; + + bitpoll = pkgs.python3Packages.buildPythonApplication rec { + pname = "bitpoll"; + version = "master-${builtins.substring 0 7 bitpollSrc.rev}"; + + src = bitpollSrc; + + format = "other"; + + propagatedBuildInputs = with pkgs.python3Packages; [ + # Core Django dependencies + django + + # Calendar and date handling + caldav + icalendar + python-dateutil + pytz + vobject + + # Crypto and security + cryptography + + # Web and HTTP + requests + + # Markup and styling + markdown + bleach + + # Data handling + pydantic + + # Database + psycopg2 + + # Utilities + six + lxml + + # Additional dependencies + setuptools + wheel + ] ++ [ + # System dependencies + pkgs.gettext + ]; + + nativeBuildInputs = with pkgs; [ + gettext + ]; + + buildInputs = with pkgs; [ + # LDAP support + openldap + cyrus_sasl + ]; + + # Don't run tests during build + doCheck = false; + + preBuild = '' + # Copy our settings file + cp ${settingsLocal} bitpoll/settings_local.py + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/share/bitpoll + cp -r . $out/share/bitpoll/ + + # Create wrapper script + mkdir -p $out/bin + cat > $out/bin/bitpoll-manage << EOF + #!/bin/sh + cd $out/share/bitpoll + export PYTHONPATH=$out/share/bitpoll:\$PYTHONPATH + exec ${pkgs.python3}/bin/python manage.py "\$@" + EOF + chmod +x $out/bin/bitpoll-manage + + # Create server script + cat > $out/bin/bitpoll-server << EOF + #!/bin/sh + cd $out/share/bitpoll + export PYTHONPATH=$out/share/bitpoll:\$PYTHONPATH + exec ${pkgs.python3}/bin/python manage.py runserver "\$@" + EOF + chmod +x $out/bin/bitpoll-server + + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "A web application for scheduling meetings and general polling"; + homepage = "https://github.com/fsinfuhh/Bitpoll"; + license = licenses.gpl3Only; + maintainers = [ ]; + platforms = platforms.unix; + }; + }; + + in { + packages = { + default = bitpoll; + bitpoll = bitpoll; + }; + + apps = { + default = { + type = "app"; + program = "${bitpoll}/bin/bitpoll-server"; + meta = { + description = "Run Bitpoll development server"; + }; + }; + bitpoll-manage = { + type = "app"; + program = "${bitpoll}/bin/bitpoll-manage"; + meta = { + description = "Run Bitpoll management commands"; + }; + }; + }; + + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + python3Packages.pip + python3Packages.virtualenv + gettext + openldap + cyrus_sasl + ]; + }; + } + ) // { + nixosModules.default = self.nixosModules.bitpoll; + + nixosModules.bitpoll = { config, lib, pkgs, ... }: + with lib; + let + cfg = config.services.bitpoll; + bitpollPackage = self.packages.${pkgs.system}.bitpoll; + in { + options.services.bitpoll = { + enable = mkEnableOption "Bitpoll service"; + + package = mkOption { + type = types.package; + default = bitpollPackage; + description = "The Bitpoll package to use"; + }; + + port = mkOption { + type = types.port; + default = 8000; + description = "Port to listen on"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind to"; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/bitpoll"; + description = "Directory to store Bitpoll data"; + }; + + secretKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "File containing the Django secret key"; + }; + + allowedHosts = mkOption { + type = types.listOf types.str; + default = [ "localhost" "127.0.0.1" ]; + description = "List of allowed hosts"; + }; + + extraSettings = mkOption { + type = types.lines; + default = ""; + description = "Extra settings to append to settings_local.py"; + }; + }; + + config = mkIf cfg.enable { + users.users.bitpoll = { + isSystemUser = true; + group = "bitpoll"; + home = cfg.dataDir; + createHome = true; + }; + + users.groups.bitpoll = {}; + + systemd.services.bitpoll = { + description = "Bitpoll web application"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + environment = { + PYTHONPATH = "${cfg.package}/share/bitpoll"; + BITPOLL_DATA_DIR = cfg.dataDir; + } // optionalAttrs (cfg.secretKeyFile != null) { + BITPOLL_SECRET_KEY_FILE = cfg.secretKeyFile; + }; + + preStart = '' + # Ensure data directory exists and has correct permissions + mkdir -p ${cfg.dataDir}/{static,media} + chown -R bitpoll:bitpoll ${cfg.dataDir} + chmod 750 ${cfg.dataDir} + + # Create runtime settings if secret key file is provided + if [ -n "''${BITPOLL_SECRET_KEY_FILE:-}" ] && [ -f "$BITPOLL_SECRET_KEY_FILE" ]; then + export BITPOLL_SECRET_KEY="$(cat "$BITPOLL_SECRET_KEY_FILE")" + fi + + # Run database migrations + cd ${cfg.package}/share/bitpoll + ${cfg.package}/bin/bitpoll-manage migrate --noinput + + # Collect static files + ${cfg.package}/bin/bitpoll-manage collectstatic --noinput --clear + + # Compile messages + ${cfg.package}/bin/bitpoll-manage compilemessages + ''; + + serviceConfig = { + Type = "exec"; + User = "bitpoll"; + Group = "bitpoll"; + WorkingDirectory = "${cfg.package}/share/bitpoll"; + ExecStart = "${cfg.package}/bin/bitpoll-server ${cfg.host}:${toString cfg.port}"; + Restart = "always"; + RestartSec = "10s"; + + # Security settings + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [ cfg.dataDir ]; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + }; + }; + + # Open firewall port if needed + # networking.firewall.allowedTCPPorts = [ cfg.port ]; + }; + }; + + }; +}