From feed949d47ada12077a1a9eca3f83f5c8138487b Mon Sep 17 00:00:00 2001 From: makefu Date: Thu, 23 Jul 2015 17:54:31 +0200 Subject: add pnp --- 0make/makefu/pnp.makefile | 4 ++++ 1systems/makefu/pnp.nix | 0 2 files changed, 4 insertions(+) create mode 100644 0make/makefu/pnp.makefile create mode 100644 1systems/makefu/pnp.nix diff --git a/0make/makefu/pnp.makefile b/0make/makefu/pnp.makefile new file mode 100644 index 00000000..c2099f74 --- /dev/null +++ b/0make/makefu/pnp.makefile @@ -0,0 +1,4 @@ +deploy_host := root@uriel +nixpkgs_url := https://github.com/nixos/nixpkgs +nixpkgs_rev := 961fcbabd7643171ea74bd550fee1ce5c13c2e90 +secrets_dir := /home/makefu/secrets/pnp diff --git a/1systems/makefu/pnp.nix b/1systems/makefu/pnp.nix new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From f076edf90a47117dcf4029e6458b59b7f318fa8e Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 23 Jul 2015 18:20:41 +0200 Subject: 2 tv git-public: s/shitment/stockholm/ --- 2configs/tv/git-public.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/2configs/tv/git-public.nix b/2configs/tv/git-public.nix index aef83667..7babcbb5 100644 --- a/2configs/tv/git-public.nix +++ b/2configs/tv/git-public.nix @@ -22,8 +22,8 @@ let (public "quipper") (public "regfish") (public' { - name = "shitment"; - desc = "turn all the computers into one computer!"; + name = "stockholm"; + desc = "take all the computers hostage, they'll love you!"; }) (public "wai-middleware-time") (public "web-routes-wai-custom") -- cgit v1.2.3 From b2f4cc4b124f7fa67203e0b46cc308eab6813f5e Mon Sep 17 00:00:00 2001 From: makefu Date: Thu, 23 Jul 2015 19:07:27 +0200 Subject: 3 tv.retiolum: describe connectTo --- 3modules/tv/retiolum.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/3modules/tv/retiolum.nix b/3modules/tv/retiolum.nix index ca1418c3..e8a1f6c2 100644 --- a/3modules/tv/retiolum.nix +++ b/3modules/tv/retiolum.nix @@ -82,7 +82,11 @@ let connectTo = mkOption { type = types.listOf types.str; default = [ "fastpoke" "pigstarter" "kheurop" ]; - description = "TODO describe me"; + description = '' + The list of hosts in the network which the client will try to connect + to. These hosts should have an 'Address' configured which points to a + routeable IPv4 or IPv6 address. + ''; }; }; -- cgit v1.2.3 From 461bacfd6a63ee1d4c12805724c408fb21ff9b7d Mon Sep 17 00:00:00 2001 From: makefu Date: Thu, 23 Jul 2015 19:07:05 +0200 Subject: tv/retiolum.nix: add doc for ConnectTo --- 3modules/tv/retiolum.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/3modules/tv/retiolum.nix b/3modules/tv/retiolum.nix index ca1418c3..7a5ab70d 100644 --- a/3modules/tv/retiolum.nix +++ b/3modules/tv/retiolum.nix @@ -82,7 +82,12 @@ let connectTo = mkOption { type = types.listOf types.str; default = [ "fastpoke" "pigstarter" "kheurop" ]; - description = "TODO describe me"; + description = '' + The list of hosts in the network which the client will try to connect to. + These hosts should have an 'Address' configured which points to a routeable + IPv4 or IPv6 address + '' + ; }; }; -- cgit v1.2.3 From 9be3c091d31d3ffc1d18a67bbf2523e4afc796cd Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 24 Jul 2015 00:24:12 +0200 Subject: 2 tv git-public: simplify --- 2configs/tv/git-public.nix | 141 +++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 74 deletions(-) diff --git a/2configs/tv/git-public.nix b/2configs/tv/git-public.nix index 7babcbb5..7222f99e 100644 --- a/2configs/tv/git-public.nix +++ b/2configs/tv/git-public.nix @@ -1,87 +1,80 @@ { config, lib, pkgs, ... }: - -with lib; +with import ../../4lib/tv { inherit lib pkgs; }; let - inherit (builtins) map readFile; - inherit (lib) concatMap listToAttrs; - # TODO lib should already include our stuff - inherit (import ../../4lib/tv { inherit lib pkgs; }) addNames git; - - public-git-repos = [ - (public "cgserver") - (public "crude-mail-setup") - (public "dot-xmonad") - (public "hack") - (public "load-env") - (public "make-snapshot") - (public "mime") - (public "much") - (public "nixos-infest") - (public "nixpkgs") - (public "painload") - (public "quipper") - (public "regfish") - (public' { - name = "stockholm"; - desc = "take all the computers hostage, they'll love you!"; - }) - (public "wai-middleware-time") - (public "web-routes-wai-custom") - (public "xintmap") - ]; - users = addNames { - tv = { pubkey = readFile ../../Zpubkeys/tv_wu.ssh.pub; }; - lass = { pubkey = readFile ../../Zpubkeys/lass.ssh.pub; }; - uriel = { pubkey = readFile ../../Zpubkeys/uriel.ssh.pub; }; - makefu = { pubkey = readFile ../../Zpubkeys/makefu.ssh.pub; }; + out = { + imports = [ ../../3modules/tv/git.nix ]; + tv.git = { + enable = true; + root-title = "public repositories at ${config.tv.identity.self.name}"; + root-desc = "keep calm and engage"; + inherit repos rules users; + }; }; - repos = listToAttrs (map ({ repo, ... }: { name = repo.name; value = repo; }) public-git-repos); + repos = public-repos; + rules = concatMap make-rules (attrValues repos); - rules = concatMap ({ rules, ... }: rules) public-git-repos; + public-repos = mapAttrs make-public-repo { + cgserver = {}; + crude-mail-setup = {}; + dot-xmonad = {}; + hack = {}; + load-env = {}; + make-snapshot = {}; + mime = {}; + much = {}; + nixos-infest = {}; + nixpkgs = {}; + painload = {}; + quipper = {}; + regfish = {}; + stockholm = { + desc = "take all the computers hostage, they'll love you!"; + }; + wai-middleware-time = {}; + web-routes-wai-custom = {}; + xintmap = {}; + }; - public' = { name, desc }: - let - x = public name; - in - x // { repo = x.repo // { inherit desc; }; }; + # TODO move users to separate module + users = mapAttrs make-user { + tv = ../../Zpubkeys/tv_wu.ssh.pub; + lass = ../../Zpubkeys/lass.ssh.pub; + uriel = ../../Zpubkeys/uriel.ssh.pub; + makefu = ../../Zpubkeys/makefu.ssh.pub; + }; - public = repo-name: - rec { - repo = { - name = repo-name; - hooks = { - post-receive = git.irc-announce { - nick = config.networking.hostName; # TODO make this the default - channel = "#retiolum"; - server = "cd.retiolum"; - }; - }; - public = true; + make-public-repo = name: { desc ? null, ... }: { + inherit name desc; + public = true; + hooks = { + post-receive = git.irc-announce { + # TODO make nick = config.tv.identity.self.name the default + nick = config.tv.identity.self.name; + channel = "#retiolum"; + server = "cd.retiolum"; }; - rules = with git; with users; [ - { user = tv; - repo = [ repo ]; - perm = push "refs/*" [ non-fast-forward create delete merge ]; - } - { user = [ lass makefu uriel ]; - repo = [ repo ]; - perm = fetch; - } - ]; }; + }; -in + make-rules = + with git // users; + repo: + singleton { + user = tv; + repo = [ repo ]; + perm = push "refs/*" [ non-fast-forward create delete merge ]; + } ++ + optional repo.public { + user = [ lass makefu uriel ]; + repo = [ repo ]; + perm = fetch; + }; -{ - imports = [ - ../../3modules/tv/git.nix - ]; - tv.git = { - enable = true; - inherit repos rules users; - root-title = "public repositories at ${config.networking.hostName}"; - root-desc = "keep calm and engage"; + make-user = name: pubkey-file: { + inherit name; + pubkey = readFile pubkey-file; }; -} + +in out -- cgit v1.2.3 From ea498c46bc98771ab0e66eec21612cbf78b3ef01 Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 24 Jul 2015 09:38:59 +0200 Subject: tv urlwatch: simple-evcorr has moved to GitHub --- 1systems/tv/wu.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1systems/tv/wu.nix b/1systems/tv/wu.nix index 400005cb..76bb43ec 100644 --- a/1systems/tv/wu.nix +++ b/1systems/tv/wu.nix @@ -168,7 +168,7 @@ in # then we have to update the package # ref src/nixpkgs/pkgs/tools/admin/sec/default.nix - http://simple-evcorr.sourceforge.net/ + https://api.github.com/repos/simple-evcorr/sec/tags # ref src/nixpkgs/pkgs/tools/networking/urlwatch/default.nix https://thp.io/2008/urlwatch/ -- cgit v1.2.3 From 2b3030c7b27f98b8f00d91c63bd60c980e64071b Mon Sep 17 00:00:00 2001 From: makefu Date: Fri, 24 Jul 2015 10:52:43 +0200 Subject: makefu: init pnp this is the first entry for my hosts, it provides only very basic support with a lot of copy-paste from tv/lass --- 0make/makefu/pnp.makefile | 4 +- 1systems/makefu/pnp.nix | 38 ++++++++++++++++++ 2configs/makefu/base.nix | 96 ++++++++++++++++++++++++++++++++++++++++++++ Zpubkeys/makefu.ssh.pub | 1 - Zpubkeys/makefu_arch.ssh.pub | 1 + 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 2configs/makefu/base.nix delete mode 100644 Zpubkeys/makefu.ssh.pub create mode 100644 Zpubkeys/makefu_arch.ssh.pub diff --git a/0make/makefu/pnp.makefile b/0make/makefu/pnp.makefile index c2099f74..a18efe0e 100644 --- a/0make/makefu/pnp.makefile +++ b/0make/makefu/pnp.makefile @@ -1,4 +1,4 @@ -deploy_host := root@uriel +deploy_host := root@pnp nixpkgs_url := https://github.com/nixos/nixpkgs -nixpkgs_rev := 961fcbabd7643171ea74bd550fee1ce5c13c2e90 +nixpkgs_rev := 4c01e6d91993b6de128795f4fbdd25f6227fb870 secrets_dir := /home/makefu/secrets/pnp diff --git a/1systems/makefu/pnp.nix b/1systems/makefu/pnp.nix index e69de29b..51f5bb00 100644 --- a/1systems/makefu/pnp.nix +++ b/1systems/makefu/pnp.nix @@ -0,0 +1,38 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, ... }: + +{ + imports = + [ # Include the results of the hardware scan. + + ../../2configs/makefu/base.nix + ]; + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/vda"; + + boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "ehci_pci" "virtio_pci" "virtio_blk" ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + hardware.enableAllFirmware = true; + hardware.cpu.amd.updateMicrocode = true; + + fileSystems."/" = + { device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + + nix.maxJobs = 1; + networking.hostName = "pnp"; # Define your hostname. + +# $ nix-env -qaP | grep wget + environment.systemPackages = with pkgs; [ + wget + git + gnumake + ]; + +} diff --git a/2configs/makefu/base.nix b/2configs/makefu/base.nix new file mode 100644 index 00000000..ab2e6f24 --- /dev/null +++ b/2configs/makefu/base.nix @@ -0,0 +1,96 @@ +{ config, lib, pkgs, ... }: + +with lib; +{ + imports = [ ]; + users.extraUsers = { + root = { + openssh.authorizedKeys.keys = map readFile [ + ../../Zpubkeys/makefu_arch.ssh.pub + ]; + }; + makefu = { + uid = 9001; + group = "users"; + home = "/home/makefu"; + createHome = true; + useDefaultShell = true; + extraGroups = [ + "wheel" + ]; + openssh.authorizedKeys.keys = map readFile [ + ../../Zpubkeys/makefu_arch.ssh.pub + ]; + }; + }; + + services.openssh.enable = true; + nix.useChroot = true; + + users.mutableUsers = true; + + boot.tmpOnTmpfs = true; + systemd.tmpfiles.rules = [ + "d /tmp 1777 root root - -" + ]; + + environment.extraInit = '' + EDITOR=vim + ''; + + environment.systemPackages = with pkgs; [ + git + vim + rxvt_unicode.terminfo + ]; + + programs.bash = { + enableCompletion = true; + interactiveShellInit = '' + HISTCONTROL='erasedups:ignorespace' + HISTSIZE=900001 + HISTFILESIZE=$HISTSIZE + + shopt -s checkhash + shopt -s histappend histreedit histverify + shopt -s no_empty_cmd_completion + complete -d cd + + ''; + promptInit = '' + case $UID in + 0) PS1='\[\e[1;31m\]\w\[\e[0m\] ' ;; + 9001) PS1='\[\e[1;32m\]\w\[\e[0m\] ' ;; + *) PS1='\[\e[1;35m\]\u \[\e[1;32m\]\w\[\e[0m\] ' ;; + esac + if test -n "$SSH_CLIENT"; then + PS1='\[\033[35m\]\h'" $PS1" + fi + ''; + }; + environment.shellAliases = { + lsl = "ls -latr"; + }; + + security.setuidPrograms = [ "sendmail" ]; + + services.journald.extraConfig = '' + SystemMaxUse=1G + RuntimeMaxUse=128M + ''; + nixpkgs.config.packageOverrides = pkgs: { + nano = pkgs.runCommand "empty" {} "mkdir -p $out"; + }; + services.cron.enable = false; + services.nscd.enable = false; + boot.kernel.sysctl = { +# Enable IPv6 Privacy Extensions + "net.ipv6.conf.all.use_tempaddr" = 2; + "net.ipv6.conf.default.use_tempaddr" = 2; + }; + i18n = { + consoleKeyMap = "us"; + defaultLocale = "en_US.UTF-8"; + }; + +} diff --git a/Zpubkeys/makefu.ssh.pub b/Zpubkeys/makefu.ssh.pub deleted file mode 100644 index 6092ec46..00000000 --- a/Zpubkeys/makefu.ssh.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl3RTOHd5DLiVeUbUr/GSiKoRWknXQnbkIf+uNiFO+XxiqZVojPlumQUVhasY8UzDzj9tSDruUKXpjut50FhIO5UFAgsBeMJyoZbgY/+R+QKU00Q19+IiUtxeFol/9dCO+F4o937MC0OpAC10LbOXN/9SYIXueYk3pJxIycXwUqhYmyEqtDdVh9Rx32LBVqlBoXRHpNGPLiswV2qNe0b5p919IGcslzf1XoUzfE3a3yjk/XbWh/59xnl4V7Oe7+iQheFxOT6rFA30WYwEygs5As//ZYtxvnn0gA02gOnXJsNjOW9irlxOUeP7IOU6Ye3WRKFRR0+7PS+w8IJLag2xb makefu@pornocauster diff --git a/Zpubkeys/makefu_arch.ssh.pub b/Zpubkeys/makefu_arch.ssh.pub new file mode 100644 index 00000000..6092ec46 --- /dev/null +++ b/Zpubkeys/makefu_arch.ssh.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl3RTOHd5DLiVeUbUr/GSiKoRWknXQnbkIf+uNiFO+XxiqZVojPlumQUVhasY8UzDzj9tSDruUKXpjut50FhIO5UFAgsBeMJyoZbgY/+R+QKU00Q19+IiUtxeFol/9dCO+F4o937MC0OpAC10LbOXN/9SYIXueYk3pJxIycXwUqhYmyEqtDdVh9Rx32LBVqlBoXRHpNGPLiswV2qNe0b5p919IGcslzf1XoUzfE3a3yjk/XbWh/59xnl4V7Oe7+iQheFxOT6rFA30WYwEygs5As//ZYtxvnn0gA02gOnXJsNjOW9irlxOUeP7IOU6Ye3WRKFRR0+7PS+w8IJLag2xb makefu@pornocauster -- cgit v1.2.3 From 7846e26f8660b58d67eb90a21e7249715f49ac89 Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 24 Jul 2015 11:22:21 +0200 Subject: 3: {tv -> krebs}.retiolum --- 3modules/krebs/retiolum.nix | 226 ++++++++++++++++++++++++++++++++++++++++++++ 3modules/tv/retiolum.nix | 211 ++--------------------------------------- 2 files changed, 233 insertions(+), 204 deletions(-) create mode 100644 3modules/krebs/retiolum.nix diff --git a/3modules/krebs/retiolum.nix b/3modules/krebs/retiolum.nix new file mode 100644 index 00000000..447592ee --- /dev/null +++ b/3modules/krebs/retiolum.nix @@ -0,0 +1,226 @@ +{ config, pkgs, lib, ... }: + +with builtins; +with lib; +let + cfg = config.krebs.retiolum; + + out = { + options.krebs.retiolum = api; + config = mkIf cfg.enable imp; + }; + + api = { + enable = mkEnableOption "krebs.retiolum"; + + name = mkOption { + type = types.str; + default = config.networking.hostName; + # Description stolen from tinc.conf(5). + description = '' + This is the name which identifies this tinc daemon. It must + be unique for the virtual private network this daemon will + connect to. The Name may only consist of alphanumeric and + underscore characters. If Name starts with a $, then the + contents of the environment variable that follows will be + used. In that case, invalid characters will be converted to + underscores. If Name is $HOST, but no such environment + variable exist, the hostname will be read using the + gethostnname() system call This is the name which identifies + the this tinc daemon. + ''; + }; + + generateEtcHosts = mkOption { + type = types.str; + default = "both"; + description = '' + If set to short, long, or both, + then generate entries in /etc/hosts from subnets. + ''; + }; + + network = mkOption { + type = types.str; + default = "retiolum"; + description = '' + The tinc network name. + It is used to generate long host entries, + and name the TUN device. + ''; + }; + + tincPackage = mkOption { + type = types.package; + default = pkgs.tinc; + description = "Tincd package to use."; + }; + + hosts = mkOption { + default = null; + description = '' + Hosts package or path to use. + If a path is given, then it will be used to generate an ad-hoc package. + ''; + }; + + iproutePackage = mkOption { + type = types.package; + default = pkgs.iproute; + description = "Iproute2 package to use."; + }; + + + privateKeyFile = mkOption { + # TODO if it's types.path then it gets copied to /nix/store with + # bad unsafe permissions... + type = types.str; + default = "/root/src/secrets/retiolum.rsa_key.priv"; + description = "Generate file with tincd -K."; + }; + + connectTo = mkOption { + type = types.listOf types.str; + default = [ "fastpoke" "pigstarter" "kheurop" ]; + description = '' + The list of hosts in the network which the client will try to connect + to. These hosts should have an 'Address' configured which points to a + routeable IPv4 or IPv6 address. + ''; + }; + + }; + + imp = { + environment.systemPackages = [ tinc hosts iproute ]; + + networking.extraHosts = retiolumExtraHosts; + + systemd.services.retiolum = { + description = "Tinc daemon for Retiolum"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ tinc iproute ]; + serviceConfig = { + PermissionsStartOnly = "true"; + PrivateTmp = "true"; + Restart = "always"; + # TODO we cannot chroot (-R) b/c we use symlinks to hosts + # and the private key. + ExecStartPre = pkgs.writeScript "retiolum-init" '' + #! /bin/sh + install -o ${user.name} -m 0400 ${cfg.privateKeyFile} /tmp/retiolum-rsa_key.priv + ''; + ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user.name} -D"; + SyslogIdentifier = "retiolum"; + }; + }; + + users.extraUsers = singleton { + inherit (user) name uid; + }; + }; + + user = { + name = "retiolum"; + uid = 301281149; # genid retiolum + }; + + tinc = cfg.tincPackage; + hostsType = builtins.typeOf cfg.hosts; + hosts = + if hostsType == "package" then + # use package as is + cfg.hosts + else if hostsType == "path" then + # use path to generate a package + pkgs.stdenv.mkDerivation { + name = "custom-retiolum-hosts"; + src = cfg.hosts; + installPhase = '' + mkdir $out + find . -name .git -prune -o -type f -print0 | xargs -0 cp --target-directory $out + ''; + } + else + abort "The option `services.retiolum.hosts' must be set to a package or a path" + ; + iproute = cfg.iproutePackage; + + retiolumExtraHosts = import (pkgs.runCommand "retiolum-etc-hosts" + { } + '' + generate() { + (cd ${hosts} + printf \'\' + for i in `ls`; do + names=$(hostnames $i) + for j in `sed -En 's|^ *Aliases *= *(.+)|\1|p' $i`; do + names="$names $(hostnames $j)" + done + sed -En ' + s|^ *Subnet *= *([^ /]*)(/[0-9]*)? *$|\1 '"$names"'|p + ' $i + done | sort + printf \'\' + ) + } + + case ${cfg.generateEtcHosts} in + short) + hostnames() { echo "$1"; } + generate + ;; + long) + hostnames() { echo "$1.${cfg.network}"; } + generate + ;; + both) + hostnames() { echo "$1.${cfg.network} $1"; } + generate + ;; + *) + echo '""' + ;; + esac > $out + ''); + + + confDir = pkgs.runCommand "retiolum" { + # TODO text + executable = true; + preferLocalBuild = true; + } '' + set -euf + + mkdir -p $out + + ln -s ${hosts} $out/hosts + + cat > $out/tinc.conf < $out/tinc-up <short, long, or both, - then generate entries in /etc/hosts from subnets. - ''; - }; - - network = mkOption { - type = types.str; - default = "retiolum"; - description = '' - The tinc network name. - It is used to generate long host entries, - and name the TUN device. - ''; - }; - - tincPackage = mkOption { - type = types.package; - default = pkgs.tinc; - description = "Tincd package to use."; + connectTo = mkOption { + type = with types; listOf str; }; hosts = mkOption { - default = null; - description = '' - Hosts package or path to use. - If a path is given, then it will be used to generate an ad-hoc package. - ''; - }; - - iproutePackage = mkOption { - type = types.package; - default = pkgs.iproute; - description = "Iproute2 package to use."; + type = types.path; }; - - - privateKeyFile = mkOption { - # TODO if it's types.path then it gets copied to /nix/store with - # bad unsafe permissions... - type = types.str; - default = "/root/src/secrets/retiolum.rsa_key.priv"; - description = "Generate file with tincd -K."; - }; - - connectTo = mkOption { - type = types.listOf types.str; - default = [ "fastpoke" "pigstarter" "kheurop" ]; - description = '' - The list of hosts in the network which the client will try to connect - to. These hosts should have an 'Address' configured which points to a - routeable IPv4 or IPv6 address. - ''; - }; - }; imp = { - environment.systemPackages = [ tinc hosts iproute ]; - - networking.extraHosts = retiolumExtraHosts; - - systemd.services.retiolum = { - description = "Tinc daemon for Retiolum"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - path = [ tinc iproute ]; - serviceConfig = { - PermissionsStartOnly = "true"; - PrivateTmp = "true"; - Restart = "always"; - # TODO we cannot chroot (-R) b/c we use symlinks to hosts - # and the private key. - ExecStartPre = pkgs.writeScript "retiolum-init" '' - #! /bin/sh - install -o ${user.name} -m 0400 ${cfg.privateKeyFile} /tmp/retiolum-rsa_key.priv - ''; - ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user.name} -D"; - SyslogIdentifier = "retiolum"; - }; - }; - - users.extraUsers = singleton { - inherit (user) name uid; - }; + krebs.retiolum = cfg; }; - user = { - name = "retiolum"; - uid = 301281149; # genid retiolum - }; - - tinc = cfg.tincPackage; - hostsType = builtins.typeOf cfg.hosts; - hosts = - if hostsType == "package" then - # use package as is - cfg.hosts - else if hostsType == "path" then - # use path to generate a package - pkgs.stdenv.mkDerivation { - name = "custom-retiolum-hosts"; - src = cfg.hosts; - installPhase = '' - mkdir $out - find . -name .git -prune -o -type f -print0 | xargs -0 cp --target-directory $out - ''; - } - else - abort "The option `services.retiolum.hosts' must be set to a package or a path" - ; - iproute = cfg.iproutePackage; - - retiolumExtraHosts = import (pkgs.runCommand "retiolum-etc-hosts" - { } - '' - generate() { - (cd ${hosts} - printf \'\' - for i in `ls`; do - names=$(hostnames $i) - for j in `sed -En 's|^ *Aliases *= *(.+)|\1|p' $i`; do - names="$names $(hostnames $j)" - done - sed -En ' - s|^ *Subnet *= *([^ /]*)(/[0-9]*)? *$|\1 '"$names"'|p - ' $i - done | sort - printf \'\' - ) - } - - case ${cfg.generateEtcHosts} in - short) - hostnames() { echo "$1"; } - generate - ;; - long) - hostnames() { echo "$1.${cfg.network}"; } - generate - ;; - both) - hostnames() { echo "$1.${cfg.network} $1"; } - generate - ;; - *) - echo '""' - ;; - esac > $out - ''); - - - confDir = pkgs.runCommand "retiolum" { - # TODO text - executable = true; - preferLocalBuild = true; - } '' - set -euf - - mkdir -p $out - - ln -s ${hosts} $out/hosts - - cat > $out/tinc.conf < $out/tinc-up < Date: Fri, 24 Jul 2015 11:35:24 +0200 Subject: remove kheurop from defaults (dead node) --- 3modules/tv/retiolum.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3modules/tv/retiolum.nix b/3modules/tv/retiolum.nix index e8a1f6c2..adc056a9 100644 --- a/3modules/tv/retiolum.nix +++ b/3modules/tv/retiolum.nix @@ -81,7 +81,7 @@ let connectTo = mkOption { type = types.listOf types.str; - default = [ "fastpoke" "pigstarter" "kheurop" ]; + default = [ "fastpoke" "pigstarter" "gum" ]; description = '' The list of hosts in the network which the client will try to connect to. These hosts should have an 'Address' configured which points to a -- cgit v1.2.3 From 734ec4ae00c93d48297b7c3ee226ef890187bfa3 Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 24 Jul 2015 11:50:23 +0200 Subject: 3 {tv -> krebs}.nginx --- 1systems/tv/cd.nix | 10 +++---- 1systems/tv/nomic.nix | 4 +-- 1systems/tv/wu.nix | 4 +-- 3modules/krebs/nginx.nix | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 3modules/tv/git.nix | 5 ++-- 3modules/tv/nginx.nix | 71 ----------------------------------------------- 6 files changed, 84 insertions(+), 82 deletions(-) create mode 100644 3modules/krebs/nginx.nix delete mode 100644 3modules/tv/nginx.nix diff --git a/1systems/tv/cd.nix b/1systems/tv/cd.nix index d3cae6f4..407fc25c 100644 --- a/1systems/tv/cd.nix +++ b/1systems/tv/cd.nix @@ -57,19 +57,19 @@ in { imports = [ ../../3modules/tv/iptables.nix - ../../3modules/tv/nginx.nix + ../../3modules/krebs/nginx.nix ]; tv.iptables.input-internet-accept-new-tcp = singleton "http"; - tv.nginx.servers.cgit.server-names = singleton "cgit.cd.viljetic.de"; + krebs.nginx.servers.cgit.server-names = singleton "cgit.cd.viljetic.de"; } { # TODO make public_html also available to cd, cd.retiolum (AKA default) imports = [ ../../3modules/tv/iptables.nix - ../../3modules/tv/nginx.nix + ../../3modules/krebs/nginx.nix ]; tv.iptables.input-internet-accept-new-tcp = singleton "http"; - tv.nginx.servers.public_html = { + krebs.nginx.servers.public_html = { server-names = singleton "cd.viljetic.de"; locations = singleton (nameValuePair "~ ^/~(.+?)(/.*)?\$" '' alias /home/$1/public_html$2; @@ -77,7 +77,7 @@ in }; } { - tv.nginx.servers.viljetic = { + krebs.nginx.servers.viljetic = { server-names = singleton "viljetic.de"; # TODO directly set root (instead via location) locations = singleton (nameValuePair "/" '' diff --git a/1systems/tv/nomic.nix b/1systems/tv/nomic.nix index 6f984c44..8e6812e4 100644 --- a/1systems/tv/nomic.nix +++ b/1systems/tv/nomic.nix @@ -26,8 +26,8 @@ with lib; }; } { - imports = [ ../../3modules/tv/nginx.nix ]; - tv.nginx = { + imports = [ ../../3modules/krebs/nginx.nix ]; + krebs.nginx = { enable = true; servers.default.locations = [ (nameValuePair "~ ^/~(.+?)(/.*)?\$" '' diff --git a/1systems/tv/wu.nix b/1systems/tv/wu.nix index 76bb43ec..7a12bc57 100644 --- a/1systems/tv/wu.nix +++ b/1systems/tv/wu.nix @@ -134,8 +134,8 @@ in }; } { - imports = [ ../../3modules/tv/nginx.nix ]; - tv.nginx = { + imports = [ ../../3modules/krebs/nginx.nix ]; + krebs.nginx = { enable = true; servers.default.locations = [ (nameValuePair "~ ^/~(.+?)(/.*)?\$" '' diff --git a/3modules/krebs/nginx.nix b/3modules/krebs/nginx.nix new file mode 100644 index 00000000..702e8a7f --- /dev/null +++ b/3modules/krebs/nginx.nix @@ -0,0 +1,72 @@ +{ config, pkgs, lib, ... }: + +with builtins; +with lib; +let + cfg = config.krebs.nginx; + + out = { + options.krebs.nginx = api; + config = mkIf cfg.enable imp; + }; + + api = { + enable = mkEnableOption "krebs.nginx"; + + servers = mkOption { + type = with types; attrsOf optionSet; + options = singleton { + server-names = mkOption { + type = with types; listOf str; + # TODO use identity + default = [ + "${config.networking.hostName}" + "${config.networking.hostName}.retiolum" + ]; + }; + locations = mkOption { + type = with types; listOf (attrsOf str); + }; + }; + default = {}; + }; + }; + + imp = { + services.nginx = { + enable = true; + httpConfig = '' + include ${pkgs.nginx}/conf/mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + gzip on; + server { + listen 80 default_server; + server_name _; + return 404; + } + ${concatStrings (mapAttrsToList (_: to-server) cfg.servers)} + ''; + }; + }; + + + indent = replaceChars ["\n"] ["\n "]; + + to-location = { name, value }: '' + location ${name} { + ${indent value} + } + ''; + + to-server = { server-names, locations, ... }: '' + server { + listen 80; + server_name ${toString server-names}; + ${indent (concatStrings (map to-location locations))} + } + ''; + +in +out diff --git a/3modules/tv/git.nix b/3modules/tv/git.nix index 8c73d035..ea014e2a 100644 --- a/3modules/tv/git.nix +++ b/3modules/tv/git.nix @@ -12,8 +12,9 @@ let cfg = config.tv.git; out = { + # TODO don't import krebs.nginx here imports = [ - ../../3modules/tv/nginx.nix + ../../3modules/krebs/nginx.nix ]; options.tv.git = api; config = mkIf cfg.enable (mkMerge [ @@ -210,7 +211,7 @@ let chown ${toString fcgitwrap-user.uid}:${toString fcgitwrap-group.gid} /tmp/cgit ''; - tv.nginx = { + krebs.nginx = { enable = true; servers.cgit = { server-names = [ diff --git a/3modules/tv/nginx.nix b/3modules/tv/nginx.nix deleted file mode 100644 index a58c4952..00000000 --- a/3modules/tv/nginx.nix +++ /dev/null @@ -1,71 +0,0 @@ -{ config, pkgs, lib, ... }: - -with builtins; -with lib; -let - cfg = config.tv.nginx; - - out = { - options.tv.nginx = api; - config = mkIf cfg.enable imp; - }; - - api = { - enable = mkEnableOption "tv.nginx"; - - servers = mkOption { - type = with types; attrsOf optionSet; - options = singleton { - server-names = mkOption { - type = with types; listOf str; - default = [ - "${config.networking.hostName}" - "${config.networking.hostName}.retiolum" - ]; - }; - locations = mkOption { - type = with types; listOf (attrsOf str); - }; - }; - default = {}; - }; - }; - - imp = { - services.nginx = { - enable = true; - httpConfig = '' - include ${pkgs.nginx}/conf/mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - gzip on; - server { - listen 80 default_server; - server_name _; - return 404; - } - ${concatStrings (mapAttrsToList (_: to-server) cfg.servers)} - ''; - }; - }; - - - indent = replaceChars ["\n"] ["\n "]; - - to-location = { name, value }: '' - location ${name} { - ${indent value} - } - ''; - - to-server = { server-names, locations, ... }: '' - server { - listen 80; - server_name ${toString server-names}; - ${indent (concatStrings (map to-location locations))} - } - ''; - -in -out -- cgit v1.2.3 From 5f63c4071c7b1680e75671c0acede8a9bce4b14c Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 24 Jul 2015 11:44:49 +0200 Subject: 3: {tv -> krebs}.git --- 2configs/tv/git-public.nix | 4 +- 3modules/krebs/git.nix | 484 +++++++++++++++++++++++++++++++++++++++++++++ 3modules/tv/git.nix | 484 --------------------------------------------- 3 files changed, 486 insertions(+), 486 deletions(-) create mode 100644 3modules/krebs/git.nix delete mode 100644 3modules/tv/git.nix diff --git a/2configs/tv/git-public.nix b/2configs/tv/git-public.nix index 7222f99e..7f2b5130 100644 --- a/2configs/tv/git-public.nix +++ b/2configs/tv/git-public.nix @@ -3,8 +3,8 @@ with import ../../4lib/tv { inherit lib pkgs; }; let out = { - imports = [ ../../3modules/tv/git.nix ]; - tv.git = { + imports = [ ../../3modules/krebs/git.nix ]; + krebs.git = { enable = true; root-title = "public repositories at ${config.tv.identity.self.name}"; root-desc = "keep calm and engage"; diff --git a/3modules/krebs/git.nix b/3modules/krebs/git.nix new file mode 100644 index 00000000..3c3e9342 --- /dev/null +++ b/3modules/krebs/git.nix @@ -0,0 +1,484 @@ +{ config, pkgs, lib, ... }: + +# TODO unify logging of shell scripts to user and journal +# TODO move all scripts to ${etcDir}, so ControlMaster connections +# immediately pick up new authenticators +# TODO when authorized_keys changes, then restart ssh +# (or kill already connected users somehow) + +with builtins; +with lib; +let + cfg = config.krebs.git; + + out = { + # TODO don't import krebs.nginx here + imports = [ + ../../3modules/krebs/nginx.nix + ]; + options.krebs.git = api; + config = mkIf cfg.enable (mkMerge [ + (mkIf cfg.cgit cgit-imp) + git-imp + ]); + }; + + api = { + enable = mkEnableOption "krebs.git"; + + cgit = mkOption { + type = types.bool; + default = true; + description = "Enable cgit."; # TODO better desc; talk about nginx + }; + dataDir = mkOption { + type = types.str; + default = "/var/lib/git"; + description = "Directory used to store repositories."; + }; + etcDir = mkOption { + type = types.str; + default = "/etc/git"; + }; + repos = mkOption { + type = types.attrsOf (types.submodule ({ + options = { + desc = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Repository description. + ''; + }; + section = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Repository section. + ''; + }; + name = mkOption { + type = types.str; + description = '' + Repository name. + ''; + }; + hooks = mkOption { + type = types.attrsOf types.str; + description = '' + Repository-specific hooks. + ''; + }; + public = mkOption { + type = types.bool; + default = false; + description = '' + Allow everybody to read the repository via HTTP if cgit enabled. + ''; + # TODO allow every configured user to fetch the repository via SSH. + }; + }; + })); + + default = {}; + + example = literalExample '' + { + testing = { + name = "testing"; + hooks.post-update = ''' + #! /bin/sh + set -euf + echo post-update hook: $* >&2 + '''; + }; + testing2 = { name = "testing2"; }; + } + ''; + + description = '' + Repositories. + ''; + }; + root-desc = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Text printed below the heading on the repository index page. + Default value: "a fast webinterface for the git dscm". + ''; + }; + root-title = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Text printed as heading on the repository index page. + Default value: "Git Repository Browser". + ''; + }; + rules = mkOption { + type = types.unspecified; + }; + users = mkOption { + type = types.unspecified; + }; + }; + + git-imp = { + system.activationScripts.git-init = "${init-script}"; + + # TODO maybe put all scripts here and then use PATH? + environment.etc."${etc-base}".source = + scriptFarm "git-ssh-authorizers" { + authorize-command = makeAuthorizeScript (map ({ repo, user, perm }: [ + (map getName (ensureList user)) + (map getName (ensureList repo)) + (map getName perm.allow-commands) + ]) cfg.rules); + + authorize-push = makeAuthorizeScript (map ({ repo, user, perm }: [ + (map getName (ensureList user)) + (map getName (ensureList repo)) + (ensureList perm.allow-receive-ref) + (map getName perm.allow-receive-modes) + ]) (filter (x: hasAttr "allow-receive-ref" x.perm) cfg.rules)); + }; + + users.extraUsers = singleton { + description = "Git repository hosting user"; + name = "git"; + shell = "/bin/sh"; + openssh.authorizedKeys.keys = + mapAttrsToList (_: makeAuthorizedKey git-ssh-command) cfg.users; + uid = 129318403; # genid git + }; + }; + + cgit-imp = { + users.extraUsers = lib.singleton { + inherit (fcgitwrap-user) group name uid; + home = toString (pkgs.runCommand "empty" {} "mkdir -p $out"); + }; + + users.extraGroups = lib.singleton { + inherit (fcgitwrap-group) gid name; + }; + + services.fcgiwrap = { + enable = true; + user = fcgitwrap-user.name; + group = fcgitwrap-user.group; + # socketAddress = "/run/fcgiwrap.sock" (default) + # socketType = "unix" (default) + }; + + environment.etc."cgitrc".text = '' + css=/static/cgit.css + logo=/static/cgit.png + + # if you do not want that webcrawler (like google) index your site + robots=noindex, nofollow + + virtual-root=/ + + # TODO make this nicer (and/or somewhere else) + cache-root=/tmp/cgit + + cache-size=1000 + enable-commit-graph=1 + enable-index-links=1 + enable-index-owner=0 + enable-log-filecount=1 + enable-log-linecount=1 + enable-remote-branches=1 + + ${optionalString (cfg.root-title != null) "root-title=${cfg.root-title}"} + ${optionalString (cfg.root-desc != null) "root-desc=${cfg.root-desc}"} + + snapshots=0 + max-stats=year + + ${concatMapStringsSep "\n" (repo: '' + repo.url=${repo.name} + repo.path=${cfg.dataDir}/${repo.name} + ${optionalString (repo.section != null) "repo.section=${repo.section}"} + ${optionalString (repo.desc != null) "repo.desc=${repo.desc}"} + '') (filter isPublicRepo (attrValues cfg.repos))} + ''; + + system.activationScripts.cgit = '' + mkdir -m 0700 -p /tmp/cgit + chown ${toString fcgitwrap-user.uid}:${toString fcgitwrap-group.gid} /tmp/cgit + ''; + + krebs.nginx = { + enable = true; + servers.cgit = { + server-names = [ + "cgit.${config.networking.hostName}" + "cgit.${config.networking.hostName}.retiolum" + ]; + locations = [ + (nameValuePair "/" '' + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME ${pkgs.cgit}/cgit/cgit.cgi; + fastcgi_param PATH_INFO $uri; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; + '') + (nameValuePair "/static/" '' + root ${pkgs.cgit}/cgit; + rewrite ^/static(/.*)$ $1 break; + '') + ]; + }; + }; + }; + + fcgitwrap-user = { + name = "fcgiwrap"; + uid = 2867890860; # genid fcgiwrap + group = "fcgiwrap"; + }; + + fcgitwrap-group = { + name = fcgitwrap-user.name; + gid = fcgitwrap-user.uid; + }; + + + ensureList = x: + if typeOf x == "list" then x else [x]; + + getName = x: x.name; + + isPublicRepo = getAttr "public"; # TODO this is also in ./cgit.nix + + makeAuthorizedKey = git-ssh-command: user@{ name, pubkey }: + # TODO assert name + # TODO assert pubkey + let + options = concatStringsSep "," [ + ''command="exec ${git-ssh-command} ${name}"'' + "no-agent-forwarding" + "no-port-forwarding" + "no-pty" + "no-X11-forwarding" + ]; + in + "${options} ${pubkey}"; + + # [case-pattern] -> shell-script + # Create a shell script that succeeds (exit 0) when all its arguments + # match the case patterns (in the given order). + makeAuthorizeScript = + let + # TODO escape + to-pattern = x: concatStringsSep "|" (ensureList x); + go = i: ps: + if ps == [] + then "exit 0" + else '' + case ''$${toString i} in ${to-pattern (head ps)}) + ${go (i + 1) (tail ps)} + esac''; + in + patterns: '' + #! /bin/sh + set -euf + ${concatStringsSep "\n" (map (go 1) patterns)} + exit -1 + ''; + + reponames = rules: sort lessThan (unique (map (x: x.repo.name) rules)); + + # TODO makeGitHooks that uses runCommand instead of scriptFarm? + scriptFarm = + farm-name: scripts: + let + makeScript = script-name: script-string: { + name = script-name; + path = pkgs.writeScript "${farm-name}_${script-name}" script-string; + }; + in + pkgs.linkFarm farm-name (mapAttrsToList makeScript scripts); + + + git-ssh-command = pkgs.writeScript "git-ssh-command" '' + #! /bin/sh + set -euf + + PATH=${makeSearchPath "bin" (with pkgs; [ + coreutils + git + gnugrep + gnused + systemd + ])} + + abort() { + echo "error: $1" >&2 + systemd-cat -p err -t git echo "error: $1" + exit -1 + } + + GIT_SSH_USER=$1 + + systemd-cat -p info -t git echo \ + "authorizing $GIT_SSH_USER $SSH_CONNECTION $SSH_ORIGINAL_COMMAND" + + # References: The Base Definitions volume of + # POSIX.1‐2013, Section 3.278, Portable Filename Character Set + portable_filename_bre="^[A-Za-z0-9._-]\\+$" + + command=$(echo "$SSH_ORIGINAL_COMMAND" \ + | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\1/p' \ + | grep "$portable_filename_bre" \ + || abort 'cannot read command') + + GIT_SSH_REPO=$(echo "$SSH_ORIGINAL_COMMAND" \ + | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\2/p' \ + | grep "$portable_filename_bre" \ + || abort 'cannot read reponame') + + ${cfg.etcDir}/authorize-command \ + "$GIT_SSH_USER" "$GIT_SSH_REPO" "$command" \ + || abort 'access denied' + + repodir=${escapeShellArg cfg.dataDir}/$GIT_SSH_REPO + + systemd-cat -p info -t git \ + echo "authorized exec $command $repodir" + + export GIT_SSH_USER + export GIT_SSH_REPO + exec "$command" "$repodir" + ''; + + init-script = pkgs.writeScript "git-init" '' + #! /bin/sh + set -euf + + PATH=${makeSearchPath "bin" (with pkgs; [ + coreutils + findutils + gawk + git + gnugrep + gnused + ])} + + dataDir=${escapeShellArg cfg.dataDir} + mkdir -p "$dataDir" + + # Notice how the presence of hooks symlinks determine whether + # we manage a repositry or not. + + # Make sure that no existing repository has hooks. We can delete + # symlinks because we assume we created them. + find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks -type l -delete + bad_hooks=$(find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks) + if echo "$bad_hooks" | grep -q .; then + printf 'error: unknown hooks:\n%s\n' \ + "$(echo "$bad_hooks" | sed 's/^/ /')" \ + >&2 + exit -1 + fi + + # Initialize repositories. + ${concatMapStringsSep "\n" (repo: + let + hooks = scriptFarm "git-hooks" (makeHooks repo); + in + '' + reponame=${escapeShellArg repo.name} + repodir=$dataDir/$reponame + mode=${toString (if isPublicRepo repo then 0711 else 0700)} + if ! test -d "$repodir"; then + mkdir -m "$mode" "$repodir" + git init --bare --template=/var/empty "$repodir" + chown -R git:nogroup "$repodir" + fi + ln -s ${hooks} "$repodir/hooks" + '' + ) (attrValues cfg.repos)} + + # Warn about repositories that exist but aren't mentioned in the + # current configuration (and thus didn't receive a hooks symlink). + unknown_repos=$(find "$dataDir" -mindepth 1 -maxdepth 1 \ + -type d \! -exec test -e '{}/hooks' \; -print) + if echo "$unknown_repos" | grep -q .; then + printf 'warning: stale repositories:\n%s\n' \ + "$(echo "$unknown_repos" | sed 's/^/ /')" \ + >&2 + fi + ''; + + makeHooks = repo: removeAttrs repo.hooks [ "pre-receive" ] // { + pre-receive = '' + #! /bin/sh + set -euf + + PATH=${makeSearchPath "bin" (with pkgs; [ + coreutils # env + git + systemd + ])} + + accept() { + #systemd-cat -p info -t git echo "authorized $1" + accept_string="''${accept_string+$accept_string + }authorized $1" + } + reject() { + #systemd-cat -p err -t git echo "denied $1" + #echo 'access denied' >&2 + #exit_code=-1 + reject_string="''${reject_string+$reject_string + }access denied: $1" + } + + empty=0000000000000000000000000000000000000000 + + accept_string= + reject_string= + while read oldrev newrev ref; do + + if [ $oldrev = $empty ]; then + receive_mode=create + elif [ $newrev = $empty ]; then + receive_mode=delete + elif [ "$(git merge-base $oldrev $newrev)" = $oldrev ]; then + receive_mode=fast-forward + else + receive_mode=non-fast-forward + fi + + if ${cfg.etcDir}/authorize-push \ + "$GIT_SSH_USER" "$GIT_SSH_REPO" "$ref" "$receive_mode"; then + accept "$receive_mode $ref" + else + reject "$receive_mode $ref" + fi + done + + if [ -n "$reject_string" ]; then + systemd-cat -p err -t git echo "$reject_string" + exit -1 + fi + + systemd-cat -p info -t git echo "$accept_string" + + ${optionalString (hasAttr "post-receive" repo.hooks) '' + # custom post-receive hook + ${repo.hooks.post-receive}''} + ''; + }; + + etc-base = + assert (hasPrefix "/etc/" cfg.etcDir); + removePrefix "/etc/" cfg.etcDir; + +in +out diff --git a/3modules/tv/git.nix b/3modules/tv/git.nix deleted file mode 100644 index ea014e2a..00000000 --- a/3modules/tv/git.nix +++ /dev/null @@ -1,484 +0,0 @@ -{ config, pkgs, lib, ... }: - -# TODO unify logging of shell scripts to user and journal -# TODO move all scripts to ${etcDir}, so ControlMaster connections -# immediately pick up new authenticators -# TODO when authorized_keys changes, then restart ssh -# (or kill already connected users somehow) - -with builtins; -with lib; -let - cfg = config.tv.git; - - out = { - # TODO don't import krebs.nginx here - imports = [ - ../../3modules/krebs/nginx.nix - ]; - options.tv.git = api; - config = mkIf cfg.enable (mkMerge [ - (mkIf cfg.cgit cgit-imp) - git-imp - ]); - }; - - api = { - enable = mkEnableOption "tv.git"; - - cgit = mkOption { - type = types.bool; - default = true; - description = "Enable cgit."; # TODO better desc; talk about nginx - }; - dataDir = mkOption { - type = types.str; - default = "/var/lib/git"; - description = "Directory used to store repositories."; - }; - etcDir = mkOption { - type = types.str; - default = "/etc/git"; - }; - repos = mkOption { - type = types.attrsOf (types.submodule ({ - options = { - desc = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Repository description. - ''; - }; - section = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Repository section. - ''; - }; - name = mkOption { - type = types.str; - description = '' - Repository name. - ''; - }; - hooks = mkOption { - type = types.attrsOf types.str; - description = '' - Repository-specific hooks. - ''; - }; - public = mkOption { - type = types.bool; - default = false; - description = '' - Allow everybody to read the repository via HTTP if cgit enabled. - ''; - # TODO allow every configured user to fetch the repository via SSH. - }; - }; - })); - - default = {}; - - example = literalExample '' - { - testing = { - name = "testing"; - hooks.post-update = ''' - #! /bin/sh - set -euf - echo post-update hook: $* >&2 - '''; - }; - testing2 = { name = "testing2"; }; - } - ''; - - description = '' - Repositories. - ''; - }; - root-desc = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Text printed below the heading on the repository index page. - Default value: "a fast webinterface for the git dscm". - ''; - }; - root-title = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Text printed as heading on the repository index page. - Default value: "Git Repository Browser". - ''; - }; - rules = mkOption { - type = types.unspecified; - }; - users = mkOption { - type = types.unspecified; - }; - }; - - git-imp = { - system.activationScripts.git-init = "${init-script}"; - - # TODO maybe put all scripts here and then use PATH? - environment.etc."${etc-base}".source = - scriptFarm "git-ssh-authorizers" { - authorize-command = makeAuthorizeScript (map ({ repo, user, perm }: [ - (map getName (ensureList user)) - (map getName (ensureList repo)) - (map getName perm.allow-commands) - ]) cfg.rules); - - authorize-push = makeAuthorizeScript (map ({ repo, user, perm }: [ - (map getName (ensureList user)) - (map getName (ensureList repo)) - (ensureList perm.allow-receive-ref) - (map getName perm.allow-receive-modes) - ]) (filter (x: hasAttr "allow-receive-ref" x.perm) cfg.rules)); - }; - - users.extraUsers = singleton { - description = "Git repository hosting user"; - name = "git"; - shell = "/bin/sh"; - openssh.authorizedKeys.keys = - mapAttrsToList (_: makeAuthorizedKey git-ssh-command) cfg.users; - uid = 129318403; # genid git - }; - }; - - cgit-imp = { - users.extraUsers = lib.singleton { - inherit (fcgitwrap-user) group name uid; - home = toString (pkgs.runCommand "empty" {} "mkdir -p $out"); - }; - - users.extraGroups = lib.singleton { - inherit (fcgitwrap-group) gid name; - }; - - services.fcgiwrap = { - enable = true; - user = fcgitwrap-user.name; - group = fcgitwrap-user.group; - # socketAddress = "/run/fcgiwrap.sock" (default) - # socketType = "unix" (default) - }; - - environment.etc."cgitrc".text = '' - css=/static/cgit.css - logo=/static/cgit.png - - # if you do not want that webcrawler (like google) index your site - robots=noindex, nofollow - - virtual-root=/ - - # TODO make this nicer (and/or somewhere else) - cache-root=/tmp/cgit - - cache-size=1000 - enable-commit-graph=1 - enable-index-links=1 - enable-index-owner=0 - enable-log-filecount=1 - enable-log-linecount=1 - enable-remote-branches=1 - - ${optionalString (cfg.root-title != null) "root-title=${cfg.root-title}"} - ${optionalString (cfg.root-desc != null) "root-desc=${cfg.root-desc}"} - - snapshots=0 - max-stats=year - - ${concatMapStringsSep "\n" (repo: '' - repo.url=${repo.name} - repo.path=${cfg.dataDir}/${repo.name} - ${optionalString (repo.section != null) "repo.section=${repo.section}"} - ${optionalString (repo.desc != null) "repo.desc=${repo.desc}"} - '') (filter isPublicRepo (attrValues cfg.repos))} - ''; - - system.activationScripts.cgit = '' - mkdir -m 0700 -p /tmp/cgit - chown ${toString fcgitwrap-user.uid}:${toString fcgitwrap-group.gid} /tmp/cgit - ''; - - krebs.nginx = { - enable = true; - servers.cgit = { - server-names = [ - "cgit.${config.networking.hostName}" - "cgit.${config.networking.hostName}.retiolum" - ]; - locations = [ - (nameValuePair "/" '' - include ${pkgs.nginx}/conf/fastcgi_params; - fastcgi_param SCRIPT_FILENAME ${pkgs.cgit}/cgit/cgit.cgi; - fastcgi_param PATH_INFO $uri; - fastcgi_param QUERY_STRING $args; - fastcgi_param HTTP_HOST $server_name; - fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; - '') - (nameValuePair "/static/" '' - root ${pkgs.cgit}/cgit; - rewrite ^/static(/.*)$ $1 break; - '') - ]; - }; - }; - }; - - fcgitwrap-user = { - name = "fcgiwrap"; - uid = 2867890860; # genid fcgiwrap - group = "fcgiwrap"; - }; - - fcgitwrap-group = { - name = fcgitwrap-user.name; - gid = fcgitwrap-user.uid; - }; - - - ensureList = x: - if typeOf x == "list" then x else [x]; - - getName = x: x.name; - - isPublicRepo = getAttr "public"; # TODO this is also in ./cgit.nix - - makeAuthorizedKey = git-ssh-command: user@{ name, pubkey }: - # TODO assert name - # TODO assert pubkey - let - options = concatStringsSep "," [ - ''command="exec ${git-ssh-command} ${name}"'' - "no-agent-forwarding" - "no-port-forwarding" - "no-pty" - "no-X11-forwarding" - ]; - in - "${options} ${pubkey}"; - - # [case-pattern] -> shell-script - # Create a shell script that succeeds (exit 0) when all its arguments - # match the case patterns (in the given order). - makeAuthorizeScript = - let - # TODO escape - to-pattern = x: concatStringsSep "|" (ensureList x); - go = i: ps: - if ps == [] - then "exit 0" - else '' - case ''$${toString i} in ${to-pattern (head ps)}) - ${go (i + 1) (tail ps)} - esac''; - in - patterns: '' - #! /bin/sh - set -euf - ${concatStringsSep "\n" (map (go 1) patterns)} - exit -1 - ''; - - reponames = rules: sort lessThan (unique (map (x: x.repo.name) rules)); - - # TODO makeGitHooks that uses runCommand instead of scriptFarm? - scriptFarm = - farm-name: scripts: - let - makeScript = script-name: script-string: { - name = script-name; - path = pkgs.writeScript "${farm-name}_${script-name}" script-string; - }; - in - pkgs.linkFarm farm-name (mapAttrsToList makeScript scripts); - - - git-ssh-command = pkgs.writeScript "git-ssh-command" '' - #! /bin/sh - set -euf - - PATH=${makeSearchPath "bin" (with pkgs; [ - coreutils - git - gnugrep - gnused - systemd - ])} - - abort() { - echo "error: $1" >&2 - systemd-cat -p err -t git echo "error: $1" - exit -1 - } - - GIT_SSH_USER=$1 - - systemd-cat -p info -t git echo \ - "authorizing $GIT_SSH_USER $SSH_CONNECTION $SSH_ORIGINAL_COMMAND" - - # References: The Base Definitions volume of - # POSIX.1‐2013, Section 3.278, Portable Filename Character Set - portable_filename_bre="^[A-Za-z0-9._-]\\+$" - - command=$(echo "$SSH_ORIGINAL_COMMAND" \ - | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\1/p' \ - | grep "$portable_filename_bre" \ - || abort 'cannot read command') - - GIT_SSH_REPO=$(echo "$SSH_ORIGINAL_COMMAND" \ - | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\2/p' \ - | grep "$portable_filename_bre" \ - || abort 'cannot read reponame') - - ${cfg.etcDir}/authorize-command \ - "$GIT_SSH_USER" "$GIT_SSH_REPO" "$command" \ - || abort 'access denied' - - repodir=${escapeShellArg cfg.dataDir}/$GIT_SSH_REPO - - systemd-cat -p info -t git \ - echo "authorized exec $command $repodir" - - export GIT_SSH_USER - export GIT_SSH_REPO - exec "$command" "$repodir" - ''; - - init-script = pkgs.writeScript "git-init" '' - #! /bin/sh - set -euf - - PATH=${makeSearchPath "bin" (with pkgs; [ - coreutils - findutils - gawk - git - gnugrep - gnused - ])} - - dataDir=${escapeShellArg cfg.dataDir} - mkdir -p "$dataDir" - - # Notice how the presence of hooks symlinks determine whether - # we manage a repositry or not. - - # Make sure that no existing repository has hooks. We can delete - # symlinks because we assume we created them. - find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks -type l -delete - bad_hooks=$(find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks) - if echo "$bad_hooks" | grep -q .; then - printf 'error: unknown hooks:\n%s\n' \ - "$(echo "$bad_hooks" | sed 's/^/ /')" \ - >&2 - exit -1 - fi - - # Initialize repositories. - ${concatMapStringsSep "\n" (repo: - let - hooks = scriptFarm "git-hooks" (makeHooks repo); - in - '' - reponame=${escapeShellArg repo.name} - repodir=$dataDir/$reponame - mode=${toString (if isPublicRepo repo then 0711 else 0700)} - if ! test -d "$repodir"; then - mkdir -m "$mode" "$repodir" - git init --bare --template=/var/empty "$repodir" - chown -R git:nogroup "$repodir" - fi - ln -s ${hooks} "$repodir/hooks" - '' - ) (attrValues cfg.repos)} - - # Warn about repositories that exist but aren't mentioned in the - # current configuration (and thus didn't receive a hooks symlink). - unknown_repos=$(find "$dataDir" -mindepth 1 -maxdepth 1 \ - -type d \! -exec test -e '{}/hooks' \; -print) - if echo "$unknown_repos" | grep -q .; then - printf 'warning: stale repositories:\n%s\n' \ - "$(echo "$unknown_repos" | sed 's/^/ /')" \ - >&2 - fi - ''; - - makeHooks = repo: removeAttrs repo.hooks [ "pre-receive" ] // { - pre-receive = '' - #! /bin/sh - set -euf - - PATH=${makeSearchPath "bin" (with pkgs; [ - coreutils # env - git - systemd - ])} - - accept() { - #systemd-cat -p info -t git echo "authorized $1" - accept_string="''${accept_string+$accept_string - }authorized $1" - } - reject() { - #systemd-cat -p err -t git echo "denied $1" - #echo 'access denied' >&2 - #exit_code=-1 - reject_string="''${reject_string+$reject_string - }access denied: $1" - } - - empty=0000000000000000000000000000000000000000 - - acc