all repos — mycal @ 7705a1d7144eb3a692e774ff8d506a680874617a

private calendar anonymiser

initial commit

Alan Pearce
commit

7705a1d7144eb3a692e774ff8d506a680874617a

1 file changed, 231 insertions(+), 0 deletions(-)

changed files
A flake.nix
@@ -0,0 +1,231 @@
+{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + flake-utils.url = "github:numtide/flake-utils"; + + pyproject-nix = { + url = "github:pyproject-nix/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + uv2nix = { + url = "github:pyproject-nix/uv2nix"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + pyproject-build-systems = { + url = "github:pyproject-nix/build-system-pkgs"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.uv2nix.follows = "uv2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { self + , nixpkgs + , flake-utils + , uv2nix + , pyproject-nix + , pyproject-build-systems + , ... + }: flake-utils.lib.eachDefaultSystem + (system: + let + inherit (nixpkgs) lib; + + # Load a uv workspace from a workspace root. + # Uv2nix treats all uv projects as workspace projects. + workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; + + # Create package overlay from workspace. + overlay = workspace.mkPyprojectOverlay { + # Prefer prebuilt binary wheels as a package source. + # Sdists are less likely to "just work" because of the metadata missing from uv.lock. + # Binary wheels are more likely to, but may still require overrides for library dependencies. + sourcePreference = "wheel"; # or sourcePreference = "sdist"; + # Optionally customise PEP 508 environment + # environ = { + # platform_release = "5.10.65"; + # }; + }; + + pkgs = import nixpkgs { inherit system; }; + python = pkgs.python3; + + # Extend generated overlay with build fixups + # + # Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds. + # This is an additional overlay implementing build fixups. + # See: + # - https://pyproject-nix.github.io/uv2nix/FAQ.html + pyprojectOverrides = _final: _prev: { + # Implement build fixups here. + # Note that uv2nix is _not_ using Nixpkgs buildPythonPackage. + # It's using https://pyproject-nix.github.io/pyproject.nix/build.html + }; + + # Construct package set + pythonSet = + # Use base package set from pyproject.nix builders + (pkgs.callPackage pyproject-nix.build.packages { + inherit python; + }).overrideScope + ( + lib.composeManyExtensions [ + pyproject-build-systems.overlays.default + overlay + pyprojectOverrides + ] + ); + in + { + packages = { + default = pythonSet.mkVirtualEnv "mycal" workspace.deps.default; + inherit python pythonSet; + }; + + devShells = { + default = pkgs.mkShell { + packages = with pkgs; [ + uv + ]; + inputsFrom = [ pkgs.mycal ]; + env = + { + # Prevent uv from managing Python downloads + UV_PYTHON_DOWNLOADS = "never"; + # Force uv to use nixpkgs Python interpreter + UV_PYTHON = python.interpreter; + } + // lib.optionalAttrs pkgs.stdenv.isLinux { + # Python libraries often load native shared objects using dlopen(3). + # Setting LD_LIBRARY_PATH makes the dynamic library loader aware of libraries without using RPATH for lookup. + # LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1; + }; + shellHook = '' + unset PYTHONPATH + ''; + }; + }; + }) // { + nixosModules = { + mycal = + { config, lib, pkgs, ... }: + let + cfg = config.services.mycal; + pkg = cfg.venv; + python = self.packages.${pkgs.system}.python; + + inherit (lib) mkEnableOption mkOption mkIf types; + in + { + options.services.mycal = { + enable = mkEnableOption "Mycal calendar anonymiser"; + + 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. + ''; + }; + + venv = mkOption { + type = lib.types.package; + default = self.packages.${pkgs.system}.default; + description = '' + Mycal virtual environment package + ''; + }; + + timezone = mkOption { + type = types.str; + default = "Europe/Berlin"; + description = '' + Default timezone for the calendar. + ''; + }; + + name = mkOption { + type = types.str; + description = '' + Name of person for whom this calendar is created. + ''; + }; + + calendar = mkOption { + default = { }; + type = types.submodule { + options = { + file = mkOption { + type = types.str; + default = "/path/to/calendar/file"; + description = '' + Path to the calendar file. Preferred over URL. + ''; + }; + url = mkOption { + type = types.str; + default = "https://example.com/calendar"; + description = '' + URL to the calendar file. + ''; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.mycal = { + description = "Mycal Calendar Service"; + + environment = { + TZ = cfg.timezone; + NAME = cfg.name; + CALENDAR_FILE = cfg.calendar.file; + CALENDAR_URL = cfg.calendar.url; + PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/${python.sitePackages}"; + }; + + serviceConfig = { + ExecStart = '' + ${lib.getExe python.pkgs.gunicorn} \ + --bind ${cfg.host}:${toString cfg.port} \ + --workers 1 \ + mycal:app + ''; + Restart = "on-failure"; + + DynamicUser = true; + StateDirectory = "mycal"; + RuntimeDirectory = "mycal"; + + BindReadOnlyPaths = [ + "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt" + builtins.storeDir + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/hosts" + "-/etc/localtime" + ]; + }; + + wantedBy = [ "multi-user.target" ]; + }; + }; + }; + }; + }; +}