diff options
Diffstat (limited to 'configs')
47 files changed, 2638 insertions, 0 deletions
diff --git a/configs/autotether.nix b/configs/autotether.nix new file mode 100644 index 0000000..43b5575 --- /dev/null +++ b/configs/autotether.nix @@ -0,0 +1,19 @@ +{ config, pkgs, ... }: let + cfg.serial = "17e064850405"; +in { + systemd.services.usb_tether.serviceConfig = { + SyslogIdentifier = "usb_tether"; + ExecStartPre = "${pkgs.android-tools}/bin/adb -s ${cfg.serial} wait-for-device"; + ExecStart = "${pkgs.android-tools}/bin/adb -s ${cfg.serial} shell svc usb setFunctions rndis"; + }; + services.udev.extraRules = /* sh */ '' + ACTION=="add", SUBSYSTEM=="net", KERNEL=="usb*", NAME="android" + + ACTION=="add", SUBSYSTEM=="usb", ATTR{serial}=="${cfg.serial}", \ + TAG+="systemd", ENV{SYSTEMD_WANTS}="usb_tether.service" + ''; + systemd.network.networks.android = { + matchConfig.Name = "android"; + DHCP = "yes"; + }; +} diff --git a/configs/backup.nix b/configs/backup.nix new file mode 100644 index 0000000..30d6011 --- /dev/null +++ b/configs/backup.nix @@ -0,0 +1,108 @@ +{ config, lib, mylib, pkgs, ... }: { + krebs.backup.plans = { + } // lib.mapAttrs (_: lib.recursiveUpdate { + snapshots = { + daily = { format = "%Y-%m-%d"; retain = 7; }; + weekly = { format = "%YW%W"; retain = 4; }; + monthly = { format = "%Y-%m"; retain = 12; }; + yearly = { format = "%Y"; }; + }; + }) { + bu-home-xu = { + method = "push"; + src = { host = config.krebs.hosts.bu; path = "/home"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/bu-home"; }; + startAt = "05:20"; + }; + bu-home-zu = { + method = "push"; + src = { host = config.krebs.hosts.bu; path = "/home"; }; + dst = { host = config.krebs.hosts.zu; path = "/bku/bu-home"; }; + startAt = "05:25"; + }; + nomic-home-xu = { + method = "push"; + src = { host = config.krebs.hosts.nomic; path = "/home"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/nomic-home"; }; + startAt = "05:00"; + }; + nomic-home-zu = { + method = "push"; + src = { host = config.krebs.hosts.nomic; path = "/home"; }; + dst = { host = config.krebs.hosts.zu; path = "/bku/nomic-home"; }; + startAt = "04:20"; + }; + nomic-pull-querel-home = { + method = "pull"; + src = { host = config.krebs.hosts.querel; path = "/home"; }; + dst = { host = config.krebs.hosts.nomic; path = "/fs/ponyhof/bku/querel-home"; }; + startAt = "22:00"; + }; + xu-home-bu = { + method = "push"; + src = { host = config.krebs.hosts.xu; path = "/home"; }; + dst = { host = config.krebs.hosts.bu; path = "/bku/xu-home"; }; + startAt = "04:50"; + }; + xu-home-nomic = { + method = "push"; + src = { host = config.krebs.hosts.xu; path = "/home"; }; + dst = { host = config.krebs.hosts.nomic; path = "/fs/cis3hG/bku/xu-home"; }; + startAt = "05:20"; + }; + xu-home-zu = { + method = "push"; + src = { host = config.krebs.hosts.xu; path = "/home"; }; + dst = { host = config.krebs.hosts.zu; path = "/bku/xu-home"; }; + startAt = "06:20"; + }; + xu-pull-ni-ejabberd = { + method = "pull"; + src = { host = config.krebs.hosts.ni; path = "/var/lib/ejabberd"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/ni-ejabberd"; }; + startAt = "07:00"; + }; + xu-pull-ni-home = { + method = "pull"; + src = { host = config.krebs.hosts.ni; path = "/home"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/ni-home"; }; + startAt = "07:00"; + }; + zu-home-xu = { + method = "push"; + src = { host = config.krebs.hosts.zu; path = "/home"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/zu-home"; }; + startAt = "05:00"; + }; + zu-pull-ni-ejabberd = { + method = "pull"; + src = { host = config.krebs.hosts.ni; path = "/var/lib/ejabberd"; }; + dst = { host = config.krebs.hosts.zu; path = "/bku/ni-ejabberd"; }; + startAt = "06:00"; + }; + zu-pull-ni-home = { + method = "pull"; + src = { host = config.krebs.hosts.ni; path = "/home"; }; + dst = { host = config.krebs.hosts.zu; path = "/bku/ni-home"; }; + startAt = "06:30"; + }; + } // lib.mapAttrs (_: lib.recursiveUpdate { + snapshots = { + minutely = { format = "%Y-%m-%dT%H:%M"; retain = 3; }; + hourly = { format = "%Y-%m-%dT%H"; retain = 3; }; + daily = { format = "%Y-%m-%d"; retain = 3; }; + }; + startAt = null; + }) { + xu-test-push-xu = { + method = "push"; + src = { host = config.krebs.hosts.xu; path = "/tmp/xu-bku-test-data"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/xu-test-push"; }; + }; + xu-test-pull-xu = { + method = "pull"; + src = { host = config.krebs.hosts.xu; path = "/tmp/xu-bku-test-data"; }; + dst = { host = config.krebs.hosts.xu; path = "/bku/xu-test-pull"; }; + }; + }; +} diff --git a/configs/bash/default.nix b/configs/bash/default.nix new file mode 100644 index 0000000..2e18d5b --- /dev/null +++ b/configs/bash/default.nix @@ -0,0 +1,66 @@ +{ config, mylib, pkgs, ... }: { + programs.bash = { + interactiveShellInit = /* sh */ '' + HISTCONTROL='erasedups:ignorespace' + HISTSIZE=900001 + HISTFILESIZE=$HISTSIZE + HISTTIMEFORMAT= + + shopt -s checkhash + shopt -s histappend histreedit histverify + shopt -s no_empty_cmd_completion + complete -d cd + + case $UID in + ${mylib.shell.escape (toString config.krebs.users.tv.uid)}) + if test ''${SHLVL-1} = 1 && test -n "''${DISPLAY-}"; then + _CURRENT_DESKTOP_NAME=''${_CURRENT_DESKTOP_NAME-$( + ${pkgs.xorg.xprop}/bin/xprop -notype -root \ + 32i _NET_CURRENT_DESKTOP \ + 8s _NET_DESKTOP_NAMES \ + | + ${pkgs.gnused}/bin/sed -r 's/.* = //;s/"//g;s/, /\a/g' | + { + read -r _NET_CURRENT_DESKTOP + IFS=$'\a' read -ra _NET_DESKTOP_NAMES + echo "''${_NET_DESKTOP_NAMES[$_NET_CURRENT_DESKTOP]}" + } + )} + case $_CURRENT_DESKTOP_NAME in + stockholm) + cd ~/stockholm + ;; + esac + fi + + export NIX_PATH="stockholm=$HOME/stockholm:$NIX_PATH" + ;; + esac + + ${pkgs.bash-fzf-history.bind} + + if test -n "''${BASH_EXTRA_INIT-}"; then + . "$BASH_EXTRA_INIT" + fi + ''; + promptInit = /* sh */ '' + case $UID in + 0) + PS1='\[\e[1;31m\]\w\[\e[0m\] ' + ;; + ${toString config.krebs.build.user.uid}) + 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='\[\e[35m\]\h'" $PS1" + fi + if test -n "$SSH_AGENT_PID"; then + PS1="ssh-agent[$SSH_AGENT_PID] $PS1" + fi + ''; + }; +} diff --git a/configs/binary-cache/default.nix b/configs/binary-cache/default.nix new file mode 100644 index 0000000..d9e87c7 --- /dev/null +++ b/configs/binary-cache/default.nix @@ -0,0 +1,28 @@ +{ config, pkgs, ... }: { + environment.etc."binary-cache.pubkey".text = + config.krebs.build.host.binary-cache.pubkey; + + nixpkgs.overlays = [ + (self: super: { + nix-serve = self.haskellPackages.nix-serve-ng; + }) + ]; + + services.nix-serve = { + enable = true; + secretKeyFile = "${config.krebs.secret.directory}/nix-serve.key"; + }; + + services.nginx = { + enable = true; + virtualHosts.nix-serve = { + serverAliases = [ + "cache.${config.krebs.build.host.name}.hkw" + "cache.${config.krebs.build.host.name}.r" + ]; + locations."/".extraConfig = '' + proxy_pass http://localhost:${toString config.services.nix-serve.port}; + ''; + }; + }; +} diff --git a/configs/br.nix b/configs/br.nix new file mode 100644 index 0000000..b9bc70b --- /dev/null +++ b/configs/br.nix @@ -0,0 +1,49 @@ +{ config, lib, modulesPath, mylib, pkgs, ... }: { + + imports = [ + (modulesPath + "/services/hardware/sane_extra_backends/brscan4.nix") + ]; + + krebs.nixpkgs.allowUnfreePredicate = + pkg: lib.any (mylib.eq (mylib.packageName pkg)) [ + "brother-udev-rule-type1" + "brscan4" + "brscan4-etc-files" + "mfcl2700dnlpr" + ]; + + hardware.sane = { + enable = true; + brscan4 = { + enable = true; + netDevices = { + bra = { + model = "MFCL2700DN"; + ip = "10.23.1.214"; + }; + }; + }; + }; + + services.saned.enable = true; + + # usage: scanimage -d "$(find-scanner bra)" --batch --format=tiff --resolution 150 -x 211 -y 298 + environment.systemPackages = [ + (pkgs.writeDashBin "find-scanner" '' + set -efu + name=$1 + ${pkgs.sane-backends}/bin/scanimage -f '%m %d + ' \ + | ${pkgs.gawk}/bin/awk -v dev="*$name" '$1 == dev { print $2; exit }' \ + | ${pkgs.gnugrep}/bin/grep . + '') + ]; + + services.printing = { + enable = true; + drivers = [ + pkgs.mfcl2700dncupswrapper + ]; + }; + +} diff --git a/configs/default.nix b/configs/default.nix new file mode 100644 index 0000000..5d74d96 --- /dev/null +++ b/configs/default.nix @@ -0,0 +1,131 @@ +{ config, inputs, lib, mylib, pkgs, ... }: { + boot.tmpOnTmpfs = true; + + krebs.enable = true; + + krebs.build.user = config.krebs.users.tv; + + networking.hostId = lib.mkDefault (mylib.hashToLength 8 config.networking.hostName); + networking.hostName = config.krebs.build.host.name; + + imports = [ + ./backup.nix + ./bash + ./htop.nix + ./nets/hkw.nix + ./networkd.nix + ./nginx + ./nix.nix + ./pki + ./ssh.nix + ./sshd.nix + ./vim.nix + ./xdg.nix + { + users = { + defaultUserShell = "/run/current-system/sw/bin/bash"; + mutableUsers = false; + users = { + tv = { + inherit (config.krebs.users.tv) home uid; + isNormalUser = true; + extraGroups = [ "tv" ]; + }; + }; + }; + } + { + i18n.defaultLocale = lib.mkDefault "C.UTF-8"; + security.sudo.extraConfig = '' + Defaults env_keep+="SSH_CLIENT _CURRENT_DESKTOP_NAME" + Defaults mailto="${config.krebs.users.tv.mail}" + Defaults !lecture + ''; + time.timeZone = "Europe/Berlin"; + } + + { + nixpkgs.config.allowUnfree = false; + } + { + environment.homeBinInPath = true; + + environment.profileRelativeEnvVars.PATH = lib.mkForce [ "/bin" ]; + + environment.systemPackages = with pkgs; [ + rxvt_unicode.terminfo + ]; + + environment.shellAliases = lib.mkForce { + gp = "${pkgs.pari}/bin/gp -q"; + df = "df -h"; + du = "du -h"; + + # TODO alias cannot contain #\' + # "ps?" = "ps ax | head -n 1;ps ax | fgrep -v ' grep --color=auto ' | grep"; + + ls = "ls -h --color=auto --group-directories-first"; + dmesg = "dmesg -L --reltime"; + view = "vim -R"; + }; + + environment.variables = { + NIX_PATH = lib.mkForce (lib.concatStringsSep ":" [ + "secrets=/var/src/stockholm/null" + "/var/src" + ]); + }; + } + + { + services.cron.enable = false; + services.ntp.enable = false; + services.timesyncd.enable = true; + } + + { + boot.kernel.sysctl = { + # Enable IPv6 Privacy Extensions + # + # XXX use mkForce here because since NixOS 21.11 there's a collision in + # net.ipv6.conf.default.use_tempaddr, and boot.kernel.sysctl incapable + # of merging. + # + # XXX net.ipv6.conf.all.use_tempaddr is set because it was mentioned in + # https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch06s05.html + # TODO check if that is really necessary, otherwise we can rely solely + # on networking.tempAddresses in the future (when nothing is <21.11) + "net.ipv6.conf.all.use_tempaddr" = lib.mkForce 2; + "net.ipv6.conf.default.use_tempaddr" = lib.mkForce 2; + }; + } + + { + tv.iptables.enable = true; + tv.iptables.accept-echo-request = "internet"; + } + + { + services.journald.extraConfig = '' + SystemMaxUse=1G + RuntimeMaxUse=128M + ''; + } + + { + environment.systemPackages = [ + pkgs.field + pkgs.get + pkgs.git + pkgs.git-crypt + pkgs.git-preview + pkgs.hashPassword + pkgs.htop + pkgs.kpaste + pkgs.nix-prefetch-scripts + pkgs.ovh-zone + pkgs.push + ]; + } + ]; +} diff --git a/configs/elm-packages-proxy.nix b/configs/elm-packages-proxy.nix new file mode 100644 index 0000000..caea188 --- /dev/null +++ b/configs/elm-packages-proxy.nix @@ -0,0 +1,359 @@ +{ config, lib, pkgs, ... }: let + + cfg.nameserver = "1.1.1.1"; + cfg.packageDir = "/var/lib/elm-packages"; + cfg.port = 7782; + + # TODO secret files + cfg.htpasswd = "/var/lib/certs/package.elm-lang.org/htpasswd"; + cfg.sslCertificate = "/var/lib/certs/package.elm-lang.org/fullchain.pem"; + cfg.sslCertificateKey = "/var/lib/certs/package.elm-lang.org/key.pem"; + + semverRegex = + "(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"; + +in { + services.nginx.virtualHosts."package.elm-lang.org" = { + addSSL = true; + + sslCertificate = cfg.sslCertificate; + sslCertificateKey = cfg.sslCertificateKey; + + locations."/all-packages".extraConfig = '' + proxy_pass http://127.0.0.1:${toString config.krebs.htgen.elm-packages-proxy.port}; + proxy_pass_header Server; + ''; + + locations."/all-packages/since/".extraConfig = '' + proxy_pass http://127.0.0.1:${toString config.krebs.htgen.elm-packages-proxy.port}; + proxy_pass_header Server; + ''; + + locations."~ ^/packages/(?<author>[A-Za-z0-9-]+)/(?<pname>[A-Za-z0-9-]+)/(?<version>${semverRegex})\$".extraConfig = '' + auth_basic "Restricted Area"; + auth_basic_user_file ${cfg.htpasswd}; + + proxy_set_header X-User $remote_user; + proxy_set_header X-Author $author; + proxy_set_header X-Package $pname; + proxy_set_header X-Version $version; + proxy_pass_header Server; + + proxy_pass http://127.0.0.1:${toString config.krebs.htgen.elm-packages-proxy.port}; + ''; + + locations."~ ^/packages/(?<author>[A-Za-z0-9-]+)/(?<pname>[A-Za-z0-9-]+)/(?<version>${semverRegex})/(?:zipball|elm.json|endpoint.json)\$".extraConfig = '' + set $zipball "${cfg.packageDir}/$author/$pname/$version/zipball"; + proxy_set_header X-Author $author; + proxy_set_header X-Package $pname; + proxy_set_header X-Version $version; + proxy_set_header X-Zipball $zipball; + proxy_pass_header Server; + resolver ${cfg.nameserver}; + + if (-f $zipball) { + set $new_uri http://127.0.0.1:${toString config.krebs.htgen.elm-packages-proxy.port}; + } + if (!-f $zipball) { + set $new_uri https://package.elm-lang.org$request_uri; + } + + proxy_pass $new_uri; + ''; + + locations."/search.json".extraConfig = '' + proxy_pass http://127.0.0.1:${toString config.krebs.htgen.elm-packages-proxy.port}; + proxy_pass_header Server; + ''; + }; + + krebs.htgen.elm-packages-proxy = { + port = cfg.port; + script = /* sh */ ''. ${pkgs.writeDash "elm-packages-proxy.sh" '' + PATH=${lib.makeBinPath [ + pkgs.attr + pkgs.coreutils + pkgs.curl + pkgs.findutils + pkgs.gnugrep + pkgs.jq + pkgs.p7zip + ]} + export PATH + file_response() {( + status_code=$1 + status_reason=$2 + file=$3 + content_type=$4 + + content_length=$(wc -c "$file" | cut -d\ -f1) + + printf "HTTP/1.1 $status_code $status_reason\r\n" + printf 'Connection: close\r\n' + printf 'Content-Length: %d\r\n' "$content_length" + printf 'Content-Type: %s\r\n' "$content_type" + printf 'Server: %s\r\n' "$Server" + printf '\r\n' + cat "$file" + )} + string_response() {( + status_code=$1 + status_reason=$2 + response_body=$3 + content_type=$4 + + printf "HTTP/1.1 $status_code $status_reason\r\n" + printf 'Connection: close\r\n' + printf 'Content-Length: %d\r\n' "$(expr ''${#response_body} + 1)" + printf 'Content-Type: %s\r\n' "$content_type" + printf 'Server: %s\r\n' "$Server" + printf '\r\n' + printf '%s\n' "$response_body" + )} + + case "$Method $Request_URI" in + 'GET /packages/'*) + + author=$req_x_author + pname=$req_x_package + version=$req_x_version + + zipball=${cfg.packageDir}/$author/$pname/$version/zipball + elmjson=$HOME/cache/$author%2F$pname%2F$version%2Felm.json + endpointjson=$HOME/cache/$author%2F$pname%2F$version%2Fendpoint.json + mkdir -p "$HOME/cache" + + case $(basename $Request_URI) in + zipball) + file_response 200 OK "$zipball" application/zip + exit + ;; + elm.json) + if ! test -f "$elmjson"; then + 7z x -so "$zipball" \*/elm.json > "$elmjson" + fi + file_response 200 OK "$elmjson" 'application/json; charset=UTF-8' + exit + ;; + endpoint.json) + if ! test -f "$endpointjson"; then + hash=$(sha1sum "$zipball" | cut -d\ -f1) + url=https://package.elm-lang.org/packages/$author/$pname/$version/zipball + jq -n \ + --arg hash "$hash" \ + --arg url "$url" \ + '{ $hash, $url }' \ + > "$endpointjson" + fi + file_response 200 OK "$endpointjson" 'application/json; charset=UTF-8' + exit + ;; + esac + ;; + 'POST /packages/'*) + + author=$req_x_author + pname=$req_x_package + user=$req_x_user + version=$req_x_version + + action=uploading + force=''${req_x_force-false} + zipball=${cfg.packageDir}/$author/$pname/$version/zipball + elmjson=$HOME/cache/$author%2F$pname%2F$version%2Felm.json + endpointjson=$HOME/cache/$author%2F$pname%2F$version%2Fendpoint.json + + if test -e "$zipball"; then + if test "$force" = true; then + zipball_owner=$(attr -q -g X-User "$zipball" || :) + if test "$zipball_owner" = "$req_x_user"; then + action=replacing + rm -f "$elmjson" + rm -f "$endpointjson" + else + string_response 403 Forbidden \ + "package already exists: $author/$pname@$version" \ + text/plain + exit + fi + else + string_response 409 Conflict \ + "package already exists: $author/$pname@$version" \ + text/plain + exit + fi + fi + + echo "user $user is $action package $author/$pname@$version" >&2 + # TODO check package + mkdir -p "$(dirname "$zipball")" + head -c $req_content_length > "$zipball" + + attr -q -s X-User -V "$user" "$zipball" || : + + string_response 200 OK \ + "package created: $author/$pname@$version" \ + text/plain + + exit + ;; + 'DELETE /packages/'*) + + author=$req_x_author + pname=$req_x_package + user=$req_x_user + version=$req_x_version + + zipball=${cfg.packageDir}/$author/$pname/$version/zipball + elmjson=$HOME/cache/$author%2F$pname%2F$version%2Felm.json + endpointjson=$HOME/cache/$author%2F$pname%2F$version%2Fendpoint.json + + if test -e "$zipball"; then + zipball_owner=$(attr -q -g X-User "$zipball" || :) + if test "$zipball_owner" = "$req_x_user"; then + echo "user $user is deleting package $author/$pname@$version" >&2 + rm -f "$elmjson" + rm -f "$endpointjson" + rm "$zipball" + string_response 200 OK \ + "package deleted: $author/$pname@$version" \ + text/plain + exit + else + string_response 403 Forbidden \ + "package already exists: $author/$pname@$version" \ + text/plain + exit + fi + fi + ;; + 'GET /all-packages'|'POST /all-packages') + + response=$(mktemp -t htgen.$$.elm-packages-proxy.all-packages.XXXXXXXX) + trap "rm $response >&2" EXIT + + { + # upstream packages + curl -fsS https://package.elm-lang.org"$Request_URI" + + # private packages + (cd ${cfg.packageDir}; find -mindepth 3 -maxdepth 3) | + jq -Rs ' + split("\n") | + map( + select(.!="") | + match("^\\./(?<author>[^/]+)/(?<pname>[^/]+)/(?<version>[^/]+)$").captures | + map({key:.name,value:.string}) | + from_entries + ) | + reduce .[] as $item ({}; + ($item|"\(.author)/\(.pname)") as $name | + . + { "\($name)": ((.[$name] // []) + [$item.version]) } + ) + ' + } | + jq -cs add > $response + + file_response 200 OK "$response" 'application/json; charset=UTF-8' + exit + ;; + 'GET /all-packages/since/'*|'POST /all-packages/since/'*) + + response=$(mktemp -t htgen.$$.elm-packages-proxy.all-packages.XXXXXXXX) + trap "rm $response >&2" EXIT + + { + # upstream packages + curl -fsS https://package.elm-lang.org"$Request_URI" + + # private packages + (cd ${cfg.packageDir}; find -mindepth 3 -maxdepth 3) | + jq -Rs ' + split("\n") | + map( + select(.!="") | + sub("^\\./(?<author>[^/]+)/(?<pname>[^/]+)/(?<version>[^/]+)$";"\(.author)/\(.pname)@\(.version)") + ) | + sort_by(split("@") | [.[0]]+(.[1]|split(".")|map(tonumber))) | + reverse + ' + } | + jq -cs add > $response + + file_response 200 OK "$response" 'application/json; charset=UTF-8' + exit + ;; + 'GET /search.json') + + searchjson=$HOME/cache/search.json + mkdir -p "$HOME/cache" + + # update cached search.json + ( + last_modified=$( + if test -f "$searchjson"; then + date -Rr "$searchjson" + else + date -R -d @0 + fi + ) + tempsearchjson=$(mktemp "$searchjson.XXXXXXXX") + trap 'rm "$tempsearchjson" >&2' EXIT + curl -fsS --compressed https://package.elm-lang.org/search.json \ + -H "If-Modified-Since: $last_modified" \ + -o "$tempsearchjson" + if test -s "$tempsearchjson"; then + mv "$tempsearchjson" "$searchjson" + trap - EXIT + fi + ) + + response=$(mktemp -t htgen.$$.elm-packages-proxy.search.XXXXXXXX) + trap 'rm "$response" >&2' EXIT + + { + printf '{"upstream":'; cat "$searchjson" + printf ',"private":'; (cd ${cfg.packageDir}; find -mindepth 3 -maxdepth 3) | + jq -Rs ' + split("\n") | + map( + select(.!="") | + match("^\\./(?<author>[^/]+)/(?<pname>[^/]+)/(?<version>[^/]+)$").captures | + map({key:.name,value:.string}) | + from_entries + ) | + map({ + key: "\(.author)/\(.pname)", + value: .version, + }) | + from_entries + ' + printf '}' + } | + jq -c ' + reduce .upstream[] as $upstreamItem ({ private, output: [] }; + .private[$upstreamItem.name] as $privateItem | + if $privateItem then + .output += [$upstreamItem * { version: $privateItem.version }] | + .private |= del(.[$upstreamItem.name]) + else |