diff options
Diffstat (limited to 'src')
30 files changed, 2313 insertions, 0 deletions
diff --git a/src/ci/git-post-receive.sh b/src/ci/git-post-receive.sh new file mode 100755 index 0000000..46bf5a5 --- /dev/null +++ b/src/ci/git-post-receive.sh @@ -0,0 +1,178 @@ +#!/bin/sh +# shellcheck source=/dev/null disable=2317 +. /etc/rc +set -eu + + +# shellcheck disable=2034 +read -r _oldrev SHA REFNAME + +if [ "$SHA" = '0000000000000000000000000000000000000000' ]; then + exit +fi + + +SKIP_DEPLOY=false +for n in $(seq 0 $((GIT_PUSH_OPTION_COUNT - 1))); do + opt="$(eval "printf '%s' \"\$GIT_PUSH_OPTION_$n\"")" + case "$opt" in + ci.skip) + cat <<-EOF + + "$opt" option detected, not running CI. + + EOF + exit + ;; + deploy.skip) + SKIP_DEPLOY=true + ;; + *) + ;; + esac +done + + +epoch() { + awk 'BEGIN { srand(); print(srand()); }' +} + +now() { + date '+%Y-%m-%dT%H:%M:%S%:z' +} + +NAME="$(basename "$PWD" .git)" +LOGS_DIR=/var/log/ci/"$NAME"/ +TIMESTAMP="$(now)" +FILENAME="$TIMESTAMP-$SHA.log" +LOGFILE="$LOGS_DIR/$FILENAME" +mkdir -p "$LOGS_DIR" + + +END_MARKER='\033[0m' +LIGHT_BLUE_B='\033[1;36m' +YELLOW='\033[1;33m' + +blue() { + printf "${LIGHT_BLUE_B}%s${END_MARKER}" "$1" +} + +yellow() { + printf "${YELLOW}%s${END_MARKER}" "$1" +} + +info() { + sed "s|^\(.\)|$(blue 'CI'): \1|" +} + + +uuid() { + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +tmpname() { + printf '%s/uuid-tmpname with spaces.%s' "${TMPDIR:-/tmp}" "$(uuid)" +} + +mkdtemp() { + name="$(tmpname)" + mkdir -- "$name" + printf '%s' "$name" +} + + +{ + cat <<-EOF | info + Starting CI job at: $(now) + EOF + START="$(epoch)" + + duration() { + if [ "$RUN_DURATION" -gt 60 ]; then + cat <<-EOF + $(yellow 'WARNING'): run took more than 1 minute! ($RUN_DURATION seconds) + EOF + else + cat <<-EOF + Run took $RUN_DURATION seconds. + EOF + fi + } + + finish() { + STATUS="$?" + END="$(epoch)" + RUN_DURATION=$((END - START)) + cat <<-EOF | info + Finishing CI job at: $(now) + Exit status was $STATUS + Re-run with: + \$ $CMD + $(duration) + EOF + + NOTE="$( + cat <<-EOF + See CI logs with: + git notes --ref=refs/notes/ci-logs show $SHA + git notes --ref=refs/notes/ci-data show $SHA + + Exit status: $STATUS + Duration: $RUN_DURATION + EOF + )" + git notes --ref=refs/notes/ci-data add -f -m "$( + cat <<-EOF + status $STATUS + sha $SHA + filename $FILENAME + duration $RUN_DURATION + timestamp $TIMESTAMP + to-prod $TO_PROD + refname $REFNAME + EOF + )" "$SHA" + git notes --ref=refs/notes/ci-logs add -f -F "$LOGFILE" "$SHA" + git notes add -f -m "$NOTE" "$SHA" + + { + printf 'Git CI HTML report for %s (%s) started.\n' "$NAME" "$SHA" >&2 + DIR="$(mkdtemp)" + report -o "$DIR" + sudo -u deployer rsync \ + --chmod=D775,F664 \ + --chown=deployer:deployer \ + --delete \ + -a \ + "$DIR"/ "$HTML_OUTDIR_CI"/ + rm -rf "$DIR" + printf 'Git CI HTML report for %s (%s) finished.\n' "$NAME" "$SHA" >&2 + } 2>&1 | logger -i -p local0.warn -t git-ci 1>/dev/null 2>&1 & + } + trap finish EXIT + + unset GIT_DIR + + if [ "$REFNAME" = 'refs/heads/main' ] && [ "$SKIP_DEPLOY" = false ]; then + cat <<-EOF | info + In branch "main", running deploy for $SHA. + EOF + TO_PROD=true + CMD="sudo reconfigure $SHA" + else + if [ "$SKIP_DEPLOY" = true ]; then + cat <<-EOF | info + "deploy.skip" option detected, skipping deploy for $SHA. + EOF + else + cat <<-EOF | info + Not on branch "main", skipping deploy for $SHA. + EOF + fi + TO_PROD=false + CMD="sudo reconfigure -n $SHA" + fi + $CMD +} 2>&1 | ts -s '%.s' | tee "$LOGFILE" diff --git a/src/ci/git-pre-receive.sh b/src/ci/git-pre-receive.sh new file mode 100755 index 0000000..8cd83ee --- /dev/null +++ b/src/ci/git-pre-receive.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -eu + +read -r _oldrev SHA _refname +unset GIT_DIR + +if [ "$SHA" = '0000000000000000000000000000000000000000' ]; then + exit +fi + +printf 'Upgrading post-receive hook...' >&2 +git show "$SHA":src/infrastructure/ci/git-post-receive.sh > hooks/post-receive +chmod +x hooks/post-receive +printf 'done.\n' >&2 diff --git a/src/config/conf.env.in b/src/config/conf.env.in new file mode 100644 index 0000000..e54daef --- /dev/null +++ b/src/config/conf.env.in @@ -0,0 +1,16 @@ +#!/bin/sh + +NAME='@NAME@' +TLD='@TLD@' +URL='@URL@' +OFFSITE_SSH='@OFFSITE_SSH@' +OUT_SUFFIX='' +PRIV_SUFFIX='' +CI_SUFFIX="ci" + +HTML_OUTDIR_TOP="/srv/www/$OUT_SUFFIX" +HTML_OUTDIR_PRIV="$HTML_OUTDIR_TOP$PRIV_SUFFIX" +HTML_OUTDIR_CI="$HTML_OUTDIR_TOP/$CI_SUFFIX" +HOMEPAGE="https://$TLD/$OUT_SUFFIX/" +CGIT_URL="https://$TLD/git/$NAME/commit/?id=" +REPO_NAME="$NAME.git" diff --git a/src/config/gitconfig b/src/config/gitconfig new file mode 100644 index 0000000..f1f1eb3 --- /dev/null +++ b/src/config/gitconfig @@ -0,0 +1,11 @@ +[init] + defaultBranch = main +[user] + email = ci@$TLD + name = "Git CI" +[advice] + detachedHead = false +[receive] + advertisePushOptions = true +[uploadpack] + allowAnySHA1InWant = true diff --git a/src/config/init.scm b/src/config/init.scm new file mode 100644 index 0000000..9e962e8 --- /dev/null +++ b/src/config/init.scm @@ -0,0 +1,6 @@ +(use-modules + (ice-9 colorized) + (ice-9 readline)) + +(activate-colorized) +(activate-readline) diff --git a/src/config/known_hosts.txt b/src/config/known_hosts.txt new file mode 100644 index 0000000..74ba219 --- /dev/null +++ b/src/config/known_hosts.txt @@ -0,0 +1,5 @@ +# rsync.net public keys +# Verified in 2023-03-08 at: +# https://www.rsync.net/resources/fingerprints.txt + +hk-s020.rsync.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILcPl9x9JfRFwsn09NnDw/xBZbAN80ZQck+h6AqlVqPH diff --git a/src/config/profile.sh b/src/config/profile.sh new file mode 100644 index 0000000..1dca8b2 --- /dev/null +++ b/src/config/profile.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /etc/rc +ln -fs .profile .bashrc diff --git a/src/config/rc.sh b/src/config/rc.sh new file mode 100644 index 0000000..b44d3d1 --- /dev/null +++ b/src/config/rc.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /etc/profile +. /etc/conf.env + +export XDG_PREFIX=~/.usr +export XDG_CACHE_HOME="$XDG_PREFIX"/var/cache +export XDG_CONFIG_HOME="$XDG_PREFIX"/etc +export XDG_DATA_HOME="$XDG_PREFIX"/share +export XDG_STATE_HOME="$XDG_PREFIX"/state +export XDG_LOG_HOME="$XDG_PREFIX"/var/log + + +HOME_PARENT="$(dirname "$HOME")" +if [ "$HOME_PARENT" = '/home' ] || [ "$HOME_PARENT" = '/' ]; then + mkdir -p \ + "$XDG_CONFIG_HOME" \ + "$XDG_CACHE_HOME" \ + "$XDG_DATA_HOME" \ + "$XDG_LOG_HOME" \ + "$XDG_STATE_HOME"/ssh/conn +fi + + +GUIX_PROFILE="$XDG_CONFIG_HOME"/guix/current +if [ -r "$GUIX_PROFILE"/etc/profile ]; then + # shellcheck source=/dev/null + . "$GUIX_PROFILE"/etc/profile +fi + +export ENV=~/.profile +export HISTSIZE=-1 +export HISTCONTROL=ignorespace:ignoredups +export EDITOR=vi +export VISUAL="$EDITOR" +export PAGER='less -R' + +export EXINIT=' + " set number + " set autoindent + set ruler + set showmode + set showmatch +' + +export HISTFILE="$XDG_STATE_HOME"/bash-history +export LESSHISTFILE="$XDG_STATE_HOME"/lesshst +export RLWRAP_HOME="$XDG_CACHE_HOME"/rlwrap +export GUILE_HISTORY="$XDG_STATE_HOME"/guile-history + +HOSTNAME="$(hostname)" +export BORG_REPO="$OFFSITE_SSH:borg/$HOSTNAME" +export BORG_REMOTE_PATH='borg1' +export BORG_PASSCOMMAND='cat /opt/secrets/borg-passphrase.txt' + +export GIT_CONFIG_GLOBAL=/etc/gitconfig + +unalias -a +alias l='ls -lahF --color' +alias grep='grep --color=auto' +alias diff='diff --color=auto' +alias watch='watch --color ' +alias man='MANWIDTH=$((COLUMNS > 80 ? 80 : COLUMNS)) man' +alias less='less -R' +alias tree='tree -aC' +alias mv='mv -i' +alias e='vi' + +alias sqlite='rlwrap sqlite3' +alias guile='guile -l /etc/init.scm' + +error_marker() { + STATUS=$? + if [ "$STATUS" != 0 ]; then + printf ' (!! %s !!) ' "$STATUS" + fi +} +export PS1='`error_marker`\T \w/ +\u@\H\$ ' diff --git a/src/config/ssh.conf b/src/config/ssh.conf new file mode 100644 index 0000000..ca41df0 --- /dev/null +++ b/src/config/ssh.conf @@ -0,0 +1,6 @@ +Host * + ServerAliveInterval 30 + ServerAliveCountMax 20 + ControlMaster auto + ControlPath ${XDG_STATE_HOME}/ssh/conn/%r@%h:%p + ControlPersist 1h diff --git a/src/guix/channels.scm b/src/guix/channels.scm new file mode 100644 index 0000000..16a9c7d --- /dev/null +++ b/src/guix/channels.scm @@ -0,0 +1,12 @@ +(append + (list + (channel + (name 'org-euandre) + (url "git://euandre.org/package-repository") + (branch "main") + (introduction + (make-channel-introduction + "d749e053e6db365069cb9b2ef47a78b06f9e7361" + (openpgp-fingerprint + "5BDA E9B8 B2F6 C6BC BB0D 6CE5 81F9 0EC3 CD35 6060"))))) + %default-channels) diff --git a/src/guix/system.scm b/src/guix/system.scm new file mode 100644 index 0000000..5e16737 --- /dev/null +++ b/src/guix/system.scm @@ -0,0 +1,474 @@ +(use-modules + ((ice-9 textual-ports) #:prefix textual-ports:) + ((srfi srfi-1) #:prefix s1:) + ((xyz euandreh heredoc) #:prefix heredoc:) + ((org euandre queue) #:prefix queue:) + (gnu) + (guix build-system trivial) + (guix packages)) +(use-package-modules + admin + ssh + version-control) +(use-service-modules + admin + certbot + cgit + dns + mail + mcron + networking + security + ssh + web) +(heredoc:enable-syntax) + + +(define +working-dir+ + (canonicalize-path ".")) + +(define (str . rest) + (apply string-append rest)) + +(define (fmt . rest) + (apply format #f rest)) + +(define (path s) + (str +working-dir+ "/" s)) + +(define (slurp s) + (call-with-input-file + s + textual-ports:get-string-all)) + +(define file + (compose slurp path)) + +(define +tld+ + (string-trim-right + (slurp (path "tld.txt")))) + +(define +ipv4+ "216.238.73.1") +(define +ipv6+ "2001:19f0:b400:1582:5400:04ff:fea9:370e") + +(define +users+ + '(("andre" "EuAndreh" ("wheel" "become-deployer" "become-secrets-keeper")) + ("laisse" "Laísses" ()))) + +(define +user-accounts+ + (map (lambda (user) + (let ((name (s1:first user)) + (comment (s1:second user)) + (groups (s1:third user))) + (user-account + (name name) + (comment comment) + (group "users") + (supplementary-groups groups)))) + +users+)) + +(define (ssh-file-for user) + (let ((name (s1:first user))) + (path (fmt "src/keys/SSH/~a.pub.txt" name)))) + +(define +authorized-keys+ + (let ((users-with-keys + (map (lambda (user) + `(,@user ,(slurp (ssh-file-for user)))) + (filter (lambda (user) + (file-exists? (ssh-file-for user))) + +users+)))) + (append + (map (lambda (user) + (let ((name (s1:first user)) + (key (s1:fourth user))) + `(,name ,(plain-file (str name "-id_rsa.pub") + key)))) + users-with-keys) + `(("git" ,@(map (lambda (user) + (let ((name (s1:first user)) + (key (s1:fourth user))) + (plain-file (str name "-git-id_rsa.pub") + key))) + users-with-keys)))))) + +(define (script name content) + (package + (name name) + (version "latest") + (source #f) + (build-system trivial-build-system) + (arguments + (list + #:modules '((guix build utils)) + #:builder + #~(begin + (use-modules (guix build utils)) + (let* ((bin (string-append %output "/bin")) + (prog (string-append bin "/" #$name))) + (mkdir-p bin) + (call-with-output-file prog + (lambda (port) + (display #$content port))) + (chmod prog #o755))))) + (home-page #f) + (synopsis #f) + (description #f) + (license #f))) + + +(define ns1 (fmt "ns1.~a." +tld+)) +(define ns2 (fmt "ns2.~a." +tld+)) +(define ns ns1) +(define mail (fmt "hostmaster.~a." +tld+)) +(define dkim-selector "dkimproxyout") +(define dkim-public-key-path "/var/lib/dkimproxyout/public.key") + +(define dkim-name (str dkim-selector "._domainkey")) +(define dkim-public-key + (if (file-exists? dkim-public-key-path) + (string-join + (reverse + (cdr + (reverse + (cdr + (string-split (slurp dkim-public-key-path) + #\newline))))) + "") + "stub-public-key-for-building")) + +(define ipv4-reverse-domain + (str + (string-join (reverse + (string-split +ipv4+ + #\.)) + ".") + ".in-addr.arpa")) + +(define ipv6-reverse-domain + (str + (string-join (reverse + (map (lambda (s) (fmt "~a" s)) + (string->list + (string-delete #\: +ipv6+)))) + ".") + ".ip6.arpa")) + +(define-zone-entries tld-zone + ("@" "" "IN" "NS" ns1) + ("@" "" "IN" "NS" ns2) + ("ns1" "" "IN" "A" +ipv4+) + ("ns1" "" "IN" "AAAA" +ipv6+) + ("ns2" "" "IN" "A" +ipv4+) + ("ns2" "" "IN" "AAAA" +ipv6+) + + ("@" "" "IN" "A" +ipv4+) + ("@" "" "IN" "AAAA" +ipv6+) + + ("@" "" "IN" "CAA" "0 issue \"letsencrypt.org\"") + ("@" "" "IN" "CAA" "0 issuewild \";\"") + ("@" "" "IN" "CAA" (fmt "0 iodef \"mailto:root@~a\"" +tld+)) + + ("mta-sts" "" "IN" "A" +ipv4+) + ("mta-sts" "" "IN" "AAAA" +ipv6+) + ("_mta-sts" "" "IN" "TXT" "\"v=STSv1; id=20230314\"") + ("@" "" "IN" "MX" (fmt "10 ~a." +tld+)) + ("_dmarc" "" "IN" "TXT" "\"v=DMARC1; p=quarantine\"") + ("@" "" "IN" "TXT" (fmt "\"v=spf1 a:~a -all\"" +tld+)) + (dkim-name "" "IN" "TXT" (fmt "\"v=DKIM1; k=rsa; t=s; p=~a\"" dkim-public-key))) + +(define-zone-entries ipv4-reverse-domain-zone + ("@" "" "IN" "PTR" (str +tld+ ".")) + ("@" "" "IN" "NS" ns1) + ("@" "" "IN" "NS" ns2)) + +(define-zone-entries ipv6-reverse-domain-zone + ("@" "" "IN" "PTR" (str +tld+ ".")) + ("@" "" "IN" "NS" ns1) + ("@" "" "IN" "NS" ns2)) + +(define zones + (list + (knot-zone-configuration + (domain +tld+) + (semantic-checks? #t) + (zone + (zone-file + (origin +tld+) + (ns ns) + (mail mail) + (entries tld-zone)))) + (knot-zone-configuration + (domain ipv4-reverse-domain) + (semantic-checks? #t) + (zone + (zone-file + (origin ipv4-reverse-domain) + (ns ns) + (mail mail) + (entries ipv4-reverse-domain-zone)))) + (knot-zone-configuration + (domain ipv6-reverse-domain) + (semantic-checks? #t) + (zone + (zone-file + (origin ipv6-reverse-domain) + (ns ns) + (mail mail) + (entries ipv6-reverse-domain-zone)))))) + + +(operating-system + (locale "en_GB.UTF-8") + (timezone "America/Sao_Paulo") + (host-name +tld+) + (skeletons + `((".profile" + ,(plain-file + "user-profile" + (file "src/config/profile.sh"))))) + (users + (append + (list + (user-account + (name "git") + (group "git") + (system? #t) + (comment "External SSH Git user") + (home-directory "/srv/git") + (create-home-directory? #f) + (shell + (file-append git "/bin/git-shell"))) + (user-account + (name "deployer") + (group "deployer") + (system? #t) + (comment "The account used to run deployment commands") + (home-directory "/var/empty") + (create-home-directory? #f) + (shell + (file-append shadow "/sbin/nologin"))) + (user-account + (name "secrets-keeper") + (group "secrets-keeper") + (system? #t) + (comment "The account used to manage production secrets") + (home-directory "/var/empty") + (create-home-directory? #f) + (shell + (file-append shadow "/sbin/nologin")))) + +user-accounts+ + %base-user-accounts)) + (groups + (append + (list + (user-group + (name "git") + (system? #t)) + (user-group + (name "deployer") + (system? #t)) + (user-group + (name "become-deployer") + (system? #t)) + (user-group + (name "secrets-keeper") + (system? #t)) + (user-group + (name "become-secrets-keeper") + (system? #t))) + %base-groups)) + (sudoers-file + (plain-file "sudoers" #"- + root ALL=(ALL) ALL + %wheel ALL= ALL + %become-deployer ALL=(deployer) NOPASSWD: ALL + %become-secrets-keeper ALL=(secrets-keeper) NOPASSWD: /run/current-system/profile/bin/rsync, /run/current-system/profile/bin/setfacl, /run/current-system/profile/bin/rm + git ALL= NOPASSWD: /run/current-system/profile/bin/reconfigure, /run/current-system/profile/bin/cicd + git ALL=(deployer) NOPASSWD: /run/current-system/profile/bin/rsync, /run/current-system/profile/bin/mkdir + "#)) + (packages + (append + (map + (compose list specification->package+output symbol->string) + '(nss-certs + guile-heredoc + parted + acl + bind:utils + knot:tools + file + git + lsof + mailutils-sendmail + curl + make + borg + rsync + sqlite + strace + rlwrap + trash-cli + tree)) + (list + (script "gc" (file "src/scripts/gc.sh")) + (script "cicd" (file "src/scripts/cicd.sh")) + (script "check" (file "src/scripts/check.sh")) + (script "backup" (file "src/scripts/backup.sh")) + (script "deploy" (file "src/scripts/deploy.sh")) + (script "report" (file "src/scripts/report.sh")) + (script "cronjob" (file "src/scripts/cronjob.sh")) + (script "reconfigure" (file "src/scripts/reconfigure.sh"))) + %base-packages)) + (services + (append + (list + (service ntp-service-type) + (service dhcp-client-service-type) + (service knot-service-type + (knot-configuration + (zones zones))) + (service openssh-service-type + (openssh-configuration + (openssh openssh-sans-x) + (password-authentication? #f) + (authorized-keys +authorized-keys+) + (extra-content #"- + ClientAliveInterval 30 + ClientAliveCountMax 20 + MaxSessions 20 + SetEnv GIT_CONFIG_GLOBAL=/etc/gitconfig + "#))) + (simple-service 'extra-rottlog-rotations rottlog-service-type + (list + (log-rotation + (frequency 'weekly) + (files '("/var/log/cronjobs.log")) + (options '("rotate 52"))))) + (service fail2ban-service-type) + (service mcron-service-type + (mcron-configuration + (jobs + (list + #~(job "0 0 * * *" "cronjob check") + #~(job "0 1 * * *" "cronjob env BORG_REPO=/mnt/backup/borg backup -q cron") + #~(job "0 2 * * *" "cronjob backup -q cron") + #~(job "0 3 * * 0" "cronjob gc") + #~(job "0 4 * * *" "cronjob reconfigure -U"))))) + (service certbot-service-type + (certbot-configuration + (email (str "root@" +tld+)) + (certificates + (list + (certificate-configuration + (domains (list +tld+)) + (deploy-hook + (program-file + "nginx-deploy-hook" + #~(let ((pid (call-with-input-file "/var/run/nginx/pid" read))) + (kill pid SIGHUP))))))))) + (service nginx-service-type + (nginx-configuration + (server-blocks + (list + (nginx-server-configuration + (server-name (list +tld+)) + (listen '("[::]:443 ssl http2" "443 ssl http2")) + (root "/srv/www") + (ssl-certificate (fmt "/etc/letsencrypt/live/~a/fullchain.pem" +tld+)) + (ssl-certificate-key (fmt "/etc/letsencrypt/live/~a/privkey.pem" +tld+)) + (locations + (list + (nginx-location-configuration + (uri "/api/") + (body + (list +#; + (fmt "include /var/run/~a/curr.conf;~%" +tld+)))) + (nginx-location-configuration + (uri "/git/static/") + (body + (list + (list "alias " cgit "/share/cgit/;")))) + (nginx-location-configuration + (uri "/git/") + (body + (list + (list "fastcgi_param SCRIPT_FILENAME " cgit "/lib/cgit/cgit.cgi;") + #"- + fastcgi_param PATH_INFO $uri; + fastcgi_param QUERY_STRING $args; + fastcgi_param HTTP_HOST $server_name; + fastcgi_pass localhost:9000; + rewrite /git(.*) $1 break; + "#))))) + (raw-content + '(#"- + # BearSSL still doesn't do TLSv1.3, so we deem TLSv1.2 as + # acceptable + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH; + ssl_prefer_server_ciphers on; + gzip off; # Disable catch-all compression due to BREACH + charset UTF-8; + autoindex on; + add_header Strict-Transport-Security 'max-age=31536000; includeSubdomains' always; + "#))))))) + (service cgit-service-type queue:cgit-pre-configuration) + (simple-service 'extra-etc-file etc-service-type + `(("rc" ,(plain-file "rc.sh" (file "src/config/rc.sh"))) + ("known_hosts" ,(plain-file "known_hosts" (file "src/config/known_hosts.txt"))) + ("id_rsa.pub" ,(plain-file "id_rsa.pub" (file (fmt "src/keys/SSH/root@~a.id_rsa.pub.stripped" +tld+)))) + ("ssh.conf" ,(plain-file "ssh.conf" (file "src/config/ssh.conf"))) + ("init.scm" ,(plain-file "init.scm" (file "src/config/init.scm"))) + ("conf.env" ,(plain-file "conf.env" (file "src/config/conf.env"))) + ("gitconfig" ,(plain-file "gitconfig" (file "src/config/gitconfig"))))) + (service queue:shadow-group-service-type) + (service queue:dkimproxyout-service-type) + (service queue:cyrus-sasl-service-type) + (service queue:dovecot-service-type) + (service queue:internet-postfix-service-type + (queue:postfix-configuration + (enable-submission? #t) + (main.cf-extra #"- + message_size_limit = 102400000 + mailbox_size_limit = 5120000000 + "#))) + (service mail-aliases-service-type + `(("root" "andre") + ("support" ,@(map s1:first +users+))))) + (modify-services %base-services + (rottlog-service-type config => + (rottlog-configuration + (inherit config) + (rc-file + (file-append queue:rottlog-mailutils-sendmail "/etc/rc"))))))) + (bootloader + (bootloader-configuration + (bootloader grub-bootloader) + (targets '("/dev/vda")))) + (swap-devices + (list + (swap-space + (target + (uuid "fde5e4a8-acc2-4c9a-9712-5494724c2c04"))))) + (file-systems + (append + (list + (file-system + (mount-point "/") + (device + (uuid "da72be6a-0c6b-4874-a57f-2046fcba13af" 'btrfs)) + (type "btrfs")) + (file-system + (mount-point "/mnt/production") + (needed-for-boot? #t) + (device + (uuid "c50ad9fa-c7a1-49a1-93d2-6633f3cf929f" 'btrfs)) + (type "btrfs")) + (file-system + (mount-point "/mnt/backup") + (device + (uuid "d675e98c-3f48-44d1-b085-36c476d9313f" 'btrfs)) + (type "btrfs"))) + %base-file-systems))) diff --git a/src/keys/GPG/andre.asc b/src/keys/GPG/andre.asc new file mode 100644 index 0000000..9164cbd --- /dev/null +++ b/src/keys/GPG/andre.asc @@ -0,0 +1,86 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +s5K/1f77Aqr6hdYRfw5KqGs= +=kMg0 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/keys/SSH/andre.pub.txt b/src/keys/SSH/andre.pub.txt new file mode 100644 index 0000000..bfd5e6f --- /dev/null +++ b/src/keys/SSH/andre.pub.txt @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDnUv7iWOejQNa3fZ6v4lkHT6qFRp2+NuzIpFJ2Vy7eP58XZoiz6HJPcCU8Hf95JXwaXEwS4S7mXdw1x60hd8JIe058Ek6MZSSVQmlLfocGsAYj1wTrLmnQ8+PV0IeQlNj1aytBI1fL+v3IPt+JdLt6b+g3vwcEUU9efzxx2E0KZ5GIpb2meiCQ6ha+tcd7XqegB53eQj/h/coE2zLJodpaJ3xbj894pE/OJCNC0+4d0Sv7oHhY7QoLYldTQbSgPyhyfl4iZpJf6OEPZxK2cJaB+cbeoBB6aGNyU+CIJToM+uAJJ7H7EpvxfcnfJQ1PuY5szTdvFbW820euiUEKEW69mW4uaFNPSc6D4Z8tZ5hXQIqBD40irULhF0CYNkIILmyNV/KJIZ5HkbQ1q+UrCFHJyvuH/3aCTjj9OSfE7xHPQ3xd3Xw8vvj0Mjie09xFbbcklBTw5WRzH7cw8c+Q0O69kZZ8b+ykcdzWTeZeWNdnzptNqnMjfheig90rUIJ7DN0c+53jCUcGpWJxJhcYF9Uk1RNHmSE5+VzK1y+20t0grVFX90nApm4Tl35QPrX7Qxp9C81cWiUB8xCAE6jYrmd4x+P/3wSQfc1Xg0Eg3QjJB+6JD7cbyDJpzDR3ja+CLZCAr9I0B4rDKD2d6et/z67iXPnZUWMyZ8RVVZPFbBMOTw== openpgp:0xF727046D diff --git a/src/keys/SSH/laisse.pub.txt b/src/keys/SSH/laisse.pub.txt new file mode 100644 index 0000000..9dc9d85 --- /dev/null +++ b/src/keys/SSH/laisse.pub.txt @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+cecVzuCf1GfURtCa8MDEPZ4bcJSnakcunlWw4FTCl9XTyO46Wx5wRfEHPMMhu6tE55OzbqxbdZLIoni4PvUA1KxAddJuz6vuAmAxGA2a46Xg5Pi+efYGue2194cYAyCW6dn7RcU+aTNoHTGVdypjUcTZwkQ7hBSlz0ICbVWYUa6qDbsKK68bWuhSoOFzOkERHQEBhXcIkg0uKZmTsDzEJZ+2H0kUgdvsaUKDTPpujPU7AOV9sDEFyNDh77aX1RSx14J3gCiJuWAYk1iLuoxbvg5VOieePRRvaDbtJt3RvSFHgNiVHWnBtK1FAg2EbMHl72dWSKhdjpPD37AQE0GX diff --git a/src/keys/SSH/root@papo.im.id_rsa.pub.stripped b/src/keys/SSH/root@papo.im.id_rsa.pub.stripped new file mode 100644 index 0000000..9d6cf4b --- /dev/null +++ b/src/keys/SSH/root@papo.im.id_rsa.pub.stripped @@ -0,0 +1 @@ +FIXME diff --git a/src/keys/SSH/root@papo.im.id_rsa.pub.txt b/src/keys/SSH/root@papo.im.id_rsa.pub.txt new file mode 100644 index 0000000..9d6cf4b --- /dev/null +++ b/src/keys/SSH/root@papo.im.id_rsa.pub.txt @@ -0,0 +1 @@ +FIXME diff --git a/src/keys/gpg-import.sh b/src/keys/gpg-import.sh new file mode 100755 index 0000000..63d2347 --- /dev/null +++ b/src/keys/gpg-import.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + gpg-import.sh + gpg-import.sh -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Import GPG keys under src/infrastructure/keys/GPG/ and mark them as + trusted, so that they can be used as recipients for encryption. + + + Examples: + + Just run it: + + $ gpg-import.sh + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +gpg --import src/infrastructure/keys/GPG/* + +gpg --with-colons --show-key src/infrastructure/keys/GPG/* | + awk -F: '$1 == "fpr" { print $10 }' | + while read -r fpr; do + printf '5\ny\n' | + gpg --command-fd 0 --expert --edit-key "$fpr" trust + done diff --git a/src/keys/gpg-recipients.sh b/src/keys/gpg-recipients.sh new file mode 100755 index 0000000..ad6e522 --- /dev/null +++ b/src/keys/gpg-recipients.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + gpg-recipients.sh + gpg-recipients.sh -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Process GPG keys under src/infrastructure/keys/GPG/, and emit + the command-line flags to be given to the `gpg` command, as in: + + $ gpg -r KEY1 -r KEY2 ... + + gpg-recipients.sh emits the `-r KEY1 -r KEY2` part, getting + those values from the fingerprints of the GPG keys in the + directory. + + + Examples: + + Just run it: + + $ gpg-recipients.sh + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +gpg --with-colons --show-key src/infrastructure/keys/GPG/* | + awk -F: '$1 == "fpr" { printf " -r %s", $10 }' diff --git a/src/scripts/backup.sh b/src/scripts/backup.sh new file mode 100755 index 0000000..6a2a4ff --- /dev/null +++ b/src/scripts/backup.sh @@ -0,0 +1,149 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + backup [-q] [-C COMMENT] [-x] [ARCHIVE_TAG] + backup -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -q disable verbose mode, useful for batch sessions + -C COMMENT the comment text to be attached to the archive + -x disable checking the repository after creating the backup + -h, --help show this message + + ARCHIVE_TAG the tag used to create the new + backup (default: "manual") + + + The repository is expected to have been create with: + + $ borg init -e repokey-blake2 + + The following environment variables are expected to be exported: + + $BORG_PASSCOMMAND + $BORG_REPO + $BORG_REMOTE_PATH + + Password-less SSH access is required, usually done via adding + /root/.ssh/id_rsa.pub to the ssh remote's + $THE_REMOTE:.ssh/authorized_keys + + Root permission is also required. + + + Examples: + + Run backup from cronjob: + + $ backup + + + Run backup from cronjob: + + $ backup -q cronjob + + + Create backup with a comment, a tag, and verbose mode active, and do not + verify the repository afterwards: + + $ backup -xC 'The backup has a comment' + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +VERBOSE_FLAGS='--verbose --progress' +COMMENT=' ' +CHECK=true +while getopts 'qC:xh' flag; do + case "$flag" in + q) + VERBOSE_FLAGS='' + ;; + C) + COMMENT="$OPTARG" + ;; + x) + CHECK=false + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +ARCHIVE_TAG="${1:-manual}" + + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +run() { + STATUS=0 + set -x + # shellcheck disable=2086 + sudo -i borg create \ + $VERBOSE_FLAGS \ + --comment "$COMMENT" \ + --stats \ + --compression lzma,9 \ + "$BORG_REPO::$(hostname)-{now}-$ARCHIVE_TAG" \ + /mnt/production/ \ + /root/ \ + /home/ \ + /etc/ \ + /var/ \ + /opt/ \ + /srv/ || STATUS=$? + set +x + + if [ "$STATUS" = 0 ]; then + return 0 + elif [ "$STATUS" = 1 ]; then + printf 'WARNING, but no ERROR.\n' >&2 + return 0 + else + return "$STATUS" + fi +} + +run + +if [ "$CHECK" = true ]; then + # shellcheck disable=2086 + sudo -i borg check $VERBOSE_FLAGS --verify-data "$BORG_REPO" +fi diff --git a/src/scripts/check.sh b/src/scripts/check.sh new file mode 100755 index 0000000..5c63816 --- /dev/null +++ b/src/scripts/check.sh @@ -0,0 +1,92 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + check + check -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Run system sanity checks, such as email reachability, alarms + reachability, filesystem checks, etc. + + + Examples: + + Just run it + + $ check + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +uuid() { + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +for alias in abuse admin postmaster hostmaster; do + uuid | mail -s "\"$alias\" alias test from $(id -un)@$(hostname)" "$alias@$(hostname)" +done + + +PARTITIONS=' +/dev/vda3 +/dev/vdb1 +/dev/vdc1 +' +set -x + +for part in $PARTITIONS; do + btrfs scrub start -B "$part" + btrfs check --force -p "$part" +done diff --git a/src/scripts/cicd.sh b/src/scripts/cicd.sh new file mode 100755 index 0000000..58f4fce --- /dev/null +++ b/src/scripts/cicd.sh @@ -0,0 +1,168 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + cicd [-n] NAME [SHA] + cicd -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -n build the system, but don't switch to it (dry-run) + -h, --help show this message + + NAME the name of the project + SHA the repository SHA to checkout (default: main) + + + Do a CI/CD run of the project called NAME, located at + /srv/git/$NAME.git. If -n is given, only build, otherwise + also do the deploy when the build is successfull. + + A "build" consists of: + - doing a fresh clone of the project on a temporary directory; + - checkout the project to version $SHA; + - when a "manifest.scm" file exists in the root of the project, + use it to launch a containerized Guix shell; otherwise use a + fallback template for a containerized Guix shell; + - build the "dev" target of the Makefile via "make dev". + + A "deploy" consists of: + - copying the "description" metadata file to + /srv/git/$NAME.git/description, in order to update the project + repository's description; + - upgrading the "pre-receive" Git hook, so that future runs are + affected by it; + - copying the "public/" directory that the "dev" target built to + the /srv/www/s/$NAME/ directory, so that the projects "public/" + directory is accessible via the web address + "https://euandre.org/s/$NAME/". + + This command must be ran as root. + + + Examples: + + Build and deploy the "remembering" project on the default branch: + + $ sudo cicd remembering + + + Build the "urubu" project on a specific commit, but don't deploy: + + $ sudo cicd -n urubu 916dafc092f797349a54515756f2c8e477326511 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +DRY_RUN=false +while getopts 'nh' flag; do + case "$flag" in + n) + DRY_RUN=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +NAME="${1:-}" +SHA="${2:-main}" +REPO="/srv/git/$NAME.git" + +if [ -z "$NAME" ]; then + printf 'Missing NAME.\n\n' >&2 + usage >&2 + exit 2 +fi + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +set +eu +# shellcheck source=/dev/null +. /etc/rc +set -eu + + +uuid() { + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +tmpname() { + printf '%s/uuid-tmpname with spaces.%s' "${TMPDIR:-/tmp}" "$(uuid)" +} + +mkdtemp() { + name="$(tmpname)" + mkdir -- "$name" + printf '%s' "$name" +} + + +TMP="$(mkdtemp)" +trap 'rm -rf "$TMP"' EXIT + + +set -x +chown deployer:deployer "$TMP" +cd "$TMP" +sudo -u deployer git clone "$REPO" . +sudo -u deployer --preserve-env=GIT_CONFIG_GLOBAL git checkout "$SHA" +guix system describe + +if [ -f manifest.scm ]; then + guix shell -Cv3 -m manifest.scm -- make dev +else + guix shell -Cv3 -- make dev +fi + +if [ "$DRY_RUN" = false ]; then + # COMMENT: pre-receive is always running the previous version! + # The same is true for the reconfigure script itself. + sudo cp description "$REPO"/description + sudo cp aux/ci/git-pre-receive.sh "$REPO"/hooks/pre-receive + + sudo -u deployer rsync \ + --delete \ + --chmod=D775,F664 \ + --chown=deployer:deployer \ + --exclude 'ci/*' \ + -a \ + public/ /srv/www/s/"$NAME"/ +fi diff --git a/src/scripts/cronjob.sh b/src/scripts/cronjob.sh new file mode 100755 index 0000000..4cd456e --- /dev/null +++ b/src/scripts/cronjob.sh @@ -0,0 +1,169 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + cronjob COMMAND... + cronjob -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + COMMAND the command to be executed + + + Execute the given command, and send the output to email, with + special treatment to the status code. It kills the job it it + lasts more than one hour. + + It load the appropriate files, so that the actual cron + declaration is smaller. + + + Examples: + + Run a backup: + + $ cronjob backup -q cron + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "${1:-}" ]; then + printf 'Missing COMMAND.\n\n' >&2 + usage >&2 + exit 2 +fi + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +set +eu +# shellcheck source=/dev/null +. /etc/rc +set -eu + + + +epoch() { + awk 'BEGIN { srand(); print(srand()); }' +} + +now() { + date '+%Y-%m-%dT%H:%M:%S%:z' +} + + +uuid() { + od -xN20 /dev/random | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +mkstemp() { + name="${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)" + touch "$name" + printf '%s' "$name" +} + +pre() { + # Same as: + # sed -u "s|^|[$CMD]: |" + # but the "-u" option is not POSIX + IFS='' + while read -r line; do + printf '[%s]: %s\n' "$CMD" "$line" + done +} + +duration() { + minutes=$((${1} / 60)) + seconds=$((${1} % 60)) + printf '%sm%ss' "$minutes" "$seconds" +} + + +CMD="$*" +HOSTNAME="$(hostname)" +FROM="cronjob@$HOSTNAME" +TIMEOUT='10800' # three hours +STATUS_F="$(mkstemp)" +OUT="$(mkstemp)" + +email() { + { + cat <<-EOF + Content-Type: text/plain; charset=UTF-8 + Content-Transfer-Encoding: 8bit + From: $FROM + To: root@localhost + Subject: (exit status: $(cat "$STATUS_F")) - $HOSTNAME: $CMD + + EOF + cat "$OUT" + } | sendmail -t -f "$FROM" + rm -f "$OUT" "$STATUS_F" +} +trap email EXIT + +{ + cat <<-EOF + Running commad: $CMD + Starting at: $(now) + + EOF + + START="$(epoch)" + STATUS=0 + timeout "$TIMEOUT" "$@" || STATUS=$? + printf '%s' "$STATUS" > "$STATUS_F" + END="$(epoch)" + DURATION_SECONDS=$((END - START)) + + cat <<-EOF + + Finished at: $(now) + Duration: $(duration "$DURATION_SECONDS") + EOF +} 2>&1 | pre | ts '%Y-%m-%dT%H:%M:%S' | tee "$OUT" >> /var/log/cronjobs.log diff --git a/src/scripts/deploy.sh b/src/scripts/deploy.sh new file mode 100755 index 0000000..dc30484 --- /dev/null +++ b/src/scripts/deploy.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + deploy + deploy -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Do a blue/green deployment of the main service. It makes sure + that the new service is up and running before shutting down the + old one. + + + Examples: + + Just do the deploy: + + $ deploy + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +echo FIXME diff --git a/src/scripts/gc.sh b/src/scripts/gc.sh new file mode 100755 index 0000000..e037f3c --- /dev/null +++ b/src/scripts/gc.sh @@ -0,0 +1,146 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + gc [TYPE] + gc -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + TYPE what to do GC on (default: all): + - guix + - deploy + - trash + - tmpdir + - logs + + + GC the server, deleting old, unusable data, in order to free + disk space system-wide. + + + Examples: + + Just run it, for all: + + $ gc + + + Cleanup tmpdir: + + $ gc tmpdir + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +disk() { + df -h / /mnt/backup/ | + tail -n +2 | + awk '{ printf "%s\t%s/%s\t%s\n", $4, $3, $2, $6 }' +} + +today() { + date '+%Y-%m-%d' +} + +gc_guix() { + sudo -i guix system delete-generations 1m + sudo -i guix gc -d 1m +} + +gc_deploy() { + find /opt/deploy \ + ! -path /opt/deploy -prune \ + -type d \ + -not -name "$(today)*" \ + -exec rm -rvf "{}" ';' +} + +gc_trash() { + yes | sudo -i trash-empty +} + +gc_tmpdir() { + find "${TMPDIR:-/tmp}" -atime +10 -exec rm -vf "{}" ';' +} + +gc_logs() { + find /var/log/ci/ -atime +10 -exec rm -vf "{}" ';' +} + + +gc_all() { + gc_guix + gc_deploy + gc_trash + gc_tmpdir + gc_logs +} + + +TYPE="${1:-all}" +CMD=gc_"$TYPE" +if ! command -v "$CMD" >/dev/null; then + printf 'Invalid TYPE: "%s".\n\n' "$TYPE" >&2 + usage >&2 + exit 2 +fi + +BEFORE="$(disk)" +set -x +"$CMD" +set +x +AFTER="$(disk)" + +cat <<-EOF + Disk space: + before: $BEFORE + after: $AFTER +EOF diff --git a/src/scripts/reconfigure.sh b/src/scripts/reconfigure.sh new file mode 100755 index 0000000..8fa47c5 --- /dev/null +++ b/src/scripts/reconfigure.sh @@ -0,0 +1,146 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + reconfigure [-n] [-U] [SHA] + reconfigure -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -n build the system, but don't switch to it (dry-run) + -U pull the latest channels before reconfiguring + -h, --help show this message + + SHA the repository SHA to checkout (default: main) + + + Run a "guix system reconfigure" as root via "sudo -i". If a -U + flag is given, perform a "guix pull" (in root profile) prior to + the reconfigure. The user must be able to become the "deployer" + user, either via "sudo reconfigure" or by being member of the + "become-deployer" group. + + + Examples: + + Reconfigure the system: + + $ reconfigure + + + Build the system on a custom SHA, but don't switch to it: + + $ reconfigure -n 916dafc092f797349a54515756f2c8e477326511 + + + Update and upgrade: + + $ reconfigure -U + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +UPDATE=false +DRY_RUN=false +while getopts 'nUh' flag; do + case "$flag" in + n) + DRY_RUN=true + ;; + U) + UPDATE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +# shellcheck source=/dev/null +. /etc/conf.env +SHA="${1:-main}" +REPO="/srv/git/$REPO_NAME" +NOW="$(date '+%Y-%m-%dT%H:%M:%S%:z')" +NOW_DIR=/opt/deploy/"$NOW" +NPROC=$(($(nproc) * 2 + 1)) + + +if [ "$(id -un)" != 'root' ]; then + printf 'This script must be run as root.\n\n' >&2 + usage >&2 + exit 2 +fi + + +set +eu +# shellcheck source=/dev/null +. /etc/rc +set -eu + + +if [ "$UPDATE" = true ] && [ "$DRY_RUN" = false ]; then + sudo -i guix pull -v3 +fi + +set -x +sudo -u deployer git clone --depth=1 "file://$REPO" "$NOW_DIR" +sudo -u deployer rm -f /opt/deploy/current +sudo -u deployer ln -rs "$NOW_DIR" /opt/deploy/current +cd /opt/deploy/current +sudo -u deployer git fetch --depth=1 "file://$REPO" "$SHA" +sudo -u deployer --preserve-env=GIT_CONFIG_GLOBAL git checkout "$SHA" +guix system describe + +if [ "$DRY_RUN" = true ]; then + sudo -i guix system -c$NPROC -v3 build "$PWD"/src/infrastructure/guix/system.scm +else + # COMMENT: pre-receive is always running the previous version! + # The same is true for the reconfigure script itself. + cp description "$REPO"/description + cp src/infrastructure/ci/git-pre-receive.sh "$REPO"/hooks/pre-receive + cp src/infrastructure/guix/channels.scm /etc/guix/ + cp src/infrastructure/guix/system.scm /etc/guix/ + + sudo -i guix system -c$NPROC -v3 reconfigure /etc/guix/system.scm + + sudo -u deployer rsync \ + --delete \ + --chmod=D775,F664 \ + --chown=deployer:deployer \ + --exclude "$CI_SUFFIX/*" \ + -a \ + /run/current-system/profile/share/doc/"$NAME"/ "$HTML_OUTDIR_TOP"/ + + ln -sf /var/log/"$NAME".log "$HTML_OUTDIR_PRIV" + + deploy +fi diff --git a/src/scripts/report.sh b/src/scripts/report.sh new file mode 100755 index 0000000..e14e40a --- /dev/null +++ b/src/scripts/report.sh @@ -0,0 +1,260 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + report [-C REPO] -o DIRECTORY + report -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -C REPO change to REPO when doing Git operations (default: $PWD) + -o DIRECTORY the directory where to place the generated files + -h, --help show this message + + + Gather data from Git Notes, and generate an HTML report on CI runs. + + Two refs with notes are expected: + 1. refs/notes/ci-data: contains metadata abount the CI runs, + with timestamps, filenames and exit status; + 2. refs/notes/ci-logs: contains the content of the log. + + When reconstructing the CI run, the $FILENAME present in + the refs/notes/ci-data ref names the file, and its content comes + from refs/notes/ci-logs. + + On a CI run that generated the numbers from 1 to 10, for a file named + 'my-ci-run-2020-01-01-deadbeef.log' that exited successfully, the + expected output on the target directory "public" is: + + $ tree public/ + public/ + index.html + data/ + my-ci-run-2020-01-01-deadbeef.log + ... + logs/ + my-ci-run-2020-01-01-deadbeef.log + ... + + $ cat public/data/my-ci-run-2020-01-01-deadbeef.log + 0 deadbeef my-ci-run-2020-01-01-deadbeef.log + + $ cat public/logs/my-ci-run-2020-01-01-deadbeef.log + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + The generated 'index.html' is a webpage with the list of all known + CI runs, their status, a link to the commit and a link to the + log file. + + To enable fetching these refs by default, do so in the git config: + + $ git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*' + + + Examples: + + Generate the report on the 'www' directory: + + $ report -o www + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +REPO="$PWD" +while getopts 'C:o:h' flag; do + case "$flag" in + C) + REPO="$OPTARG" + ;; + o) + OUTDIR="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "${OUTDIR:-}" ]; then + printf 'Missing -o OUTDIR.\n\n' >&2 + usage >&2 + exit 2 +fi + +if [ -r src/infrastructure/config/conf.env ]; then + CONF=src/infrastructure/config/conf.env +else + CONF=/etc/conf.env +fi + +# shellcheck source=/dev/null +. "$CONF" + + +esc() { + sed \ + -e 's|&|\&|g' \ + -e 's|<|\<|g' \ + -e 's|>|\>|g' \ + -e 's|"|\"|g' \ + -e "s|'|\'|g" +} + +mkdir -p "$OUTDIR" +cd "$OUTDIR" +mkdir -p logs data + +for c in $(git -C "$REPO" notes list | cut -d' ' -f2); do + git -C "$REPO" notes --ref=refs/notes/ci-data show "$c" > data/FILENAME-tmp + FILENAME="$(grep '^filename ' data/FILENAME-tmp | cut -d' ' -f2-)" + mv data/FILENAME-tmp data/"$FILENAME" + git -C "$REPO" notes --ref=refs/notes/ci-logs show "$c" > logs/"$FILENAME" +done + +{ + cat <<-EOF + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="CI logs for $NAME" /> + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> + <title>$NAME - CI logs</title> + <style> + body { + max-width: 800px; + margin: 0 auto; + } + + code { + display: block; + margin: 1em 0em 3em 3em; + overflow: auto; + } + + pre { + display: inline; + } + + ol { + list-style-type: disc; + } + + pre, code { + background-color: #ddd; + } + + @media(prefers-color-scheme: dark) { + :root { + color: white; + background-color: black; + } + + a { + color: hsl(211, 100%, 60%); + } + + a:visited { + color: hsl(242, 100%, 80%); + } + + pre, code { + background-color: #222; + } + } + </style> + </head> + <body> + <main> + <h1> + CI logs for + <a href="$HOMEPAGE">$NAME</a> + </h1> + <ol> + EOF + + + PASS='✅' # ✅ + WARN='🐌' # 🐌 + FAIL='❌' # ❌ + find data/ -type f | LANG=C.UTF-8 sort -r | while read -r f; do + STATUS="$( grep '^status ' "$f" | cut -d' ' -f2- | esc)" + SHA="$( grep '^sha ' "$f" | cut -d' ' -f2- | esc)" + FILENAME="$(grep '^filename ' "$f" | cut -d' ' -f2- | esc)" + DURATION="$(grep '^duration ' "$f" | cut -d' ' -f2- | cut -d'"' -f1 | esc)" + MESSAGE="$({ + git -C "$REPO" log -1 --format=%B "$SHA" || { + git fetch origin "$SHA" + git -C "$REPO" log -1 --format=%B "$SHA" + } + } | esc)" + + if [ "$STATUS" = 0 ]; then + if [ "$DURATION" -le 60 ]; then + STATUS_MARKER="$PASS" + else + STATUS_MARKER="$WARN" + fi + else + STATUS_MARKER="$FAIL" + fi + + cat <<-EOF + <li id="$FILENAME"> + <a href="#$FILENAME"><pre>#</pre></a> + $STATUS_MARKER - <pre>${DURATION:-?}s</pre> + <pre>(<a href="${CGIT_URL}${SHA}">commit</a>)</pre> + <a href="logs/$FILENAME"><pre>$FILENAME</pre></a> + <pre>(<a href="data/$FILENAME">data</a>)</pre> + <br /> + <code><pre>$MESSAGE</pre></code> + </li> + EOF + done + + cat <<-EOF + </ol> + </main> + </body> + </html> + EOF +} > index.html diff --git a/src/secrets/VPS-root.txt.gpg b/src/secrets/VPS-root.txt.gpg new file mode 100644 index 0000000..b59ff3b --- /dev/null +++ b/src/secrets/VPS-root.txt.gpg @@ -0,0 +1,18 @@ +-----BEGIN PGP MESSAGE----- + +hQIMAxJMwbOrBV1aARAArxPpuV4r4+yYBPz4qhSjbyQmRU+EihwxNC+ARAGpiNn/ +VARwHARJwPrJX0SP7qqtyzz5xqWhjhquAo+GL463T313Wbroaj4hD4NJDe2dagtU +SFTbxwmj2iWiS6PhwHG7H3YU+eRK0p58guNiMS23delRZUyVxYrlMtq7RPtyLSxw +7KIi+/bA+bGaxTdu162BPC+upRwBBOVUrQxHpFuwzZZ/g0L6JLG1SzP0/sXTYyJQ +P2db6p6vUieXCW1VIN7P30/vWzGmCNtOpNtCPSrgvx2C6YGL5OdvJT+gbEjWXhLg +jLZc7DXE/zLN/fAleFr9U4rquopmP+O/df3JRDvmY+1H6e5QT6Se9a6EsF3JW9ys +odwKjkYqQGljuJ4oB4rygAx46X8C+0xkKNexsbP/nAJ6UV97mwUgU6lNtCcYoA+B +iU1UkU9xbjiSr6N6VvNx+0/oFMyoTHB/uwLxuaU7Dk+f+SHAMSCsnvGMLXzaSXPl +rRdSTAn0mGbDVwuNv/LtOwM2zyss0zfd3gxS2R1+IXX0eSLzCVN2BlUITGqNE4gz +jT/7LCK0gexJGCoe5Ux7jDxUBVmQKjT3XTCMKcls1MzxfxXfk7iw8Lp3T3OhkCX5 +yfaaAYY3xSB/MBim8PX0jE6W8TbdRvMqyfFr/TehJkjJ4NrSCGBi68OOnAzcl9fS +YAFGGaEqRK5bClwmYcgwo4hOZjWNNlh061FWDET7Qz8rlZiT4+KLWkERCqUKhJWG +fVtgfy1yA4QBV9KVIN4GBAoqnR8oo10154SD4CjSQmWjaJsb+Mn10T3XXmW2QFex +pw== +=rFvw +-----END PGP MESSAGE----- diff --git a/src/secrets/borg-passphrase.txt.gpg b/src/secrets/borg-passphrase.txt.gpg new file mode 100644 index 0000000..f49548a --- /dev/null +++ b/src/secrets/borg-passphrase.txt.gpg @@ -0,0 +1,18 @@ +-----BEGIN PGP MESSAGE----- + +hQIMAxJMwbOrBV1aARAAy2JxbJvEmNkNDuM+bPOcatFwNOo+0YVPQdahDaeIrXqq +DgUYxZBuauNPP95NhlNWG2TgmeDGxk6K/KEmYFF+6Uv2zJ9sE9IkSvgt50iacO6h +2Dk/dLGi8a6F2tOpwR3vcx1KFCtMtsIKba0e85Uj9D1d2QsTX/546pTDX/LaxQQI +D4hAK0t3NEzvTVD3Dd7mFsq1gRTf8+aPnm+J5UdYs6QfimcUKTSDsfmWxmzeEXaj +znKGdtMqdyjtNibK5U92elDvt+bt/8m3mh50zaFPb8t5xQwJVvgsXZd+tx2DWinz +APt7UWf5k63HTm2aRX/XFw1Dsjr4ujkWRl9XPiw6C4taCbT0UjV9n236pbfA/WXd +e3tZy26Csd3UIIEg26tdH9t87TfZN2TBH6wZge1yyFUWckXywJKaeegMArFZ6s0A +7MqFknA9g0vgJFIiOZB80YGl9xDKmAA1RTSh1g0eBvz0OSAe+Z5EgmJ9IcnBMRyb +xZFM2Y8R7z94WoEGl7CYtT0UfobRcQLy7vqKTVzaZmNLAYNUYbM5bBr1NZxPHe1G +44Lxtdjmw53uDZLo4AKGNdDjfyTHL0hctWhCTQJ5oaXm1G6FNJF7YxHejdTrqIBW +CNk7IcKAkIQVG5y09uBTKi4Yo3n7/yIh8UmeyYWPkkHuuoGyCJHMBTnwlq2Kou7S +YAGQfq0rVlgWrtdrJXXpEqxAv8xWsu2UR1+0yKyVaWsMghEU6HXTVoLtQcM6WP6w +bDc4CEU7peGhqeAw+zeNcvsaWub9eNBNpTZu1IVsyDG1I+Qqni8SVpgztAASYR9d +pg== +=tQYt +-----END PGP MESSAGE----- diff --git a/src/secrets/root@papo.im.id_rsa.txt.gpg b/src/secrets/root@papo.im.id_rsa.txt.gpg new file mode 100644 index 0000000..c3bd663 --- /dev/null +++ b/src/secrets/root@papo.im.id_rsa.txt.gpg @@ -0,0 +1,18 @@ +-----BEGIN PGP MESSAGE----- + +hQIMAxJMwbOrBV1aARAAlyc6jYsn5hVbnfZktuOsXZcn+3Z0aHFLbQJsL4yR32yN +nGE5avTpGSff3qIKcjvN1G4+AN7O97nos3iULQWUTmhrCtZzxRgA0+hwI+qVqLRm +BorRrIKzOZsQBCrbdhomm1Hwy+g/kEkU91Ux3sCLSfS8B5315maqZv5MaM6KyBMU +bCbkjnokiCmkp0UTq4VBM1sh/gSoqgpajIp/q24MBg1T8v0Y4LPL2E7lXY0IvuF7 +q0XYG/E8nUv8aC1rgeU8EUAHGhwTRvE3/BVYNSNZv9RHsFSpZG/FpuKu2FO8VpIG +zgjOKIYH+dA7ilu86suRRuiGMzLw4ifSlpcBmsz1a+8ITSxoVQC9V34rIdujhyYu +Mk5I79+54ZP974tv7lY88NCF8a5vQekRIeRGQxql0U1RP5l0I0nBq/PRiU8n2EF9 +gO9CpOiviT8odM/r34ZFxeRJ1x/gPXaUCiw9B2E0N2vemElaSSyY7+bFHJJIgmYi +DYUChG15HcsZeBpbqxkDQhNrP8z/0Tlm8Kp5YPQ0ErVlCINnx/3z4o5jkRigi/GB +MPRFfG0jg8CQADcaK5+nG7uhFtmG/uPoB1xoM96Jmqa7J7+uKZE2CthkOoOyYn/f +SWjpetoV8sJBcUYyWA9fQSRbeblJ28jbbVryUg6bVQ560V/Jm6Caisx8rTTd91nS +YAFibbyZMndIhJ9UYBdFIo1SqS5SIiizU9SpeLWCa/n+GdxTueOpO4Qz0oE+694T +VGrEQxDouQtHvcLFGkDLGz1K9i2ILBIeK/AfZ2lowp9pmny1x/9TjmjtNf62+53I +tw== +=tL9t +-----END PGP MESSAGE----- diff --git a/src/secrets/rsync.net.txt.gpg b/src/secrets/rsync.net.txt.gpg new file mode 100644 index 0000000..0352e45 --- /dev/null +++ b/src/secrets/rsync.net.txt.gpg @@ -0,0 +1,18 @@ +-----BEGIN PGP MESSAGE----- + +hQIMAxJMwbOrBV1aARAAznUOLmXnP72G64k+Gz8UmpAdQVTn7dmUqaLQAm3G2ONF +Gdjw5FaLdNEpwMmBVrQeWHxlyL4TtdTkvhDuk3Y7glX6IyNb2E1mOtODOwyn9YW0 +Pq5dq9Q9jMp3DgrcHNF4XayhUYBOdsgZiFVCZGrAigxWiXiIBh2tT/WF7wYDtUCH +8gWAp2WqJ2sfh/v2Slv/jWfTNJEQv3t/2xO4KthT/D75Nfr6QnTZBaO28nZURQLe +eebNC9v1UW6CUV1saWCYEYhE66UAZHzNcYPxZujJS7tjr3Sc5PZKsRs3BBliL0+P +LWvApBZ6zT/PfqMZJkOXnPCY6kM29Wj8bDD2DE80dZS+MLdPGpxNZlOJ9CBq9l2I +f12jBBZ0Ncb/TzqMeoyjOxi3BQ4m85s4KcX9v2bmyGxq7dhXVPo7OrBYtzGJDEBU +7B8I083zYDxhFtW8HpCyRJhAsJeD3I+r+44Lbf05tJglFV96MK78jZeILVvJbK0L +paNIwoorzUPVbbenFzeOLo0qVaivpMhUPo+0p+oiyv0sK/edO4tI3G4RWUpzGTdF +ECf87NFZ4F5dtJyLxz6014CrILbVhyqX4Mfxv74nEDijCcEdl2xAGniohLUzoyTH +2HMwwGouoMne5DxEViY2WFK/pk0Ee1kqUNYcUML82nkOe50ykOmIUrVKW+pzXZ7S +YAEnGi77S2Ds57UiRcbVqa/TLjuRFbsnqotVMitrXQjDE45eov8erNTVt7TlG0Dk +X+CnBuqK1qma0XxjZHnzRwrmsP/7fz9W5ZAGwV5U55uC6OUkee6G3BNzZgVhEBAs +qg== +=z0m6 +-----END PGP MESSAGE----- |