From d71274cf202252316c4a6167eadb1d6835c1a546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:17:24 +0200 Subject: [PATCH 01/13] feat(nix): adding flake.nix --- .gitignore | 2 ++ flake.lock | 61 +++++++++++++++++++++++++++++++++++++++ flake.nix | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.gitignore b/.gitignore index 22a8197dd..69fb3fd63 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ ui.zip # local embedded frontend output for fast E2E runs internal/http/dist/* !internal/http/dist/.gitkeep + +result diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..225d4a914 --- /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": 1781577229, + "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "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 000000000..068f0f2eb --- /dev/null +++ b/flake.nix @@ -0,0 +1,83 @@ +{ + description = "LeafWiki – a fast wiki for people who think in folders, not feeds"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + version = "0.0.0"; + + # Build the Vite/React frontend + ui = pkgs.buildNpmPackage { + pname = "leafwiki-ui"; + inherit version; + src = ./ui/leafwiki-ui; + + npmDepsHash = "sha256-gYL+VczQNEISjvNEtsfjFMFJ0tvfYgqeERlUcHdzAsY="; + + env.VITE_API_URL = "/"; + + installPhase = '' + runHook preInstall + cp -r dist $out + runHook postInstall + ''; + }; + + # Build the Go backend, embedding the compiled frontend + leafwiki = pkgs.buildGoModule { + pname = "leafwiki"; + inherit version; + src = ./.; + + vendorHash = "sha256-/5K4BfYCFeNCJ/Sfd1eV7GO3LMx7pEe5yst7el+TfaY="; + + # Copy frontend dist into the expected location before the Go build + preBuild = '' + mkdir -p internal/http/dist + cp -r ${ui}/. internal/http/dist/ + ''; + + ldflags = [ + "-s" + "-w" + "-X github.com/perber/wiki/internal/http.EmbedFrontend=true" + "-X github.com/perber/wiki/internal/http.Environment=production" + ]; + + # Only build the main binary; e2e-proxy is a separate Go module + subPackages = [ "cmd/leafwiki" ]; + + # modernc.org/sqlite is pure Go – no C compiler needed + env.CGO_ENABLED = "0"; + }; + in + { + packages = { + default = leafwiki; + inherit leafwiki ui; + }; + + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + # Go toolchain + go + gopls + gotools + golangci-lint + # Node / frontend + nodejs + nodePackages.typescript-language-server + # Utilities + git + gnumake + ]; + }; + }); +} From 4e98565a0ec75edd43f928ab85f7be1a88a1d80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:27:16 +0200 Subject: [PATCH 02/13] fix(nix): fixing develop shell --- flake.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 068f0f2eb..2d628a21c 100644 --- a/flake.nix +++ b/flake.nix @@ -64,6 +64,11 @@ inherit leafwiki ui; }; + apps = { + default = flake-utils.lib.mkApp { drv = leafwiki; }; + leafwiki = flake-utils.lib.mkApp { drv = leafwiki; }; + }; + devShells.default = pkgs.mkShell { packages = with pkgs; [ # Go toolchain @@ -73,7 +78,7 @@ golangci-lint # Node / frontend nodejs - nodePackages.typescript-language-server + typescript-language-server # Utilities git gnumake From 3f48f52e28cc818e01cc0cd34ab99e7d9398e1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:30:37 +0200 Subject: [PATCH 03/13] docs(nix): adding nix run to readme --- README.md | 4 ++++ flake.nix | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 16c0d57aa..923f1423d 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,13 @@ If you've looked at Wiki.js or Outline and thought "this is too much to operate → If it fits, [a star](https://github.com/perber/leafwiki) helps others find it. ```bash +# Docker docker run -p 8080:8080 -v ~/leafwiki-data:/app/data \ ghcr.io/perber/leafwiki:latest \ --jwt-secret=yoursecret --admin-password=yourpassword --allow-insecure=true + +# Nix +nix run github:perber/leafwiki -- --help ``` → [All install options](#install) (Docker Compose, Linux installer, binary) diff --git a/flake.nix b/flake.nix index 2d628a21c..9b4220660 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = { nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; From 8f61186071fd4ea0dd19b765306fb12369e8a913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:32:31 +0200 Subject: [PATCH 04/13] fix(nix): revert removal of self argument --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 9b4220660..2d628a21c 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { nixpkgs, flake-utils }: + outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; From ca3859dbb1914d032113aa46893676d42aed520c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:36:41 +0200 Subject: [PATCH 05/13] chore(nix): change character --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 2d628a21c..cb9ea177d 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "LeafWiki – a fast wiki for people who think in folders, not feeds"; + description = "LeafWiki - a fast wiki for people who think in folders, not feeds"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; From 068873cb27d97761c272a19600a0495979143f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:43:52 +0200 Subject: [PATCH 06/13] chore(nix): adding install section for leafwiki --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 923f1423d..3820bc8c2 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,12 @@ sudo ./install.sh --non-interactive --env-file ./.env - [Install with nginx on Ubuntu](docs/install/nginx.md) - [Install on a Raspberry Pi](docs/install/raspberry.md) +### Nix + +```bash +nix run github:perber/leafwiki -- --jwt-secret=yoursecret --admin-password=yourpassword --allow-insecure=true +``` + ### Binary ```bash From 36e0701e4d6ec8ce782703c528db41bdfa477cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:46:09 +0200 Subject: [PATCH 07/13] chore(nix): adding vscode extension recommendation for nix --- .vscode/extensions.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..d2b9d9e59 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "bbenoist.nix" + ] +} From 4e7c0cc27051558083178e76934109e4d2c2cffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:50:29 +0200 Subject: [PATCH 08/13] feat(direnv): adding direnv with nix support --- .envrc | 14 ++++++++++++++ .gitignore | 1 + 2 files changed, 15 insertions(+) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..c974eaba2 --- /dev/null +++ b/.envrc @@ -0,0 +1,14 @@ +# shellcheck shell=bash + +if command -v nix >/dev/null 2>&1; then + if ! has nix_direnv_version; then + log_status "nix-direnv not found; installing to add nix support..." + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.1/direnvrc" "sha256-p+fzQdrms/hDa7g+soShAybJNo4bN4SIAeSfqNKgD5I=" + fi + + log_status "Loading nix shell from flake..." + log_status "To suppress direnv log output: export DIRENV_LOG_FORMAT=\"\"" + + watch_file $(find . -name "*.nix") + use flake +fi diff --git a/.gitignore b/.gitignore index 69fb3fd63..4aa32ba3c 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ internal/http/dist/* !internal/http/dist/.gitkeep result +.direnv From 3873f713a4bf6b6efb80668b274a950bdd32e5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:52:20 +0200 Subject: [PATCH 09/13] chore(direnv): adding direnv extension recommendation for vscode --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d2b9d9e59..49869f319 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ - "bbenoist.nix" + "bbenoist.nix", + "mkhl.direnv" ] } From 135bbf45c69b7de7719fab09a4fe12875e049646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 14:57:58 +0200 Subject: [PATCH 10/13] fix(nix): fixing ui dist copy --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index cb9ea177d..24bafa8bb 100644 --- a/flake.nix +++ b/flake.nix @@ -25,7 +25,8 @@ installPhase = '' runHook preInstall - cp -r dist $out + mkdir -p $out + cp -r dist/. $out/ runHook postInstall ''; }; From 18ef67d6c0d5805f18967776122bb5ea8c621ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 15:24:19 +0200 Subject: [PATCH 11/13] feat(nix): adding nixos and homemanager modules and refactored flake.nix into individual files --- README.md | 77 ++++++++++++++++++++++++ flake.nix | 114 +++++++++++------------------------- nix/common.nix | 72 +++++++++++++++++++++++ nix/home-manager-module.nix | 49 ++++++++++++++++ nix/nixos-module.nix | 79 +++++++++++++++++++++++++ nix/packages.nix | 46 +++++++++++++++ nix/shell.nix | 18 ++++++ 7 files changed, 376 insertions(+), 79 deletions(-) create mode 100644 nix/common.nix create mode 100644 nix/home-manager-module.nix create mode 100644 nix/nixos-module.nix create mode 100644 nix/packages.nix create mode 100644 nix/shell.nix diff --git a/README.md b/README.md index 3820bc8c2..e229278e5 100644 --- a/README.md +++ b/README.md @@ -171,10 +171,87 @@ sudo ./install.sh --non-interactive --env-file ./.env ### Nix +#### Try it without installing + ```bash nix run github:perber/leafwiki -- --jwt-secret=yoursecret --admin-password=yourpassword --allow-insecure=true ``` +#### Add to a NixOS configuration + +In your `flake.nix`: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + leafwiki.url = "github:perber/leafwiki"; + }; + + outputs = { nixpkgs, leafwiki, ... }: { + nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { + modules = [ + leafwiki.nixosModules.default + { + services.leafwiki = { + enable = true; + jwtSecretFile = "/run/secrets/leafwiki-jwt"; + adminPasswordFile = "/run/secrets/leafwiki-admin-password"; + # Optional overrides: + # host = "0.0.0.0"; + # port = 8080; + # dataDir = "/var/lib/leafwiki"; + }; + } + ]; + }; + }; +} +``` + +Secrets are loaded from files at service start using systemd `LoadCredential`, so they never appear in the Nix store or the unit file. + +For public wikis with no login: + +```nix +services.leafwiki = { + enable = true; + disableAuth = true; +}; +``` + +#### Add to a Home Manager configuration + +```nix +{ + inputs = { + home-manager.url = "github:nix-community/home-manager"; + leafwiki.url = "github:perber/leafwiki"; + }; + + outputs = { home-manager, leafwiki, ... }: { + homeConfigurations.myuser = home-manager.lib.homeManagerConfiguration { + modules = [ + leafwiki.homeManagerModules.default + { + services.leafwiki = { + enable = true; + jwtSecretFile = "/run/user/1000/secrets/leafwiki-jwt"; + adminPasswordFile = "/run/user/1000/secrets/leafwiki-admin-password"; + }; + } + ]; + }; + }; +} +``` + +The Home Manager module runs leafwiki as a `systemd --user` service. Enable lingering so it starts at boot without a login session: + +```bash +loginctl enable-linger $USER +``` + ### Binary ```bash diff --git a/flake.nix b/flake.nix index 24bafa8bb..3070fd739 100644 --- a/flake.nix +++ b/flake.nix @@ -7,83 +7,39 @@ }; outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - - version = "0.0.0"; - - # Build the Vite/React frontend - ui = pkgs.buildNpmPackage { - pname = "leafwiki-ui"; - inherit version; - src = ./ui/leafwiki-ui; - - npmDepsHash = "sha256-gYL+VczQNEISjvNEtsfjFMFJ0tvfYgqeERlUcHdzAsY="; - - env.VITE_API_URL = "/"; - - installPhase = '' - runHook preInstall - mkdir -p $out - cp -r dist/. $out/ - runHook postInstall - ''; - }; - - # Build the Go backend, embedding the compiled frontend - leafwiki = pkgs.buildGoModule { - pname = "leafwiki"; - inherit version; - src = ./.; - - vendorHash = "sha256-/5K4BfYCFeNCJ/Sfd1eV7GO3LMx7pEe5yst7el+TfaY="; - - # Copy frontend dist into the expected location before the Go build - preBuild = '' - mkdir -p internal/http/dist - cp -r ${ui}/. internal/http/dist/ - ''; - - ldflags = [ - "-s" - "-w" - "-X github.com/perber/wiki/internal/http.EmbedFrontend=true" - "-X github.com/perber/wiki/internal/http.Environment=production" - ]; - - # Only build the main binary; e2e-proxy is a separate Go module - subPackages = [ "cmd/leafwiki" ]; - - # modernc.org/sqlite is pure Go – no C compiler needed - env.CGO_ENABLED = "0"; - }; - in - { - packages = { - default = leafwiki; - inherit leafwiki ui; - }; - - apps = { - default = flake-utils.lib.mkApp { drv = leafwiki; }; - leafwiki = flake-utils.lib.mkApp { drv = leafwiki; }; - }; - - devShells.default = pkgs.mkShell { - packages = with pkgs; [ - # Go toolchain - go - gopls - gotools - golangci-lint - # Node / frontend - nodejs - typescript-language-server - # Utilities - git - gnumake - ]; - }; - }); + let + version = "0.0.0"; + + perSystemOutputs = flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + pkgSet = import ./nix/packages.nix { inherit pkgs version; }; + packages = { default = pkgSet.leafwiki; inherit (pkgSet) leafwiki ui; }; + apps = rec { + default = leafwiki-app; + leafwiki-app = { + type = "app"; + program = "${pkgSet.leafwiki}/bin/leafwiki"; + }; + }; + shell = import ./nix/shell.nix { inherit pkgs; }; + in + { + inherit packages apps; + inherit (shell) devShells; + formatter = pkgs.nixfmt; + }); + in + perSystemOutputs // { + nixosModules = { + default = import ./nix/nixos-module.nix self; + leafwiki = import ./nix/nixos-module.nix self; + }; + + homeManagerModules = { + default = import ./nix/home-manager-module.nix self; + leafwiki = import ./nix/home-manager-module.nix self; + }; + }; } + diff --git a/nix/common.nix b/nix/common.nix new file mode 100644 index 000000000..a3fa3954d --- /dev/null +++ b/nix/common.nix @@ -0,0 +1,72 @@ +# Shared option declarations and helpers used by both the NixOS and the +# Home Manager module. Callers must pass: +# - lib – the nixpkgs lib +# - defaultPkg – the resolved leafwiki package for the current system +# - dataDirType – the mkOption type for dataDir (path vs. str differs) +# - dataDirDefault – the default value for dataDir +{ lib, defaultPkg, dataDirType, dataDirDefault }: +{ + # ---- shared option declarations ----------------------------------------- + options = { + package = lib.mkOption { + type = lib.types.package; + default = defaultPkg; + description = "The leafwiki package to use."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Address to bind to."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port to listen on."; + }; + + dataDir = lib.mkOption { + type = dataDirType; + default = dataDirDefault; + description = "Directory to store wiki data."; + }; + + jwtSecretFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Path to a file containing the JWT secret. Required unless disableAuth is true."; + }; + + adminPasswordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Path to a file containing the admin password. Required unless disableAuth is true."; + }; + + disableAuth = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Disable authentication (everyone can read and edit)."; + }; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Additional CLI arguments passed to leafwiki."; + }; + }; + + # ---- shared helpers ------------------------------------------------------ + + # Build the ExecStart string. The authArgs differ slightly between the NixOS + # module (reads from $CREDENTIALS_DIRECTORY) and Home Manager (reads the file + # path directly), so the caller supplies them. + mkExecStart = cfg: authArgs: + lib.escapeShellArgs ([ + "${cfg.package}/bin/leafwiki" + "--host=${cfg.host}" + "--port=${toString cfg.port}" + "--data-dir=${cfg.dataDir}" + ] ++ authArgs ++ cfg.extraArgs); +} diff --git a/nix/home-manager-module.nix b/nix/home-manager-module.nix new file mode 100644 index 000000000..6df7990cb --- /dev/null +++ b/nix/home-manager-module.nix @@ -0,0 +1,49 @@ +# Home Manager module – runs leafwiki as a systemd --user service. +# Secrets are read from plain files at service start time. +self: +{ config, lib, pkgs, ... }: +let + cfg = config.services.leafwiki; + # Fall back gracefully when the flake's system package is unavailable + # (e.g. when home-manager is used standalone with a different nixpkgs). + leafwikiPkg = + self.packages.${pkgs.stdenv.hostPlatform.system}.leafwiki or pkgs.leafwiki; + common = import ./common.nix { + inherit lib; + defaultPkg = leafwikiPkg; + # str instead of path: supports systemd specifiers like %h + dataDirType = lib.types.str; + dataDirDefault = "%h/.local/share/leafwiki"; + }; +in +{ + options.services.leafwiki = { + enable = lib.mkEnableOption "LeafWiki (user service)"; + } // common.options; + + config = lib.mkIf cfg.enable { + systemd.user.services.leafwiki = { + Unit = { + Description = "LeafWiki"; + After = [ "network.target" ]; + }; + + Service = { + ExecStart = + let + authArgs = + if cfg.disableAuth + then [ "--disable-auth" ] + else [ + ''--jwt-secret=$(cat "${cfg.jwtSecretFile}")'' + ''--admin-password=$(cat "${cfg.adminPasswordFile}")'' + ]; + in + common.mkExecStart cfg authArgs; + Restart = "on-failure"; + }; + + Install.WantedBy = [ "default.target" ]; + }; + }; +} diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix new file mode 100644 index 000000000..c73d68892 --- /dev/null +++ b/nix/nixos-module.nix @@ -0,0 +1,79 @@ +# NixOS module – runs leafwiki as a hardened system-level systemd service. +# Secret files are loaded via systemd LoadCredential so they never appear +# in the Nix store or the unit file. +self: +{ config, lib, pkgs, ... }: +let + cfg = config.services.leafwiki; + leafwikiPkg = self.packages.${pkgs.stdenv.hostPlatform.system}.leafwiki; + common = import ./common.nix { + inherit lib; + defaultPkg = leafwikiPkg; + dataDirType = lib.types.path; + dataDirDefault = "/var/lib/leafwiki"; + }; +in +{ + options.services.leafwiki = { + enable = lib.mkEnableOption "LeafWiki"; + + user = lib.mkOption { + type = lib.types.str; + default = "leafwiki"; + description = "User account under which leafwiki runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "leafwiki"; + description = "Group under which leafwiki runs."; + }; + } // common.options; + + config = lib.mkIf cfg.enable { + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + home = cfg.dataDir; + createHome = true; + }; + users.groups.${cfg.group} = {}; + + systemd.services.leafwiki = { + description = "LeafWiki"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.dataDir; + Restart = "on-failure"; + + ExecStart = + let + authArgs = + if cfg.disableAuth + then [ "--disable-auth" ] + else [ + # LoadCredential exposes secrets under $CREDENTIALS_DIRECTORY + ''--jwt-secret=$(cat "$CREDENTIALS_DIRECTORY/jwt-secret")'' + ''--admin-password=$(cat "$CREDENTIALS_DIRECTORY/admin-password")'' + ]; + in + common.mkExecStart cfg authArgs; + + LoadCredential = lib.optionals (!cfg.disableAuth) [ + "jwt-secret:${cfg.jwtSecretFile}" + "admin-password:${cfg.adminPasswordFile}" + ]; + + # Hardening + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ReadWritePaths = [ cfg.dataDir ]; + }; + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 000000000..391c96f3b --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,46 @@ +# Per-system package derivations, consumed by flake-utils.lib.eachDefaultSystem. +{ pkgs, version }: +rec { + ui = pkgs.buildNpmPackage { + pname = "leafwiki-ui"; + inherit version; + src = ../ui/leafwiki-ui; + + npmDepsHash = "sha256-gYL+VczQNEISjvNEtsfjFMFJ0tvfYgqeERlUcHdzAsY="; + + env.VITE_API_URL = "/"; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp -r dist/. $out/ + runHook postInstall + ''; + }; + + leafwiki = pkgs.buildGoModule { + pname = "leafwiki"; + inherit version; + src = ../.; + + vendorHash = "sha256-/5K4BfYCFeNCJ/Sfd1eV7GO3LMx7pEe5yst7el+TfaY="; + + preBuild = '' + mkdir -p internal/http/dist + cp -r ${ui}/. internal/http/dist/ + ''; + + ldflags = [ + "-s" + "-w" + "-X github.com/perber/wiki/internal/http.EmbedFrontend=true" + "-X github.com/perber/wiki/internal/http.Environment=production" + ]; + + # Only build the main binary; e2e-proxy is a separate Go module + subPackages = [ "cmd/leafwiki" ]; + + # modernc.org/sqlite is pure Go – no C compiler needed + env.CGO_ENABLED = "0"; + }; +} diff --git a/nix/shell.nix b/nix/shell.nix new file mode 100644 index 000000000..2e107379c --- /dev/null +++ b/nix/shell.nix @@ -0,0 +1,18 @@ +{ pkgs }: +{ + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + # Go toolchain + go + gopls + gotools + golangci-lint + # Node / frontend + nodejs + typescript-language-server + # Utilities + git + gnumake + ]; + }; +} From 49f40685d7db62ad63dae0ccf2f7d39202926677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 15:30:34 +0200 Subject: [PATCH 12/13] chore(nix): reformatting --- flake.nix | 30 ++++++++++++++++++++---------- nix/common.nix | 28 +++++++++++++++++++--------- nix/home-manager-module.nix | 26 ++++++++++++++++---------- nix/nixos-module.nix | 27 +++++++++++++++++---------- 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/flake.nix b/flake.nix index 3070fd739..79ad6dcb7 100644 --- a/flake.nix +++ b/flake.nix @@ -6,31 +6,42 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = + { + self, + nixpkgs, + flake-utils, + }: let version = "0.0.0"; - perSystemOutputs = flake-utils.lib.eachDefaultSystem (system: + perSystemOutputs = flake-utils.lib.eachDefaultSystem ( + system: let - pkgs = nixpkgs.legacyPackages.${system}; - pkgSet = import ./nix/packages.nix { inherit pkgs version; }; - packages = { default = pkgSet.leafwiki; inherit (pkgSet) leafwiki ui; }; - apps = rec { + pkgs = nixpkgs.legacyPackages.${system}; + pkgSet = import ./nix/packages.nix { inherit pkgs version; }; + packages = { + default = pkgSet.leafwiki; + inherit (pkgSet) leafwiki ui; + }; + apps = rec { default = leafwiki-app; leafwiki-app = { type = "app"; program = "${pkgSet.leafwiki}/bin/leafwiki"; }; }; - shell = import ./nix/shell.nix { inherit pkgs; }; + shell = import ./nix/shell.nix { inherit pkgs; }; in { inherit packages apps; inherit (shell) devShells; formatter = pkgs.nixfmt; - }); + } + ); in - perSystemOutputs // { + perSystemOutputs + // { nixosModules = { default = import ./nix/nixos-module.nix self; leafwiki = import ./nix/nixos-module.nix self; @@ -42,4 +53,3 @@ }; }; } - diff --git a/nix/common.nix b/nix/common.nix index a3fa3954d..a52e50d88 100644 --- a/nix/common.nix +++ b/nix/common.nix @@ -4,7 +4,12 @@ # - defaultPkg – the resolved leafwiki package for the current system # - dataDirType – the mkOption type for dataDir (path vs. str differs) # - dataDirDefault – the default value for dataDir -{ lib, defaultPkg, dataDirType, dataDirDefault }: +{ + lib, + defaultPkg, + dataDirType, + dataDirDefault, +}: { # ---- shared option declarations ----------------------------------------- options = { @@ -52,7 +57,7 @@ extraArgs = lib.mkOption { type = lib.types.listOf lib.types.str; - default = []; + default = [ ]; description = "Additional CLI arguments passed to leafwiki."; }; }; @@ -62,11 +67,16 @@ # Build the ExecStart string. The authArgs differ slightly between the NixOS # module (reads from $CREDENTIALS_DIRECTORY) and Home Manager (reads the file # path directly), so the caller supplies them. - mkExecStart = cfg: authArgs: - lib.escapeShellArgs ([ - "${cfg.package}/bin/leafwiki" - "--host=${cfg.host}" - "--port=${toString cfg.port}" - "--data-dir=${cfg.dataDir}" - ] ++ authArgs ++ cfg.extraArgs); + mkExecStart = + cfg: authArgs: + lib.escapeShellArgs ( + [ + "${cfg.package}/bin/leafwiki" + "--host=${cfg.host}" + "--port=${toString cfg.port}" + "--data-dir=${cfg.dataDir}" + ] + ++ authArgs + ++ cfg.extraArgs + ); } diff --git a/nix/home-manager-module.nix b/nix/home-manager-module.nix index 6df7990cb..64dac7573 100644 --- a/nix/home-manager-module.nix +++ b/nix/home-manager-module.nix @@ -1,13 +1,17 @@ # Home Manager module – runs leafwiki as a systemd --user service. # Secrets are read from plain files at service start time. self: -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.leafwiki; # Fall back gracefully when the flake's system package is unavailable # (e.g. when home-manager is used standalone with a different nixpkgs). - leafwikiPkg = - self.packages.${pkgs.stdenv.hostPlatform.system}.leafwiki or pkgs.leafwiki; + leafwikiPkg = self.packages.${pkgs.stdenv.hostPlatform.system}.leafwiki or pkgs.leafwiki; common = import ./common.nix { inherit lib; defaultPkg = leafwikiPkg; @@ -19,7 +23,8 @@ in { options.services.leafwiki = { enable = lib.mkEnableOption "LeafWiki (user service)"; - } // common.options; + } + // common.options; config = lib.mkIf cfg.enable { systemd.user.services.leafwiki = { @@ -32,12 +37,13 @@ in ExecStart = let authArgs = - if cfg.disableAuth - then [ "--disable-auth" ] - else [ - ''--jwt-secret=$(cat "${cfg.jwtSecretFile}")'' - ''--admin-password=$(cat "${cfg.adminPasswordFile}")'' - ]; + if cfg.disableAuth then + [ "--disable-auth" ] + else + [ + ''--jwt-secret=$(cat "${cfg.jwtSecretFile}")'' + ''--admin-password=$(cat "${cfg.adminPasswordFile}")'' + ]; in common.mkExecStart cfg authArgs; Restart = "on-failure"; diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix index c73d68892..619ec541e 100644 --- a/nix/nixos-module.nix +++ b/nix/nixos-module.nix @@ -2,7 +2,12 @@ # Secret files are loaded via systemd LoadCredential so they never appear # in the Nix store or the unit file. self: -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.leafwiki; leafwikiPkg = self.packages.${pkgs.stdenv.hostPlatform.system}.leafwiki; @@ -28,7 +33,8 @@ in default = "leafwiki"; description = "Group under which leafwiki runs."; }; - } // common.options; + } + // common.options; config = lib.mkIf cfg.enable { users.users.${cfg.user} = { @@ -37,7 +43,7 @@ in home = cfg.dataDir; createHome = true; }; - users.groups.${cfg.group} = {}; + users.groups.${cfg.group} = { }; systemd.services.leafwiki = { description = "LeafWiki"; @@ -53,13 +59,14 @@ in ExecStart = let authArgs = - if cfg.disableAuth - then [ "--disable-auth" ] - else [ - # LoadCredential exposes secrets under $CREDENTIALS_DIRECTORY - ''--jwt-secret=$(cat "$CREDENTIALS_DIRECTORY/jwt-secret")'' - ''--admin-password=$(cat "$CREDENTIALS_DIRECTORY/admin-password")'' - ]; + if cfg.disableAuth then + [ "--disable-auth" ] + else + [ + # LoadCredential exposes secrets under $CREDENTIALS_DIRECTORY + ''--jwt-secret=$(cat "$CREDENTIALS_DIRECTORY/jwt-secret")'' + ''--admin-password=$(cat "$CREDENTIALS_DIRECTORY/admin-password")'' + ]; in common.mkExecStart cfg authArgs; From 75d4353edb98d63eff50fdb3d15b23aef0c084a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6slinger?= Date: Thu, 18 Jun 2026 15:39:38 +0200 Subject: [PATCH 13/13] chore(nix): moved disableAuth option to example section --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index e229278e5..980102285 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ In your `flake.nix`: # host = "0.0.0.0"; # port = 8080; # dataDir = "/var/lib/leafwiki"; + # disableAuth = true; # insecure - only for local dev instances! }; } ]; @@ -211,15 +212,6 @@ In your `flake.nix`: Secrets are loaded from files at service start using systemd `LoadCredential`, so they never appear in the Nix store or the unit file. -For public wikis with no login: - -```nix -services.leafwiki = { - enable = true; - disableAuth = true; -}; -``` - #### Add to a Home Manager configuration ```nix