Changes
8 changed files (+630/-14)
-
-
@@ -4,3 +4,6 @@ result# demo/ directory acts as a playground/sandbox, and real git repositories sits there. demo/* !demo/config.yaml # QEMU disk image. *.qcow2
-
-
-
@@ -48,3 +48,53 @@ podman run --volume ./demo:/var/www/legit --publish 5555:5555 pocka/legit:latestIf you don't have podman on your NixOS system and quickly test these steps, use `nix develop .#podman` devShell. It has podman and configures minimum podman environment. ## Testing NixOS module `flake.nix` has [soft-serve](https://github.com/charmbracelet/soft-serve) + [Caddy](https://caddyserver.com/) + legit NixOS configuration for testing purpose. Run, `nix run .#nixosConfigurations.soft-legit.config.system.build.vm` to launch it in QEMU. It'll create `nixos.qcow2` file as a disk image, so delete the file to start from scratch. To access the VM via SSH and HTTP, set `QEMU_NET_OPTS="hostfwd=tcp::<HOST HTTP PORT>-:80,hostfwd=tcp::<HOST SSH PORT>-:22222"` to forward TCP packets. For example, ```sh QEMU_NET_OPTS="hostfwd=tcp::8000-:80,hostfwd=tcp::22222-:22222" nix run .#nixosConfigurations.soft-legit.config.system.build.vm ``` As soft-serve has no way to setup initial users without setting a `SOFT_SERVE_INITIAL_ADMIN_KEYS` environment variable _on first launch_, you have to do some manual work for initial launch. The systemd service for soft-service does not start until SSH public keys for initial admin are registered. ```sh # inside VM... # Replace https://codeberg.org/pocka.keys with another provider / your account. curl -sL https://codeberg.org/pocka.keys | write-initial-admin-keys # ...the "write-initial-admin-keys" script writes to a systemd environment file. ``` Once you write your SSH public key to the file, the service automatically launches and you can access soft-serve as an admin user. Then, import a git repository via SSH: ```sh # on host... # This command uses the host port number I used in the above QEMU launch example command. ssh -o IdentitiesOnly=yes -p 22222 localhost -- repo import legit https://github.com/pocka/legit ``` After successful repository creation, legit service automatically starts and you can access legit pages on localhost at the forwarded HTTP port. You can also check NixOS configurations without running the VM, by `nix flake check`. ## Testing Home Manager module You can test [Home Manager](https://github.com/nix-community/home-manager) module using NixOS VM as well. See the above "Testing NixOS module" section for required steps. Key differences are: - The flake reference will be `.#nixosConfigurations.soft-legit-hm` (`nix run .#nixosConfigurations.soft-legit-hm.system.build.vm`) - Login user will be `alice` rather than `admin` - Both soft-serve and legit run under `alice` (you have to add `--user` flag to systemd, like `systemd --user status legit.service`)
-
-
-
@@ -34,26 +34,35 @@ You can also run legit without installing by `go run .`.### Nix As a quick and dirty way, you can use the original `legit-web` package and overlay to install this fork. Add this repository as a Flake input and use `nixosModules.default` or `homeManagerModules.default`. ```nix final: prev: # your/flake.nix { legit-web = prev.legit-web.overrideAttrs (old: { src = prev.fetchFromGitHub { owner = "pocka"; repo = "legit"; rev = "bc147a9425e6265adca2672103c0d0b0dfcd735d"; hash = "sha256-We3ceKWo9viSfM9C/l7CvKiwfGf8bbKvH7M6M0xU1Cg="; }; vendorHash = "sha256-QxkMxO8uzBCC3oMSWjdVsbR2cluYMx5OOKTgaNOLHxc="; }); inputs = { # --- snip --- legit = { url = "github:pocka/legit"; inputs.nixpkgs.follows = "nixpkgs"; # optional }; }; outputs = { nixpkgs, legit, ... }: { nixosConfigurations.foo = nixpkgs.lib.nixosSystem { # --- snip --- modules = [ # --- snip --- legit.nixosModules.default ]; }; }; } ``` Runtime error will happen if Go toolchain in your nixpkgs is older than v1.24.1. ### OCI (Docker, Podman) Build the image on the project root.
-
-
-
@@ -57,6 +57,9 @@} ); homeManagerModules.default = import ./nix/home-manager-module.nix self; nixosModules.default = import ./nix/nixos-module.nix self; apps = forAllSystems ( { system, pkgs }: { k6 = {
-
@@ -151,5 +154,33 @@}; } ); nixosConfigurations = let system = "x86_64-linux"; in { soft-legit = nixpkgs.lib.nixosSystem { inherit system; pkgs = import nixpkgs { inherit system; }; modules = [ "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ./nix/nixos-configuration-soft-legit.nix self.nixosModules.default ]; }; soft-legit-hm = nixpkgs.lib.nixosSystem { inherit system; pkgs = import nixpkgs { inherit system; }; modules = [ "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ./nix/nixos-configuration-soft-legit-hm.nix ({ ... }: { home-manager.extraSpecialArgs.legit = self; }) ]; }; }; }; }
-
-
-
@@ -0,0 +1,100 @@# Copyright 2026 Shota FUJI <pockawoooh@gmail.com> # SPDX-License-Identifier: MIT self: { config, lib, pkgs, ... }: let yaml = pkgs.formats.yaml { }; in { options = { services.legit = { enable = lib.mkEnableOption "legit"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.system}.legit; }; config = lib.mkOption { type = yaml.type; default = { repo.scanPath = "${config.xdg.dataHome}/legit/repos"; meta = { syntaxHighlight = true; }; footer = { poweredBy = true; }; server = { host = "127.0.0.1"; port = 5555; }; }; description = '' The contents of the configuration file for legit. ''; example = lib.literalExpression '' { repo.scanPath = "''${config.xdg.dataHome}/legit/repos"; meta = { title = "My Projects"; description = "My git repos"; syntaxHighlight = true; }; footer = { links = [ { text = "Contact"; href = "mailto:alice@example.com"; } ]; poweredBy = true; }; server = { name = "git.example.com"; host = "127.0.0.1"; port = 5555; }; } ''; }; }; }; config = let cfg = config.services.legit; configFile = yaml.generate "legit.config.yaml" cfg.config; in lib.mkIf cfg.enable { systemd.user.paths.legit-repos = { Unit = { Description = "repositories directory read by legit"; }; Install = { WantedBy = [ "default.target" ]; }; Path = { PathExists = cfg.config.repo.scanPath; Unit = "legit.service"; }; }; systemd.user.services.legit = { Unit = { Description = "web frontend for git repositories"; After = [ "network.target" ]; }; Service = { Type = "simple"; ExecStart = "${lib.getExe cfg.package} -config ${configFile}"; Restart = "always"; }; }; }; }
-
-
-
@@ -0,0 +1,154 @@# Copyright 2026 Shota FUJI <pockawoooh@gmail.com> # SPDX-License-Identifier: MIT { config, lib, pkgs, ... }: let home-manager = builtins.fetchTarball { url = "https://github.com/nix-community/home-manager/archive/4ce190229c73d44536caa7072f6308fb2d8feeb3.tar.gz"; sha256 = "1cqangi17i4nfkjpzpzpsavcgnaqdvarjjsjg09sbj5mnhnv6v35"; }; in { imports = [ (import "${home-manager}/nixos") ]; system.stateVersion = "26.11"; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; networking.firewall.enable = false; users.users = { alice = { isNormalUser = true; password = "pass"; }; }; security.polkit = { enable = true; # Password-less poweroff / reboot extraConfig = '' polkit.addRule(function(action, subject) { const isPowerOff = [ "org.freedesktop.login1.reboot", "org.freedesktop.login1.reboot-multiple-sessions", "org.freedesktop.login1.power-off", "org.freedesktop.login1.power-off-multiple-sessions", ].indexOf(action.id) > -1; if (subject.user === "alice" && isPowerOff) { return polkit.Result.YES; } }) ''; }; services.getty.autologinUser = "alice"; services.caddy = { enable = true; configFile = pkgs.writeText "Caddyfile" '' { auto_https off } :80 { reverse_proxy :5555 } ''; }; home-manager.useUserPackages = true; home-manager.users.alice = { config, lib, pkgs, legit, ... }: let write-initial-admin-keys = pkgs.writeShellApplication { name = "write-initial-admin-keys"; text = '' echo "SOFT_SERVE_INITIAL_ADMIN_KEYS=\"$(cat)\"" > ${config.xdg.configHome}/soft-serve/admin.env ''; }; in { imports = [ legit.homeManagerModules.default ]; home.stateVersion = "26.05"; # This is inevitable as we use fetched tarball along with nixpkgs managed by Flake. home.enableNixpkgsReleaseCheck = false; home.packages = with pkgs; [ curl write-initial-admin-keys ]; services.legit = { enable = true; config = { repo.scanPath = "${config.xdg.dataHome}/soft-serve/repos"; }; }; xdg.configFile."soft-serve/.keep" = { text = ""; }; xdg.dataFile."soft-serve/config.yaml" = let yaml = pkgs.formats.yaml { }; in { source = yaml.generate "soft-serve.yaml" { name = "soft-serve & legit HM module demo"; log_format = "text"; ssh.listen_addr = ":22222"; http.listen_addr = ":8080"; }; }; systemd.user.paths.soft-serve-adminenv = { Unit = { Description = "soft-serve initial admin env file"; }; Install = { WantedBy = [ "default.target" ]; }; Path = { PathExists = "${config.xdg.configHome}/soft-serve/admin.env"; Unit = "soft-serve.service"; }; }; systemd.user.services.soft-serve = { Unit = { Description = "Git server for the command-line"; After = [ "network.target" ]; }; Service = { Type = "simple"; Restart = "always"; RestartSec = 1; ExecStart = "${lib.getExe pkgs.soft-serve} serve"; Environment = "SOFT_SERVE_DATA_PATH=${config.xdg.dataHome}/soft-serve"; EnvironmentFile = "${config.xdg.configHome}/soft-serve/admin.env"; WorkingDirectory = "${config.xdg.dataHome}/soft-serve"; }; }; }; }
-
-
-
@@ -0,0 +1,149 @@# Copyright 2026 Shota FUJI <pockawoooh@gmail.com> # SPDX-License-Identifier: MIT { config, lib, pkgs, ... }: let # Directory managed by soft-serve. softserveData = "/opt/soft"; softserveEnvDir = "/opt/soft-admin"; softserveAdminKeyEnv = "${softserveEnvDir}/admin.env"; write-initial-admin-keys = pkgs.writeShellApplication { name = "write-initial-admin-keys"; text = '' echo "SOFT_SERVE_INITIAL_ADMIN_KEYS=\"$(cat)\"" > ${softserveAdminKeyEnv} ''; }; in { system.stateVersion = "26.11"; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; networking.firewall.enable = false; users.groups.git = { }; systemd.tmpfiles.rules = [ "d ${softserveData} 0750 soft git -" "d ${softserveEnvDir} 0770 soft git -" ]; users.users = { soft = { isSystemUser = true; group = "git"; }; legit = { isSystemUser = true; group = "git"; }; admin = { isNormalUser = true; password = "pass"; group = "git"; }; }; security.polkit = { enable = true; # Password-less poweroff / reboot extraConfig = '' polkit.addRule(function(action, subject) { const isPowerOff = [ "org.freedesktop.login1.reboot", "org.freedesktop.login1.reboot-multiple-sessions", "org.freedesktop.login1.power-off", "org.freedesktop.login1.power-off-multiple-sessions", ].indexOf(action.id) > -1; if (subject.user === "admin" && isPowerOff) { return polkit.Result.YES; } }) ''; }; services.getty.autologinUser = "admin"; environment.systemPackages = with pkgs; [ curl soft-serve write-initial-admin-keys ]; systemd.paths.soft-serve-adminenv = { description = "availability of initial admin env file"; wantedBy = [ "default.target" ]; pathConfig = { PathExists = softserveAdminKeyEnv; Unit = "soft-serve.service"; }; }; # Service definition in nixpkgs isn't good. systemd.services.soft-serve = let yaml = pkgs.formats.yaml { }; configFile = yaml.generate "soft-serve.yaml" { name = "soft-serve & legit demo"; log_format = "text"; ssh.listen_addr = ":22222"; http.listen_addr = ":8080"; }; in { description = "Git server for the command-line"; after = [ "network.target" ]; restartTriggers = [ configFile softserveAdminKeyEnv ]; wantedBy = lib.mkForce [ ]; environment = { SOFT_SERVE_DATA_PATH = softserveData; SOFT_SERVE_CONFIG_LOCATION = configFile; }; serviceConfig = { User = "soft"; Group = "git"; Type = "simple"; ExecStart = "${lib.getExe pkgs.soft-serve} serve"; EnvironmentFile = [ softserveAdminKeyEnv ]; WorkingDirectory = softserveData; }; }; services.legit = { enable = true; config = { repo.scanPath = "${softserveData}/repos"; }; user = "legit"; group = "git"; }; services.caddy = { enable = true; configFile = pkgs.writeText "Caddyfile" '' { auto_https off } :80 { reverse_proxy :5555 } ''; }; }
-
-
nix/nixos-module.nix (new)
-
@@ -0,0 +1,120 @@# Copyright 2026 Shota FUJI <pockawoooh@gmail.com> # SPDX-License-Identifier: MIT self: { config, lib, pkgs, ... }: let yaml = pkgs.formats.yaml { }; in { disabledModules = [ "services/networking/legit.nix" ]; options = { services.legit = { enable = lib.mkEnableOption "legit"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.system}.legit; }; config = lib.mkOption { type = yaml.type; default = { repo.scanPath = "/var/www/git"; meta = { syntaxHighlight = true; }; footer = { poweredBy = true; }; server = { host = "127.0.0.1"; port = 5555; }; }; description = '' The contents of the configuration file for legit. ''; example = lib.literalExpression '' { repo.scanPath = "/var/www/git"; meta = { title = "My Projects"; description = "My git repos"; syntaxHighlight = true; }; footer = { links = [ { text = "Contact"; href = "mailto:alice@example.com"; } ]; poweredBy = true; }; server = { name = "git.example.com"; host = "127.0.0.1"; port = 5555; }; } ''; }; user = lib.mkOption { type = lib.types.nonEmptyStr; default = "legit"; description = "User account under which legit runs."; }; group = lib.mkOption { type = lib.types.nonEmptyStr; default = "legit"; description = "Group account under which legit runs."; }; }; }; config = let cfg = config.services.legit; configFile = yaml.generate "legit.config.yaml" cfg.config; in lib.mkIf cfg.enable { users.groups = lib.optionalAttrs (cfg.group == "legit") { legit = { }; }; users.users = lib.optionalAttrs (cfg.group == "legit") { legit = { group = "legit"; isSystemUser = true; }; }; systemd.services.legit = { description = "web frontend for git repositories"; after = [ "network.target" ]; restartTriggers = [ configFile ]; serviceConfig = { User = cfg.user; Group = cfg.group; Type = "simple"; ExecStart = "${lib.getExe cfg.package} -config ${configFile}"; Restart = "always"; }; }; systemd.paths.legit-repos = { description = "repositories directory read by legit"; wantedBy = [ "default.target" ]; pathConfig = { PathExists = cfg.config.repo.scanPath; Unit = "legit.service"; }; }; }; }
-