Merge pull request #242467 from dadada/dev/dadada/init-nixos-soft-serve
nixos/soft-serve: init
This commit is contained in:
commit
abca224ce4
6 changed files with 211 additions and 2 deletions
|
@ -113,6 +113,8 @@
|
||||||
|
|
||||||
- [virt-manager](https://virt-manager.org/), an UI for managing virtual machines in libvirt, is now available as `programs.virt-manager`.
|
- [virt-manager](https://virt-manager.org/), an UI for managing virtual machines in libvirt, is now available as `programs.virt-manager`.
|
||||||
|
|
||||||
|
- [Soft Serve](https://github.com/charmbracelet/soft-serve), a tasty, self-hostable Git server for the command line. Available as [services.soft-serve](#opt-services.soft-serve.enable).
|
||||||
|
|
||||||
## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
|
## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
|
||||||
|
|
||||||
- `network-online.target` has been fixed to no longer time out for systems with `networking.useDHCP = true` and `networking.useNetworkd = true`.
|
- `network-online.target` has been fixed to no longer time out for systems with `networking.useDHCP = true` and `networking.useNetworkd = true`.
|
||||||
|
|
|
@ -730,6 +730,7 @@
|
||||||
./services/misc/signald.nix
|
./services/misc/signald.nix
|
||||||
./services/misc/siproxd.nix
|
./services/misc/siproxd.nix
|
||||||
./services/misc/snapper.nix
|
./services/misc/snapper.nix
|
||||||
|
./services/misc/soft-serve.nix
|
||||||
./services/misc/sonarr.nix
|
./services/misc/sonarr.nix
|
||||||
./services/misc/sourcehut
|
./services/misc/sourcehut
|
||||||
./services/misc/spice-vdagentd.nix
|
./services/misc/spice-vdagentd.nix
|
||||||
|
|
99
nixos/modules/services/misc/soft-serve.nix
Normal file
99
nixos/modules/services/misc/soft-serve.nix
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.soft-serve;
|
||||||
|
configFile = format.generate "config.yaml" cfg.settings;
|
||||||
|
format = pkgs.formats.yaml { };
|
||||||
|
docUrl = "https://charm.sh/blog/self-hosted-soft-serve/";
|
||||||
|
stateDir = "/var/lib/soft-serve";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
services.soft-serve = {
|
||||||
|
enable = mkEnableOption "Enable soft-serve service";
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "soft-serve" { };
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = format.type;
|
||||||
|
default = { };
|
||||||
|
description = mdDoc ''
|
||||||
|
The contents of the configuration file.
|
||||||
|
|
||||||
|
See <${docUrl}>.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
name = "dadada's repos";
|
||||||
|
log_format = "text";
|
||||||
|
ssh = {
|
||||||
|
listen_addr = ":23231";
|
||||||
|
public_url = "ssh://localhost:23231";
|
||||||
|
max_timeout = 30;
|
||||||
|
idle_timeout = 120;
|
||||||
|
};
|
||||||
|
stats.listen_addr = ":23233";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
# The config file has to be inside the state dir
|
||||||
|
"L+ ${stateDir}/config.yaml - - - - ${configFile}"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.soft-serve = {
|
||||||
|
description = "Soft Serve git server";
|
||||||
|
documentation = [ docUrl ];
|
||||||
|
requires = [ "network-online.target" ];
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
environment.SOFT_SERVE_DATA_PATH = stateDir;
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
DynamicUser = true;
|
||||||
|
Restart = "always";
|
||||||
|
ExecStart = "${getExe cfg.package} serve";
|
||||||
|
StateDirectory = "soft-serve";
|
||||||
|
WorkingDirectory = stateDir;
|
||||||
|
RuntimeDirectory = "soft-serve";
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
ProcSubset = "pid";
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
UMask = "0027";
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
ProtectHome = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"~@cpu-emulation @debug @keyring @module @mount @obsolete @privileged @raw-io @reboot @setuid @swap"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.maintainers = [ maintainers.dadada ];
|
||||||
|
}
|
|
@ -733,6 +733,7 @@ in {
|
||||||
snapper = handleTest ./snapper.nix {};
|
snapper = handleTest ./snapper.nix {};
|
||||||
snipe-it = runTest ./web-apps/snipe-it.nix;
|
snipe-it = runTest ./web-apps/snipe-it.nix;
|
||||||
soapui = handleTest ./soapui.nix {};
|
soapui = handleTest ./soapui.nix {};
|
||||||
|
soft-serve = handleTest ./soft-serve.nix {};
|
||||||
sogo = handleTest ./sogo.nix {};
|
sogo = handleTest ./sogo.nix {};
|
||||||
solanum = handleTest ./solanum.nix {};
|
solanum = handleTest ./solanum.nix {};
|
||||||
sonarr = handleTest ./sonarr.nix {};
|
sonarr = handleTest ./sonarr.nix {};
|
||||||
|
|
102
nixos/tests/soft-serve.nix
Normal file
102
nixos/tests/soft-serve.nix
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||||
|
sshPort = 8231;
|
||||||
|
httpPort = 8232;
|
||||||
|
statsPort = 8233;
|
||||||
|
gitPort = 8418;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "soft-serve";
|
||||||
|
meta.maintainers = with lib.maintainers; [ dadada ];
|
||||||
|
nodes = {
|
||||||
|
client = { pkgs, ... }: {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
curl
|
||||||
|
git
|
||||||
|
openssh
|
||||||
|
];
|
||||||
|
environment.etc.sshKey = {
|
||||||
|
source = snakeOilPrivateKey;
|
||||||
|
mode = "0600";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
services.soft-serve = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
name = "TestServer";
|
||||||
|
ssh.listen_addr = ":${toString sshPort}";
|
||||||
|
git.listen_addr = ":${toString gitPort}";
|
||||||
|
http.listen_addr = ":${toString httpPort}";
|
||||||
|
stats.listen_addr = ":${toString statsPort}";
|
||||||
|
initial_admin_keys = [ snakeOilPublicKey ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall.allowedTCPPorts = [ sshPort httpPort statsPort ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
{ ... }:
|
||||||
|
''
|
||||||
|
SSH_PORT = ${toString sshPort}
|
||||||
|
HTTP_PORT = ${toString httpPort}
|
||||||
|
STATS_PORT = ${toString statsPort}
|
||||||
|
KEY = "${snakeOilPublicKey}"
|
||||||
|
SSH_KEY = "/etc/sshKey"
|
||||||
|
SSH_COMMAND = f"ssh -p {SSH_PORT} -i {SSH_KEY} -o StrictHostKeyChecking=no"
|
||||||
|
TEST_DIR = "/tmp/test"
|
||||||
|
GIT = f"git -C {TEST_DIR}"
|
||||||
|
|
||||||
|
for machine in client, server:
|
||||||
|
machine.wait_for_unit("network.target")
|
||||||
|
|
||||||
|
server.wait_for_unit("soft-serve.service")
|
||||||
|
server.wait_for_open_port(SSH_PORT)
|
||||||
|
|
||||||
|
with subtest("Get info"):
|
||||||
|
status, test = client.execute(f"{SSH_COMMAND} server info")
|
||||||
|
if status != 0:
|
||||||
|
raise Exception("Failed to get SSH info")
|
||||||
|
key = " ".join(KEY.split(" ")[0:2])
|
||||||
|
if not key in test:
|
||||||
|
raise Exception("Admin key must be configured correctly")
|
||||||
|
|
||||||
|
with subtest("Create user"):
|
||||||
|
client.succeed(f"{SSH_COMMAND} server user create beatrice")
|
||||||
|
client.succeed(f"{SSH_COMMAND} server user info beatrice")
|
||||||
|
|
||||||
|
with subtest("Create repo"):
|
||||||
|
client.succeed(f"git init {TEST_DIR}")
|
||||||
|
client.succeed(f"{GIT} config --global user.email you@example.com")
|
||||||
|
client.succeed(f"touch {TEST_DIR}/foo")
|
||||||
|
client.succeed(f"{GIT} add foo")
|
||||||
|
client.succeed(f"{GIT} commit --allow-empty -m test")
|
||||||
|
client.succeed(f"{GIT} remote add origin git@server:test")
|
||||||
|
client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' {GIT} push -u origin master")
|
||||||
|
client.execute("rm -r /tmp/test")
|
||||||
|
|
||||||
|
server.wait_for_open_port(HTTP_PORT)
|
||||||
|
|
||||||
|
with subtest("Clone over HTTP"):
|
||||||
|
client.succeed(f"curl --connect-timeout 10 http://server:{HTTP_PORT}/")
|
||||||
|
client.succeed(f"git clone http://server:{HTTP_PORT}/test /tmp/test")
|
||||||
|
client.execute("rm -r /tmp/test")
|
||||||
|
|
||||||
|
with subtest("Clone over SSH"):
|
||||||
|
client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' git clone git@server:test /tmp/test")
|
||||||
|
client.execute("rm -r /tmp/test")
|
||||||
|
|
||||||
|
with subtest("Get stats over HTTP"):
|
||||||
|
server.wait_for_open_port(STATS_PORT)
|
||||||
|
status, test = client.execute(f"curl --connect-timeout 10 http://server:{STATS_PORT}/metrics")
|
||||||
|
if status != 0:
|
||||||
|
raise Exception("Failed to get metrics from status port")
|
||||||
|
if not "go_gc_duration_seconds_count" in test:
|
||||||
|
raise Exception("Metrics did not contain key 'go_gc_duration_seconds_count'")
|
||||||
|
'';
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
{ lib, buildGoModule, fetchFromGitHub, makeWrapper, git }:
|
{ lib, buildGoModule, fetchFromGitHub, makeWrapper, nixosTests, git, bash }:
|
||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "soft-serve";
|
pname = "soft-serve";
|
||||||
|
@ -20,10 +20,14 @@ buildGoModule rec {
|
||||||
nativeBuildInputs = [ makeWrapper ];
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
|
# Soft-serve generates git-hooks at run-time.
|
||||||
|
# The scripts require git and bash inside the path.
|
||||||
wrapProgram $out/bin/soft \
|
wrapProgram $out/bin/soft \
|
||||||
--prefix PATH : "${lib.makeBinPath [ git ]}"
|
--prefix PATH : "${lib.makeBinPath [ git bash ]}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
passthru.tests = nixosTests.soft-serve;
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
description = "A tasty, self-hosted Git server for the command line";
|
description = "A tasty, self-hosted Git server for the command line";
|
||||||
homepage = "https://github.com/charmbracelet/soft-serve";
|
homepage = "https://github.com/charmbracelet/soft-serve";
|
||||||
|
|
Loading…
Reference in a new issue