{ inputs = { flake-utils.url = "github:numtide/flake-utils"; rust = { type = "github"; owner = "oxalica"; repo = "rust-overlay"; ref = "master"; inputs.flake-utils.follows = "flake-utils"; inputs.nixpkgs.follows = "nixpkgs"; }; naersk = { type = "github"; owner = "nmattia"; repo = "naersk"; ref = "master"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, rust, flake-utils, naersk }: let supportedSystems = ["aarch64-linux" "x86_64-linux"]; forAllSystems = f: flake-utils.lib.eachSystem supportedSystems f; in { nixosModule = { config, pkgs, lib, ... }: with lib; let cfg = config.services.kittybox; in { options = { services.kittybox = { enable = mkOption { type = types.bool; default = false; description = '' Whether to enable Kittybox, the IndieWeb blogging solution. ''; }; package = mkOption { type = types.package; default = self.packages.${config.nixpkgs.localSystem.system}.kittybox-micropub; defaultText = ""; description = "Which Kittybox derivation to use."; }; bind = mkOption { type = types.nullOr types.str; default = "127.0.0.1"; description = "The host for Kittybox to bind to."; example = "192.168.1.100"; }; port = mkOption { type = types.int; default = 8080; description = "The port for Kittybox to listen at."; example = 16420; }; logLevel = mkOption { type = types.str; default = "warn"; example = "info"; description = "Specify the server verbosity level. Uses RUST_LOG environment variable internally."; }; redisUri = mkOption { type = types.nullOr types.str; default = null; example = "redis://192.168.1.200:6379/"; description = "Set the Redis instance used as backing storage. If null, Redis will be configured on localhost. Use services.redis to change parameters."; }; tokenEndpoint = mkOption { type = types.str; example = "https://tokens.indieauth.com/token"; description = "Token endpoint to use for authenticating Micropub requests. Use the example if you are unsure."; }; authorizationEndpoint = mkOption { type = types.str; example = "https://indieauth.com/auth"; description = "Authorization endpoint to use to authenticate the user. You can use the default if you are unsure."; }; }; }; config = lib.mkIf cfg.enable { systemd.services.kittybox = { description = "An IndieWeb-enabled blog engine"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; restartTriggers = [ cfg.package cfg.redisUri cfg.tokenEndpoint cfg.authorizationEndpoint cfg.bind cfg.port ]; environment = { SERVE_AT = "${cfg.bind}:${builtins.toString cfg.port}"; AUTHORIZATION_ENDPOINT = cfg.authorizationEndpoint; TOKEN_ENDPOINT = cfg.tokenEndpoint; REDIS_URI = if (cfg.redisUri == null) then "redis://127.0.0.1:6379/" else cfg.redisUri; }; serviceConfig = { ExecStart = "${cfg.package}/bin/kittybox_micropub"; DynamicUser = true; }; }; services.redis = lib.mkIf (cfg.redisUri == null) { enable = true; }; }; }; } // forAllSystems (system: let pkgs = import nixpkgs { localSystem.system = system; overlays = [ rust.overlay ]; }; rust-bin = pkgs.rust-bin.stable.latest; packages = { kittybox-micropub = { stdenv, lib, redis, naersk-lib }: naersk-lib.buildPackage { pname = "kittybox-micropub"; version = "0.1.0"; src = ./.; checkInputs = [ redis ]; doCheck = stdenv.hostPlatform == stdenv.targetPlatform; meta = with lib.meta; { maintainers = with maintainers; [ vika_nezrimaya ]; platforms = supportedSystems; mainProgram = "kittybox_micropub"; }; }; }; in { packages = let naersk-lib = naersk.lib.${system}.override { rustc = pkgs.rust-bin.nightly.latest.minimal; cargo = pkgs.rust-bin.nightly.latest.minimal; }; in { kittybox-micropub = pkgs.callPackage packages.kittybox-micropub { inherit naersk-lib; }; }; defaultPackage = self.packages.${system}.kittybox-micropub; checks = { nixos-test = pkgs.nixosTest ({ lib }: { name = "nixos-kittybox"; nodes = { kittybox = { config, pkgs, lib, ... }: { imports = [ self.nixosModule ]; services.kittybox = { enable = true; # It never actually contacts those endpoints anyway unless we use Micropub so it's fine! # TODO: Once we have self-hosted software for those endpoints, # make an e2e test for common workflows (e.g. making a post) tokenEndpoint = "https://example.com"; authorizationEndpoint = "https://example.com"; }; environment.systemPackages = with pkgs; [ curl ]; }; }; testScript = '' kittybox.start() kittybox.wait_for_unit("default.target") kittybox.succeed("curl --silent http://localhost:8080/micropub") ''; }); }; devShell = pkgs.mkShell { name = "rust-dev-shell"; nativeBuildInputs = with pkgs; [ pkg-config lld rust-bin.default rust-bin.rls redis ]; }; }); }