#!/bin/sh set -e DYNDNSHOST=cryptonomic.net DEFAULT_UPSTREAM=d@cryptonomic.net:public_git/selfpublish.sh dependencies() { cat <= 2.4.46) basez bind9-dnsutils cgit curl fortune-mod fortunes-min gnupg (>= 2.2.14) libssl1.1 (>= 1.1.1d) netmask openssl ssh EOF } force() { [ "$FORCE" ] } in_group() { local g for g in $(groups) do [ "$g" = "$1" ] && return done false } as_root() { if [ "$(id -u)" = 0 ] then "$@" elif sudo -n true 2>/dev/null || in_group sudo then sudo "$@" else su -c "$*" fi } apt_install() { as_root apt-get install "$@" } write_line_once() { local line="$2" target_file="$1" if [ -e "$target_file" ] && grep -qF "$line" "$target_file" then return else printf %s "$line" | as_root tee -a "$target_file" >/dev/null fi } self_codename() { if which lsb_release >/dev/null then lsb_release -cs else sed -ne 's/^VERSION_CODENAME=//p' /etc/os-release fi } dpkg_install() { case "$(self_codename)" in buster) apt-cache policy | grep -q 'http://httpredir.debian.org/debian buster-backports/main' || write_line_once /etc/apt/sources.list.d/buster-backports.list \ 'deb http://httpredir.debian.org/debian buster-backports main' ;; esac as_root $SHELL -c "set -$- +e; dpkg -i $*; apt-get -t buster-backports -f install" } fmt_dependencies() { local outline= while read inline do if [ ! "$outline" ] then outline="Depends: $inline" elif [ $((${#outline} + ${#inline})) -ge 60 ] then printf '%s,\n' "$outline" outline=" $inline" else outline="$outline, $inline" fi done if [ "$outline" ] then printf '%s\n' "$outline" fi } control_file_file() { printf 'File: %s %s\n' "$1" "$2" sed -e ' s/^[.]/../; s/^$/./; s/^/ /; ' } control_file_file_check() { local prefix RESULT=0 header= data o tempout=$(mktemp) while [ "$header" ] || read header do set -- $header case "$1" in File:) case "$2" in /*) ;; *) return 1 ;; esac :> "$tempout" while IFS= read -r data do case "$data" in ' .'*) printf '%s\n' "${data# .}" >> "$tempout" continue ;; ' '*) printf '%s\n' "${data# }" >> "$tempout" continue ;; esac if ! diff -q "$2" "$tempout" then RESULT=1 as_root install -v -m "$3" "$tempout" -T "$2" fi header=$data continue 2 done ;; esac header= done return $RESULT } SELF_VERSION='0.1' SELF_EXECUTABLE=$(realpath "$0") SELF_PACKAGE=cryptonomic control_file() { cat <}" in uid:u:*.${domain}) $action "$@";; esac } match_first_secret_key() { action=$1 shift is_secret_key || return case "$1:$2" in fpr:*) $action "$@" process_colons_break=y ;; esac } show_fpr() { echo ${CURRENT_FPR} } find_secret_key_with_domain() { process_colons match_domain "$1" show_fpr } force() { [ "$FORCE" ]; } verbose() { if [ "$VERBOSE" ] then "$@" fi } get_home() { [ "$1" ] && getent passwd "$1" | (IFS=: read _ _ _ _ _ h _; echo $h) } GPG() { set -- gpg "$@" if [ "$SUDO_USER" ] then su "$SUDO_USER" -c "$(bash -c 'printf "%q " "$@"' bash "$@")" else "$@" fi } ensure_key_exists() { # 2. CHECK IF EXISTING SECRET KEY t=$(GPG -K --with-colons) if [ "$t" ] then # 3. CHECK IF EXISTING KEY HAS UID if GPG -K --with-colons | find_secret_key_with_domain "${cryptonomic_hostname#*.}" | grep -q . && ! force then return fi # 4. ADD UID TO EXISTING KEY gpg_default_key=$(GPG -K --with-colons | process_colons match_first_secret_key show_fpr) [ "$gpg_default_key" ] GPG --quick-add-uid "$gpg_default_key" "$uid" || force verbose GPG -K "$gpg_default_key" else # 2.5 GENERATE NEW KEY GPG --batch --passphrase '' --quick-generate-key "$uid" verbose GPG -K "$uid" fi } make_directories_maybe_become_root() { if [ -d "$html_dir" ] then if [ -d "$hu_dir" ] then [ -w "$hu_dir" ] || exec sudo -- "$0" "$@" || die "failed to exec sudo" else [ -w "$html_dir" ] || exec sudo -- "$0" "$@" || die "failed to exec sudo" mkdir -m0755 -p "$hu_dir" fi else die "Directory not found: '$html_dir' -- you probably need to run selfpublish.sh" # TODO: just run selfpublish here fi find "$html_dir"/.well-known/openpgpkey/ -xdev -type d -exec chmod 755 '{}' ';' } process_key() { local uid="$1" domain="${1#*@}" destdir="$2" tdir while read keyid some_uid do [ "$some_uid" = "$uid" ] || continue tempdir=$(mktemp -d) /usr/lib/gnupg/gpg-wks-client --install-key -C "$tempdir" "$keyid" "$uid" 2>/dev/null mkdir -p "$destdir" mv "$tempdir"/"$domain"/hu/* -t "$destdir" rm -rf "$tempdir" done } set -e # 1. GET CRYPTONOMIC UID cryptonomic_hostname=$(cryptonomic hostname) [ "$cryptonomic_hostname" ] if [ "$SUDO_USER" ] then username=$SUDO_USER export GNUPGHOME=~$username/.gnupg else username=$(id -un) fi [ "$username" ] uid=${username}@${cryptonomic_hostname} html_dir=/srv/${cryptonomic_hostname#*.}/public_html hu_dir=$html_dir/.well-known/openpgpkey/hu ensure_key_exists make_directories_maybe_become_root GPG --list-options show-only-fpr-mbox -k "$uid" 2>&- | process_key "$uid" "$hu_dir" printf 'gpg --locate-keys %s\n' "$uid" EOF control_file_file /usr/bin/cryptonomic 755 <<'EOF' #!/bin/dash set -e DOMAIN=cryptonomic.net HASH_TYPE=2 HOSTNAME=$(hostname --short) KEY_TYPE=ed25519 KEY_FILE=/etc/ssh/ssh_host_${KEY_TYPE}_key HostKeyAlgorithm=ssh-ed25519 die() { echo "$0: Error: $*" >&2; exit 1; } b16_to_b32() { printf %s "$1" | basez -x -d | basez -j -l | tr -d = } get_domain() { get_sshfp "$1" domain=$(printf %s.%s.%s "$sshfp_b32" "$KEY_TYPE" "$DOMAIN" | tail -c64) domain=$(printf %s.%s "$HOSTNAME" "$domain") } get_sshfp() { [ -f "$1" ] || return sshfp_b16=$(ssh-keygen -r . -f "$1" | sed -ne 's/^. IN SSHFP [0-9]* '"$HASH_TYPE"' //p') && [ "$sshfp_b16" ] || die "could not determine ssh client fingerprint" sshfp_b32=$(b16_to_b32 "$sshfp_b16") } indent() { sed 's/^/\t/' } withsetx() { printf "\n\n\n+ %s\n\n" "$*" "$@" | indent } delegate_command=/usr/lib/cryptonomic/cryptonomic-$1 if [ $# = 0 ] then hostname=$(cryptonomic hostname) uid=$(id -un)@${hostname} 2>/dev/null withsetx ssh-keyscan -t "${HostKeyAlgorithm}" "$hostname" 2>/dev/null withsetx dig +nocmd -taaaa "$hostname" +noall +answer 2>/dev/null withsetx dig +nocmd "$hostname" +noall +answer 2>&1 withsetx gpg --locate-keys "$uid" 2>/dev/null withsetx delv @1.1.1.1 -t sshfp "$hostname" export hostname HostKeyAlgorithm 2>&1 withsetx sh -c 'ssh -v -i /dev/null -o BatchMode=yes -o HostKeyAlgorithms=${HostKeyAlgorithm} -o VerifyHostKeyDNS=yes -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null "$hostname" -- true 2>&1 | egrep "DNS|Server host key|match:|Connecting to|Connection|Authenticating to"' elif [ "$1" = hostname ] then get_domain "${KEY_FILE}".pub || exit printf '%s\n' "$domain" elif [ "$1" = dyndns ] then shift # further command line options are ssh options if [ -z "$(cryptonomic ipv4)" ] then set -- "$@" -6 fi set -- ssh -i "$KEY_FILE" dyndns@"$DOMAIN" "$@" -- "$(hostname)" if [ -r "$KEY_FILE" ] then "$@" elif sudo -n true 2>/dev/null || groups | grep '\bsudo\b' then sudo -- "$@" else su -c "$(bash -c 'printf "%q " "$@"' bash "$@")" fi elif [ -x "$delegate_command" ] then shift exec "$delegate_command" "$@" else echo "Usage: $0 [dyndns|hostname|gpg]" >&2 exit 1 fi EOF control_file_file /usr/bin/selfpublish.sh 755 < "$SELF_EXECUTABLE" echo } in_temp_dir() { ( __tempdir=$(mktemp -d) || return __r=1 trap 'rm -rf "$__tempdir"; (exit $__r)' EXIT cd "$__tempdir" && "$@" __r=$? ) } _control_file_unchanged() { [ "$(dpkg-query -f '${Version}' -W ${SELF_PACKAGE})" = "${SELF_VERSION}" ] || return dependencies > want-dependencies dpkg-query -f '${Depends}\n' -W "${SELF_PACKAGE}" | sed 's/, */\n/g' | sort -u > have-dependencies diff -q want-dependencies have-dependencies } control_file_unchanged() { in_temp_dir _control_file_unchanged "$@" } equivocate() { if dpkg-query -s "${SELF_PACKAGE}" | grep -q '^Status: install ok installed' 2>/dev/null then if control_file_unchanged && control_file | control_file_file_check then if ! force then return fi fi fi which equivs-build >/dev/null 2>&1 || apt_install equivs ( destdir=$(mktemp -d) cd "$destdir" control_file > ./control equivs-build ./control >&2 DEB=${SELF_PACKAGE}_${SELF_VERSION}_all.deb if ! [ "$NO_APT" ] then as_root dpkg -r "${SELF_PACKAGE}" dpkg_install "$DEB" as_root install -m0644 "$DEB" "$DEBDEST" fi ) || exit } ssh_keytag_to_path_fragment() { case "$1" in ssh-dss) echo dsa ;; ecdsa-sha2-nistp256) echo ecdsa ;; ssh-rsa|ssh-ed25519) echo ${1#ssh-} ;; *) return 1 ;; esac } path_fragment_to_ssh_keytag() { case "$1" in ssh-dss|ecdsa-sha2-nistp256|ssh-rsa|ssh-ed25519) echo $1;; dss|rsa|ed25519) echo ssh-$1 ;; dsa) echo ssh-dss ;; ecdsa) echo ecdsa-sha2-nistp256 ;; *) return 1 ;; esac } get_dyndns_domain() { fragment=$(ssh_keytag_to_path_fragment "$1") || return host_keyfile=/etc/ssh/ssh_host_${fragment}_key user_keyfile=$HOME/.ssh/id_${fragment} set -- -q dyndns@"$DYNDNSHOST" "$HOSTNAME" if [ -r "$host_keyfile" ] then set -- ssh -i "$host_keyfile" "$@" elif in_group sudo then set -- sudo ssh -i "$host_keyfile" "$@" elif [ -r "$user_keyfile" ] then set -- ssh -i "$user_keyfile" "$@" else set -- ssh "$@" fi "$@" } enable_apache_modules() { local restart= for MODULE in $APACHE_MODULES do if ! [ -e /etc/apache2/mods-enabled/$MODULE.load ] && ! [ "$MODULE" = cgi -a -e /etc/apache2/mods-enabled/cgid.load ] then a2enmod $MODULE >/dev/null restart=y fi done if [ "$restart" ] then as_root systemctl restart apache2 fi } site_conf_template() { cat < MDContactEmail webmaster@$DOMAIN MDCertificateAgreement accepted MDRequireHttps temporary MDMember $HOSTNAME.$DOMAIN ServerName $DOMAIN ServerAlias $DOMAIN. Redirect / https://$DOMAIN ServerAlias $HOSTNAME.$DOMAIN ServerAlias $HOSTNAME.$DOMAIN. Redirect / https://$HOSTNAME.$DOMAIN ServerName $HOSTNAME.$DOMAIN ServerAlias $DOMAIN ServerAdmin webmaster@$HOSTNAME.$DOMAIN SSLEngine on ErrorLog /srv/$DOMAIN/logs/error.log CustomLog /srv/$DOMAIN/logs/access.log combined SetHandler server-status DocumentRoot /srv/$DOMAIN/public_html/ Options Indexes FollowSymLinks MultiViews Includes IndexOrderDefault Descending Date IndexOptions +IgnoreCase FancyIndexing IndexOptions +HTMLTable SuppressDescription SuppressHTMLPreamble IndexStyleSheet /css/autoindex.css IndexIgnore /unindexed /HEADER.html /css /images # Using an absolute url for header HeaderName /HEADER.html XBitHack on AllowOverride None Require all granted ScriptAlias /git "/usr/lib/cgit/cgit.cgi/" Alias /cgit-css "/usr/share/cgit/" END } wait_for_certificate_issuance() { local f state reloaded_apache= f=/etc/apache2/md/domains/"$1"/md.json local set=$- set +x echo -n Waiting for certificate... >&2 while true do if [ -e "$f" ] && state=$(sed -ne 's/^ *"state": *\([0-9]\+\),/\1/p' "$f") then case "$state" in 1) if ! [ "$reloaded_apache" ] then as_root systemctl reload apache2 reloaded_apache=y fi ;; 2) set -$set echo ' Done waiting for certificate.' >&2 return ;; esac fi sleep 1 echo -n . >&2 done } install_apache_vhost() { if [ -e "$SITE_CONF" ] && ! force then return fi for DIR in $APACHE_SITE_DIRS do mkdir -p "$SITE_DIR"/"$DIR" done local tmp tmp=$(mktemp "$SITE_CONF".XXXXXX) site_conf_template > "$tmp" mv -T "$tmp" "$SITE_CONF" || { rm -f "$tmp"; false; } a2ensite "$DOMAIN" >/dev/null } install_self_to_site() { SOURCE_BASENAME=${0##*/} [ -d "$SITE_DIR"/public_html ] || return dst=$SITE_DIR/public_html/$SOURCE_BASENAME src=$0 [ -e "$src" ] || return 0 if [ ! "$src" -ef "$dst" ] then cp -Tuv "$src" "$dst" >&2 cp -Tuv "$src" "$dst".txt >&2 fi DEB="${SELF_PACKAGE}_${SELF_VERSION}_all.deb" if [ -e "$DEB" ] then cp -Tuv "$DEB" "$SITE_DIR/public_html/$DEB" fi } get_upstream() { if [ -e selfpublish.sh -a -e .git ] then UPSTREAM=$(git config --get branch.master.remote 2>/dev/null) case "$UPSTREAM" in *:*) ;; *) UPSTREAM=$(git remote get-url "$UPSTREAM") ;; esac fi UPSTREAM=${UPSTREAM:-$DEFAULT_UPSTREAM} [ "$UPSTREAM" ] } write_cgit_config() { get_upstream cgit_scan_dir=/srv/public_git if ! [ -e "$cgit_scan_dir"/selfpublish.sh ] then mkdir -p "$cgit_scan_dir" (cd "$cgit_scan_dir" GIT_SSH_COMMAND='ssh -i /etc/ssh/ssh_host_ed25519_key' \ git clone --bare "$UPSTREAM" selfpublish.sh) else git push --all "$cgit_scan_dir"/selfpublish.sh fi line="scan-path=$cgit_scan_dir" grep -qxF "$line" /etc/cgitrc || printf '%s\n' "$line" >> /etc/cgitrc } install_tls_public_certificate() { local src=/etc/apache2/md/domains/"$DOMAIN"/pubcert.pem dst="$SITE_DIR"/public_html/pubcert.pem if [ "$src" -nt "$dst" ] then cp -Tuv /etc/apache2/md/domains/"$DOMAIN"/pubcert.pem "$SITE_DIR"/public_html/pubcert.pem openssl x509 -in "$dst" -noout -text > "$dst".txt fi } install_gpg_rings() { cryptonomic gpg } configure_apache_vhost() { enable_apache_modules install_self_to_site install_header_to_site install_tls_public_certificate install_gpg_rings write_cgit_config } install_header_to_site() { cat > "$SITE_DIR"/public_html/HEADER.html <<'EOF' selfpublish.sh at <!--#echo var="SERVER_NAME" -->
selfpublish.sh
curl -OR https:///selfpublish.sh && sudo bash selfpublish.sh
Distribution Debian GNU/Linux
Version buster or later
Local repository: https:///git/selfpublish.sh
Upstream repository: https://git.cryptonomic.net/selfpublish.sh/
Download single file: https:///selfpublish.sh
Clone local repository: git clone /srv//public_git/selfpublish.sh/ && cd selfpublish.sh && make
Share local repository (read-only): git clone https:///git/selfpublish.sh && cd selfpublish.sh && make
To push changes upstream: git clone d@cryptonomic.net:public_git/selfpublish.sh
Local git repository: git clone https:///git/selfpublish.sh
Fortunately,


EOF chmod +x "$SITE_DIR"/public_html/HEADER.html } check_tls() { local flags=$- set +e curl -s -S -I https://"$1" >/dev/null check_tls_result=$? set $flags } DEBDEST=$(realpath .) [ "$NO_EQUIVS" ] || equivocate >&2 APACHE_MODULES='status md rewrite ssl include cgi' APACHE_SITE_DIRS='logs public_html' HOSTNAME=$(hostname --short) DOMAIN=$(cryptonomic hostname) cryptonomic dyndns SITE_DIR=/srv/${DOMAIN#$HOSTNAME.} SITE_CONF=/etc/apache2/sites-available/${DOMAIN#$HOSTNAME.}.conf check_tls "$DOMAIN" tls_result=$? install_apache_vhost configure_apache_vhost if [ $tls_result != 0 ] || force then as_root systemctl restart apache2 wait_for_certificate_issuance "$DOMAIN" as_root systemctl reload apache2 || as_root systemctl restart apache2 else as_root systemctl reload apache2 || as_root systemctl restart apache2 fi check_tls "$DOMAIN" printf '%s\n' "https://$DOMAIN/selfpublish.sh"