diff options
-rw-r--r-- | lib/default.nix | 3 | ||||
-rw-r--r-- | lib/git.nix | 140 | ||||
-rw-r--r-- | modules/cd/default.nix | 37 | ||||
-rw-r--r-- | modules/cd/iptables.nix | 1 | ||||
-rw-r--r-- | modules/tv/git/cgit.nix | 110 | ||||
-rw-r--r-- | modules/tv/git/default.nix (renamed from modules/tv/git.nix) | 33 | ||||
-rw-r--r-- | modules/wu/default.nix | 2 | ||||
-rw-r--r-- | modules/wu/users.nix | 5 |
8 files changed, 306 insertions, 25 deletions
diff --git a/lib/default.nix b/lib/default.nix index 27cf0e25..164a6a1a 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ lib, pkgs, ... }: with builtins; @@ -11,6 +11,7 @@ rec { lib = lib // { inherit addNames; }; + inherit pkgs; }; addName = name: set: diff --git a/lib/git.nix b/lib/git.nix index 5916cf83..b28d8941 100644 --- a/lib/git.nix +++ b/lib/git.nix @@ -1,7 +1,7 @@ -{ lib, ... }: +{ lib, pkgs, ... }: let - inherit (lib) addNames; + inherit (lib) addNames escapeShellArg makeSearchPath; commands = addNames { git-receive-pack = {}; @@ -37,5 +37,139 @@ let master = "refs/heads/master"; all-heads = "refs/heads/*"; }; + + irc-announce-script = pkgs.writeScript "irc-announce-script" '' + #! /bin/sh + set -euf + + export PATH=${makeSearchPath "bin" (with pkgs; [ + coreutils + gawk + gnused + netcat + ])} + + IRC_SERVER="$1" + IRC_PORT="$2" + IRC_NICK="$3" + IRC_CHANNEL="$4" + message=$5 + + export IRC_CHANNEL # for privmsg_cat + + # echo2 and cat2 are used output to both, stdout and stderr + # This is used to see what we send to the irc server. (debug output) + echo2() { echo "$*"; echo "$*" >&2; } + cat2() { tee /dev/stderr; } + + # privmsg_cat transforms stdin to a privmsg + privmsg_cat() { awk '{ print "PRIVMSG "ENVIRON["IRC_CHANNEL"]" :"$0 }'; } + + # ircin is used to feed the output of netcat back to the "irc client" + # so we can implement expect-like behavior with sed^_^ + # XXX mkselfdestructingtmpfifo would be nice instead of this cruft + tmpdir="$(mktemp -d irc-announce_XXXXXXXX)" + cd "$tmpdir" + mkfifo ircin + trap " + rm ircin + cd '$OLDPWD' + rmdir '$tmpdir' + trap - EXIT INT QUIT + " EXIT INT QUIT + + { + echo2 "USER $LOGNAME 0 * :$LOGNAME@$(hostname)" + echo2 "NICK $IRC_NICK" + + # wait for MODE message + sed -n '/^:[^ ]* MODE /q' + + echo2 "JOIN $IRC_CHANNEL" + + printf '%s' "$message" \ + | privmsg_cat \ + | cat2 + + echo2 "PART $IRC_CHANNEL" + + # wait for PART confirmation + sed -n '/:'"$IRC_NICK"'![^ ]* PART /q' + + echo2 'QUIT :Gone to have lunch' + } < ircin \ + | nc "$IRC_SERVER" "$IRC_PORT" | tee -a ircin + ''; + + hooks = { + # TODO make this a package? + irc-announce = { nick, channel, server, port ? 6667 }: '' + #! /bin/sh + set -euf + + export PATH=${makeSearchPath "bin" (with pkgs; [ + coreutils + git + gnused + ])} + + nick=${escapeShellArg nick} + channel=${escapeShellArg channel} + server=${escapeShellArg server} + port=${toString port} + + empty=0000000000000000000000000000000000000000 + + unset message + 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 + + h=$(echo $ref | sed 's:^refs/heads/::') + + # empty_tree=$(git hash-object -t tree /dev/null + empty_tree=4b825dc6 + + id=$(echo $oldrev | cut -b-7) + id2=$(echo $newrev | cut -b-7) + if [ $oldrev = $empty ]; then id=$empty_tree; fi + if [ $newrev = $empty ]; then id2=$empty_tree; fi + + case $receive_mode in + create) + #git log --oneline $id2 + link="http://cd/cgit/$GIT_SSH_REPO/" + ;; + fast-forward|non-fast-forward) + #git diff --stat $id..$id2 + link="http://cd/cgit/$GIT_SSH_REPO/diff/?h=$h&id=$id&id2=$id2" + ;; + esac + + #host=$nick + #$host $GIT_SSH_REPO $ref $link + message="''${message+$message + }$GIT_SSH_USER $receive_mode pushed $link" + done + + if test -n "''${message-}"; then + exec ${irc-announce-script} \ + "$server" \ + "$port" \ + "$nick" \ + "$channel" \ + "$message" + fi + ''; + }; + in -commands // receive-modes // permissions // refs +commands // receive-modes // permissions // refs // hooks diff --git a/modules/cd/default.nix b/modules/cd/default.nix index 1d621e03..7223203a 100644 --- a/modules/cd/default.nix +++ b/modules/cd/default.nix @@ -11,7 +11,7 @@ ../tv/base-cac-CentOS-7-64bit.nix ../tv/ejabberd.nix # XXX echtes modul ../tv/exim-smarthost.nix - ../tv/git.nix + ../tv/git ../tv/retiolum.nix ../tv/sanitize.nix ]; @@ -48,7 +48,7 @@ let inherit (builtins) readFile; # TODO lib should already include our stuff - inherit (import ../../lib { inherit lib; }) addNames git; + inherit (import ../../lib { inherit lib pkgs; }) addNames git; in rec { enable = true; @@ -59,31 +59,38 @@ makefu = { pubkey = "xxx"; }; }; - # TODO warn about stale repodirs repos = addNames { + shitment = { + desc = "shitment repository"; + hooks = { + post-receive = git.irc-announce { + nick = config.networking.hostName; # TODO make this the default + channel = "#retiolum"; + server = "ire.retiolum"; + }; + }; + public = true; + }; testing = { + desc = "testing repository"; hooks = { - update = '' - #! /bin/sh - set -euf - echo update hook: $* >&2 - ''; - post-update = '' - #! /bin/sh - set -euf - echo post-update hook: $* >&2 - ''; + post-receive = git.irc-announce { + nick = config.networking.hostName; # TODO make this the default + channel = "#repository"; + server = "ire.retiolum"; + }; }; + public = true; }; }; rules = with git; with users; with repos; [ { user = tv; - repo = testing; + repo = [ testing shitment ]; perm = push master [ non-fast-forward create delete merge ]; } { user = [ lass makefu ]; - repo = testing; + repo = [ testing shitment ]; perm = fetch; } ]; diff --git a/modules/cd/iptables.nix b/modules/cd/iptables.nix index 48425e8d..950aa847 100644 --- a/modules/cd/iptables.nix +++ b/modules/cd/iptables.nix @@ -63,6 +63,7 @@ ip4tables -A Retiolum -j ACCEPT -p icmp --icmp-type echo-request ip6tables -A Retiolum -j ACCEPT -p ipv6-icmp -m icmp6 --icmpv6-type echo-request + ipXtables -A Retiolum -j ACCEPT -p tcp --dport http -m conntrack --ctstate NEW ${when log "ipXtables -A Retiolum -j LOG --log-level info --log-prefix 'REJECT '"} ipXtables -A Retiolum -j REJECT -p tcp --reject-with tcp-reset diff --git a/modules/tv/git/cgit.nix b/modules/tv/git/cgit.nix new file mode 100644 index 00000000..edee1990 --- /dev/null +++ b/modules/tv/git/cgit.nix @@ -0,0 +1,110 @@ +{ config, lib, pkgs, ... }: + +let + inherit (builtins) attrValues filter getAttr; + inherit (lib) concatMapStringsSep mkIf optionalString; + + cfg = config.services.git; + + isPublicRepo = getAttr "public"; # TODO this is also in ./default.nix +in + +{ + config = mkIf cfg.cgit { + + users.extraUsers = lib.singleton { + name = "fcgiwrap"; + uid = 2851179180; # genid fcgiwrap + group = "fcgiwrap"; + home = "/var/empty"; + }; + + users.extraGroups = lib.singleton { + name = "fcgiwrap"; + gid = 2851179180; # genid fcgiwrap + }; + + services.fcgiwrap = { + enable = true; + user = "fcgiwrap"; + group = "fcgiwrap"; + # socketAddress = "/run/fcgiwrap.sock" (default) + # socketType = "unix" (default) + }; + + environment.etc."cgitrc".text = '' + css=/cgit-static/cgit.css + logo=/cgit-static/cgit.png + + # if you do not want that webcrawler (like google) index your site + robots=noindex, nofollow + + virtual-root=/cgit + + # TODO make this nicer + 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 + + root-title=repositories at ${config.networking.hostName} + root-desc=keep calm and engage + + snapshots=0 + max-stats=year + + ${concatMapStringsSep "\n" (repo: '' + repo.url=${repo.name} + repo.path=${cfg.dataDir}/${repo.name} + ${optionalString (repo.desc != null) "repo.desc=${repo.desc}"} + '') (filter isPublicRepo (attrValues cfg.repos))} + ''; + + # TODO modular nginx configuration + services.nginx = + let + name = config.networking.hostName; + qname = "${name}.retiolum"; + in + { + enable = true; + httpConfig = '' + include ${pkgs.nginx}/conf/mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + gzip on; + server { + listen 80; + server_name ${name} ${qname} localhost; + root ${pkgs.cgit}/cgit; + + location /cgit-static { + rewrite ^/cgit-static(/.*)$ $1 break; + #expires 30d; + } + + location /cgit { + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/cgit.cgi; + #fastcgi_param PATH_INFO $uri; + fastcgi_split_path_info ^(/cgit/?)(.+)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; + } + + location / { + return 404; + } + } + ''; + }; + }; +} diff --git a/modules/tv/git.nix b/modules/tv/git/default.nix index d264125d..50e2f920 100644 --- a/modules/tv/git.nix +++ b/modules/tv/git/default.nix @@ -2,8 +2,8 @@ let inherit (builtins) - attrNames attrValues concatLists filter hasAttr head lessThan removeAttrs - tail toJSON typeOf; + attrNames attrValues concatLists getAttr filter hasAttr head lessThan + removeAttrs tail toJSON typeOf; inherit (lib) concatMapStringsSep concatStringsSep escapeShellArg hasPrefix literalExample makeSearchPath mapAttrsToList mkIf mkOption optionalString @@ -16,6 +16,8 @@ let getName = x: x.name; + isPublicRepo = getAttr "public"; # TODO this is also in ./cgit.nix + makeAuthorizedKey = command-script: user@{ name, pubkey }: # TODO assert name # TODO assert pubkey @@ -78,12 +80,21 @@ in # (or kill already connected users somehow) { + imports = [ + ./cgit.nix + ]; + options.services.git = { enable = mkOption { type = types.bool; default = false; description = "Enable Git repository hosting."; }; + 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"; @@ -99,6 +110,13 @@ in repos = mkOption { type = types.attrsOf (types.submodule ({ options = { + desc = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Repository description. + ''; + }; name = mkOption { type = types.str; description = '' @@ -111,6 +129,14 @@ in 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. + }; }; })); @@ -230,8 +256,9 @@ in '' reponame=${escapeShellArg repo.name} repodir=$dataDir/$reponame + mode=${toString (if isPublicRepo repo then 0711 else 0700)} if ! test -d "$repodir"; then - mkdir -m 0700 "$repodir" + mkdir -m "$mode" "$repodir" git init --bare --template=/var/empty "$repodir" chown -R git: "$repodir" fi diff --git a/modules/wu/default.nix b/modules/wu/default.nix index 84a8361a..68475ad5 100644 --- a/modules/wu/default.nix +++ b/modules/wu/default.nix @@ -1,7 +1,7 @@ { config, pkgs, ... }: let - lib = import ../../lib { inherit pkgs; }; + lib = import ../../lib { lib = pkgs.lib; inherit pkgs; }; inherit (lib) majmin; in diff --git a/modules/wu/users.nix b/modules/wu/users.nix index 88f2b658..4c863148 100644 --- a/modules/wu/users.nix +++ b/modules/wu/users.nix @@ -1,4 +1,4 @@ -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: let inherit (builtins) attrValues; @@ -194,7 +194,8 @@ let sudoers = let inherit (builtins) filter hasAttr; - inherit (import ../../lib { inherit pkgs; }) concat isSuffixOf removeSuffix setToList; + inherit (import ../../lib { inherit lib pkgs; }) + concat isSuffixOf removeSuffix setToList; hasMaster = { group ? "", ... }: isSuffixOf "-sub" group; |