summaryrefslogtreecommitdiffstats
path: root/configs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2023-09-11 18:24:28 +0200
committertv <tv@krebsco.de>2023-09-13 18:07:11 +0200
commit0c4f3acb281be6290c55a6e96bc29fab5b5c7a11 (patch)
treedadaec00477a095273475ac345b2066b4748c399 /configs
parentab1d0479e90f11806d4703ec6fffed3d5f782914 (diff)
stockholm -> hrm
Diffstat (limited to 'configs')
-rw-r--r--configs/autotether.nix19
-rw-r--r--configs/backup.nix108
-rw-r--r--configs/bash/default.nix66
-rw-r--r--configs/binary-cache/default.nix28
-rw-r--r--configs/br.nix49
-rw-r--r--configs/default.nix131
-rw-r--r--configs/elm-packages-proxy.nix359
-rw-r--r--configs/exim-retiolum.nix8
-rw-r--r--configs/exim-smarthost.nix45
-rw-r--r--configs/fs/CAC-CentOS-7-64bit.nix20
-rw-r--r--configs/gitconfig.nix15
-rw-r--r--configs/gitrepos.nix237
-rw-r--r--configs/htop.nix39
-rw-r--r--configs/hw/AO753.nix47
-rw-r--r--configs/hw/winmax2.nix48
-rw-r--r--configs/hw/x220.nix88
-rw-r--r--configs/imgur.nix21
-rw-r--r--configs/initrd/sshd.nix17
-rw-r--r--configs/mail-client.nix9
-rw-r--r--configs/man.nix13
-rw-r--r--configs/nets/hkw.nix68
-rw-r--r--configs/networkd.nix4
-rw-r--r--configs/nginx/default.nix21
-rw-r--r--configs/nginx/public_html.nix17
-rw-r--r--configs/nix.nix9
-rw-r--r--configs/pki/certs/tv.crt31
-rw-r--r--configs/pki/default.nix67
-rw-r--r--configs/ppp.nix85
-rw-r--r--configs/pulse.nix119
-rw-r--r--configs/repo-sync/wiki.nix39
-rw-r--r--configs/retiolum.nix26
-rw-r--r--configs/smartd.nix17
-rw-r--r--configs/ssh.nix21
-rw-r--r--configs/sshd.nix26
-rw-r--r--configs/urlwatch.nix118
-rw-r--r--configs/vim.nix184
-rw-r--r--configs/weechat-server.nix24
-rw-r--r--configs/wiregrill.nix36
-rw-r--r--configs/xdg.nix10
-rw-r--r--configs/xserver/Xmodmap.nix28
-rw-r--r--configs/xserver/default.nix166
-rw-r--r--configs/xserver/sxiv.nix11
-rw-r--r--configs/xserver/urxvt.nix72
-rw-r--r--configs/xserver/xkiller.nix14
-rw-r--r--configs/xserver/xserver.conf.nix38
-rw-r--r--configs/xsessions/default.nix5
-rw-r--r--configs/xsessions/urxvtd.nix15
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(.!="") |
+