aboutsummaryrefslogtreecommitdiff
path: root/src/infrastructure
diff options
context:
space:
mode:
Diffstat (limited to 'src/infrastructure')
-rw-r--r--src/infrastructure/lib/curth0.scm475
-rw-r--r--src/infrastructure/machines.scm401
-rw-r--r--src/infrastructure/rsync.net/authorized_keys4
-rw-r--r--src/infrastructure/ssh.conf19
4 files changed, 899 insertions, 0 deletions
diff --git a/src/infrastructure/lib/curth0.scm b/src/infrastructure/lib/curth0.scm
new file mode 100644
index 0000000..5d73160
--- /dev/null
+++ b/src/infrastructure/lib/curth0.scm
@@ -0,0 +1,475 @@
+(define-module (curth0)
+ #:use-module ((language tree-il) #:prefix tree-il:)
+ #:use-module ((ice-9 pretty-print) #:prefix pp:)
+ #:use-module ((srfi srfi-1) #:prefix s1:)
+ #:use-module ((srfi srfi-1) #:select (first second))
+ #:use-module ((srfi srfi-64) #:prefix t:)
+ #:export (->
+ ->>
+ tap)
+ #:re-export (first
+ second))
+
+(define (tap x)
+ (pp:pretty-print x)
+ x)
+
+(define-syntax ->
+ (syntax-rules ()
+ ((_) #f)
+ ((_ x) x)
+ ((_ x (f . (head ...))) (f x head ...))
+ ((_ x f) (f x))
+ ((_ x (f . (head ...)) rest ...) (-> (f x head ...) rest ...))
+ ((_ x f rest ...) (-> (f x) rest ...))))
+
+(define-syntax ->>
+ (syntax-rules ()
+ ((_) #f)
+ ((_ x) x)
+ ((_ x (f ...)) (f ... x))
+ ((_ x f) (f x))
+ ((_ x (f ...) rest ...) (->> (f ... x) rest ...))
+ ((_ x f rest ...) (->> (f x) rest ...))))
+
+(define (test-thread-macro)
+ (define (expand l)
+ (tree-il:tree-il->scheme (macroexpand l)))
+
+ (t:test-group "-> and ->>"
+ (t:test-equal '#f (expand '(->)))
+ (t:test-equal '#f (expand '(->>)))
+
+ (t:test-equal '1 (expand '(-> 1)))
+ (t:test-equal '1 (expand '(->> 1)))
+
+ (t:test-equal '(f 1) (expand '(-> (f 1))))
+ (t:test-equal '(f 1) (expand '(->> (f 1))))
+
+ (t:test-equal '(f 1 2 3) (expand '(-> 1 (f 2 3))))
+ (t:test-equal '(f 2 3 1) (expand '(->> 1 (f 2 3))))
+
+ (t:test-equal '(f 1) (expand '(-> 1 f)))
+ (t:test-equal '(f 1) (expand '(->> 1 f)))
+
+ (t:test-equal '(f2 (f1 1)) (expand '(-> 1 f1 f2)))
+ (t:test-equal '(f2 (f1 1)) (expand '(->> 1 f1 f2)))
+
+ (t:test-equal '(f2 (f1 1)) (expand '(-> 1 (f1) f2)))
+ (t:test-equal '(f2 (f1 1)) (expand '(->> 1 (f1) f2)))
+
+ (t:test-equal '(f2 (f1 1)) (expand '(-> 1 f1 (f2))))
+ (t:test-equal '(f2 (f1 1)) (expand '(->> 1 f1 (f2))))
+
+ (t:test-equal '(f2 (f1 1)) (expand '(-> 1 (f1) (f2))))
+ (t:test-equal '(f2 (f1 1)) (expand '(->> 1 (f1) (f2))))
+
+ (t:test-equal '(f1 1 (f2)) (expand '(-> 1 (f1 (f2)))))
+ (t:test-equal '(f1 (f2) 1) (expand '(->> 1 (f1 (f2)))))
+
+ (t:test-equal '(f6 (f5 (f4 (f3 (f1 1 (f2)))) 0 1 2))
+ (expand '(-> 1 (f1 (f2)) f3 (f4) (f5 0 1 2) f6)))
+ (t:test-equal '(f6 (f5 0 1 2 (f4 (f3 (f1 (f2) 1)))))
+ (expand '(->> 1 (f1 (f2)) f3 (f4) (f5 0 1 2) f6)))))
+
+(define consumable-chars
+ '(#\space #\tab))
+
+(define (extract-single-line-indentation chars)
+ (s1:fold (lambda (curr acc)
+ (let* ((prev (s1:first acc))
+ (halted? (s1:second acc))
+ (n (s1:third acc))
+ (non-blank? (not (member curr consumable-chars)))
+ (changed? (and (not (null? prev))
+ (not (equal? curr prev)))))
+ (cond
+ (halted? acc)
+ (non-blank? (list prev #t n))
+ (changed? (list prev #t n))
+ (#:else (list curr #f (+ 1 n))))))
+ '(#nil #f 0)
+ chars))
+
+(define (test-extract-single-line-indentation)
+ (t:test-group "extract-single-line-indentation"
+ (t:test-equal '(#nil #f 0)
+ (extract-single-line-indentation
+ '()))
+
+ (t:test-equal '(#nil #t 0)
+ (extract-single-line-indentation
+ '(#\a #\b #\c)))
+
+ (t:test-equal '(#\space #t 1)
+ (extract-single-line-indentation
+ '(#\space #\b #\c)))
+
+ (t:test-equal '(#\space #t 5)
+ (extract-single-line-indentation
+ '(#\space #\space #\space #\space #\space #\b #\c)))
+
+ (t:test-equal '(#\space #f 5)
+ (extract-single-line-indentation
+ '(#\space #\space #\space #\space #\space)))
+
+ (t:test-equal '(#\space #f 1)
+ (extract-single-line-indentation
+ '(#\space)))
+
+ (t:test-equal '(#\tab #t 3)
+ (extract-single-line-indentation
+ '(#\tab #\tab #\tab #\b #\c)))
+
+ (t:test-equal '(#\tab #f 3)
+ (extract-single-line-indentation
+ '(#\tab #\tab #\tab)))
+
+ (t:test-equal '(#\tab #f 1)
+ (extract-single-line-indentation
+ '(#\tab)))
+
+ (t:test-equal '(#\space #t 1)
+ (extract-single-line-indentation
+ '(#\space #\tab)))
+
+ (t:test-equal '(#\space #t 2)
+ (extract-single-line-indentation
+ '(#\space #\space #\tab #\space)))
+
+ (t:test-equal '(#\tab #t 3)
+ (extract-single-line-indentation
+ '(#\tab #\tab #\tab #\space)))
+
+ (t:test-equal '(#\tab #t 3)
+ (extract-single-line-indentation
+ '(#\tab #\tab #\tab #\a #\tab)))
+
+ (t:test-equal '(#\tab #t 3)
+ (extract-single-line-indentation
+ '(#\tab #\tab #\tab #\a #\space)))))
+
+(define (extract-line-indentations lines)
+ (map (compose (lambda (triple)
+ (list (s1:first triple)
+ (s1:third triple)))
+ extract-single-line-indentation
+ string->list)
+ lines))
+
+(define (test-extract-line-indentations)
+ (t:test-group "extract-line-indentations"
+ (t:test-equal '()
+ (extract-line-indentations
+ '()))
+
+ (t:test-equal '((#nil 0))
+ (extract-line-indentations
+ '("")))
+
+ (t:test-equal '((#nil 0) (#nil 0) (#nil 0))
+ (extract-line-indentations
+ '("" "" "")))
+
+ (t:test-equal '((#nil 0) (#nil 0) (#nil 0))
+ (extract-line-indentations
+ '("a" "b" "c")))
+
+ (t:test-equal '((#\space 1) (#\space 2) (#\space 3))
+ (extract-line-indentations
+ '(" " " " " ")))
+
+ (t:test-equal '((#\space 1) (#\tab 1) (#\space 3))
+ (extract-line-indentations
+ '(" " " " " ")))
+
+ (t:test-equal '((#nil 0) (#\space 3) (#\tab 2))
+ (extract-line-indentations
+ '("no spaces" " with spaces" " with tabs")))))
+
+(define (maximum-indentation lines)
+ (let* ((line-indentations (extract-line-indentations lines))
+ (chars (map s1:first line-indentations))
+ (different-indents? (not (= 1 (length (s1:delete-duplicates chars))))))
+ (if different-indents?
+ 0
+ (apply min (map s1:second line-indentations)))))
+
+(define (test-maximum-indentation)
+ (t:test-group "maximum-indentation"
+ (t:test-equal 0
+ (maximum-indentation
+ '()))
+
+ (t:test-equal 0
+ (maximum-indentation
+ '("")))
+
+ (t:test-equal 0
+ (maximum-indentation
+ '("" "" "")))
+
+ (t:test-equal 0
+ (maximum-indentation
+ '("" " " "")))
+
+ (t:test-equal 1
+ (maximum-indentation
+ '(" " " " " ")))
+
+ (t:test-equal 2
+ (maximum-indentation
+ '(" a" " b" " c")))
+
+ (t:test-equal 1
+ (maximum-indentation
+ '(" space space" " space tab" " space space tab")))
+
+ (t:test-equal 0
+ (maximum-indentation
+ '(" space space" " space tab" "none")))))
+
+(define (trim-indentation s)
+ (let* ((lines (string-split s #\newline))
+ (trim-n (maximum-indentation
+ (filter (lambda (s) (not (equal? "" s)))
+ lines))))
+ (string-join
+ (map (lambda (line)
+ (if (equal? "" line)
+ line
+ (substring line trim-n)))
+ lines)
+ "\n")))
+
+(define (test-trim-indentation)
+ (t:test-group "trim-indentation"
+ (t:test-equal ""
+ (trim-indentation ""))
+
+ (t:test-equal "alltogether"
+ (trim-indentation "alltogether"))
+
+ (t:test-equal "with spaces between"
+ (trim-indentation "with spaces between"))
+
+ (t:test-equal "with spaces around "
+ (trim-indentation " with spaces around "))
+
+ (t:test-equal "with multiple spaces "
+ (trim-indentation " with multiple spaces "))
+
+ (t:test-equal "tabs "
+ (trim-indentation " tabs "))
+
+ (t:test-equal "tabs and spaces "
+ (trim-indentation " tabs and spaces "))
+
+ (t:test-equal "\n"
+ (trim-indentation "\n"))
+
+ (t:test-equal "\n\n\nmultiple empty lines\n\n\n"
+ (trim-indentation "\n\n\nmultiple empty lines\n\n\n"))
+
+ (t:test-equal "\n\nmixed\n indentations\n"
+ (trim-indentation "\n\nmixed\n indentations\n"))
+
+ (t:test-equal "\n\n\n \n lines with only spaces\n"
+ (trim-indentation "\n\n \n \n lines with only spaces\n"))
+
+ (t:test-equal "\n\n mixed spaces\n and tabs"
+ (trim-indentation "\n\n mixed spaces\n and tabs"))
+
+ (t:test-equal "\nstripped\n tabs"
+ (trim-indentation "\n stripped\n tabs"))
+
+ (t:test-equal "
+#!/bin/sh
+set -eu
+
+ret=0
+if cmd; then
+ echo 'Done!'
+ ret=1
+fi
+exit $ret
+"
+ (trim-indentation "
+ #!/bin/sh
+ set -eu
+
+ ret=0
+ if cmd; then
+ echo 'Done!'
+ ret=1
+ fi
+ exit $ret
+"))))
+
+
+(define (non-quote-chars? chars)
+ (let ((non-quote-chars (filter (lambda (c)
+ (not (equal? #\" c)))
+ chars)))
+ (< 0 (length non-quote-chars))))
+
+(define (test-non-quote-chars?)
+ (t:test-group "non-quote-chars?"
+ (t:test-equal #f
+ (non-quote-chars?
+ '()))
+
+ (t:test-equal #f
+ (non-quote-chars?
+ '(#\")))
+
+ (t:test-equal #f
+ (non-quote-chars?
+ '(#\" #\" #\")))
+
+ (t:test-equal #t
+ (non-quote-chars?
+ '(#\" #\" #\-)))
+
+ (t:test-equal #t
+ (non-quote-chars?
+ '(#\a #\")))))
+
+(define (multiline-string-reader _char port)
+ (let ((multiline? #f)
+ (chars '()))
+ (do ((curr (read-char port)
+ (read-char port)))
+ ((equal? #\newline curr))
+ (set! chars (cons curr chars)))
+ (when (and (not (null? chars))
+ (equal? #\- (car chars)))
+ (set! multiline? #t)
+ (set! chars (cdr chars)))
+ (let ((non-quote-chars (non-quote-chars? chars)))
+ (when non-quote-chars
+ (error
+ (format
+ #f
+ "Invalid characters at the beginning of the multiline reader: ~s"
+ (reverse non-quote-chars)))))
+ (let* ((quote-count (+ 1 (length chars)))
+ (quote-n 0)
+ (output '()))
+ (while #t
+ (let ((curr (read-char port)))
+ (when (eof-object? curr)
+ (error "EOF while reading #\"\"# multiline string"))
+ (set! output (cons curr output))
+ (set! quote-n
+ (cond
+ ((and (>= quote-n quote-count)
+ (equal? #\# curr))
+ (break))
+ ((equal? #\" curr) (+ 1 quote-n))
+ (#:else 0)))))
+ (let ((s (list->string
+ (reverse
+ (s1:drop output (+ 1 quote-count))))))
+ (if multiline?
+ (trim-indentation s)
+ s)))))
+
+(read-hash-extend #\" multiline-string-reader)
+
+(define (test-multiline-string-reader)
+ (t:test-group "multiline-string-reader"
+ (t:test-equal ""
+ #""
+""#)
+
+ (t:test-equal " "
+ #""
+ ""#)
+
+ (t:test-equal " "
+ #""
+ ""#)
+
+ (t:test-equal ""
+ #""-
+ ""#)
+
+ (t:test-equal " some\n text"
+ #""
+ some
+ text""#)
+
+ (t:test-equal "some\ntext"
+ #""-
+ some
+ text""#)
+
+ (t:test-equal " indented\ntext"
+ #""-
+ indented
+ text""#)
+
+ (t:test-equal "with\nnewline\n"
+ #""-
+ with
+ newline
+ ""#)
+
+ (t:test-equal " unindented\n newline\n"
+ #""-
+ unindented
+ newline
+ ""#)
+
+ (t:test-equal "multiple quotes: #\"\"\"inside\"\"\"#\n"
+ #""""-
+ multiple quotes: #"""inside"""#
+ """"#)
+
+ (t:test-equal " indented"
+ #"
+ indented"#)
+
+ (t:test-equal "unindented"
+ #""-
+ unindented""#)
+
+ (t:test-equal "#!/bin/sh
+set -eu
+
+some-cmd
+echo \"$SOMETHING\"# here is a valid glued comment
+if cmd; then
+ echo 'Indentation here!'
+ echo \"\"# anothe valid comment, glued to an empty string
+fi
+"
+ #"""-
+ #!/bin/sh
+ set -eu
+
+ some-cmd
+ echo "$SOMETHING"# here is a valid glued comment
+ if cmd; then
+ echo 'Indentation here!'
+ echo ""# anothe valid comment, glued to an empty string
+ fi
+ """#)))
+
+(define test-fns
+ (list
+ test-thread-macro
+ test-extract-single-line-indentation
+ test-extract-line-indentations
+ test-maximum-indentation
+ test-trim-indentation
+ test-non-quote-chars?
+ test-multiline-string-reader))
+
+(define (unit-tests)
+ (t:test-begin "curth0-tests")
+ (for-each (lambda (fn) (fn)) test-fns)
+ (let ((n-fail (t:test-runner-fail-count (t:test-runner-get))))
+ (t:test-end)
+ n-fail))
diff --git a/src/infrastructure/machines.scm b/src/infrastructure/machines.scm
new file mode 100644
index 0000000..6c239d6
--- /dev/null
+++ b/src/infrastructure/machines.scm
@@ -0,0 +1,401 @@
+(use-modules (gnu)
+ (curth0)
+ (srfi srfi-26)
+
+ (gnu packages ssh)
+
+ (gnu services certbot)
+ (gnu services mcron)
+ (gnu services mail)
+ (gnu services networking)
+ (gnu services ssh)
+ (gnu services web))
+
+;;
+;; Implicit dependencies, to be automated:
+;; - /srv and /opt directories:
+;; # mkdir -p /srv/http /opt/secrets
+;; # chown -R andreh:users /opt /srv
+;; # chmod -R 755 /opt /srv
+;; - create /opt/secrets/borg-passphrase.txt
+;; $ pass generate VPS/$SERVER/borg/passphrase.txt 999
+;; $ pass show VPS/$SERVER/borg/passphrase | ssh $SERVER 'cat - > /opt/secrets/borg-passphrase.txt'
+;; - create the SSH key
+;; $ ssh-keygen
+;; - *manually* add that to the authorized_keys on rsync.net:
+;; $ scp $R:.ssh/authorized_keys src/rsync.net/
+;; $ # FIXME: add 'restrict,command="..."' to the authorized_keys entry
+;; $ ssh $SERVER cat .ssh/id_rsa.pub >> authorized_keys
+;; $ scp src/rsync.net/authorized_keys $R:.ssh/
+;; - copy borg key after the first backup:
+;; $ FIXME
+;; - generate DKIM key
+;; $ guix shell openssl -- openssl genrsa -out /opt/secrets/dkim.arrobaponto.org.key 1024
+;; $ guix shell openssl -- openssl rsa -in /opt/secrets/dkim.arrobaponto.org.key -pubout -out /opt/secrets/dkim.arrobaponto.org.pub
+;; - manually load /etc/profile-extra, /etc/bashrc-extra and /etc/ps1.sh
+;; to ~/.bashrc and ~root/.bashrc
+;;
+
+;;
+;; Friendly reminder that I've actually installed and configure email + XMPP o
+;; a Debian server in Vultr! I just deleted the snapshot that I made in case
+;; of restoring, and I deleted it just now to save money and stop paying for it.
+;;
+
+;;
+;; TODO (FIXME):
+;; - maddy (spamassasin? fail2ban? rspamd? blacklistd?)
+;; - dns (knot)
+;; - prosody? matrix-conduit? Read again HN comments on Dino 0.3 release. Maybe manage both for a while.
+;; - httpd?
+;;
+
+;;
+;; FIXME:
+;; - resize machine
+;;
+
+
+(define profile-extra
+ (plain-file "profile-extra" #"""-
+ R='16686@ch-s010.rsync.net'
+ export BORG_REMOTE_PATH='borg1'
+ export BORG_PASSPHRASE_FD='/opt/secrets/borg-passphrase.txt'
+"""#))
+
+(define bashrc-extra
+ (plain-file "bashrc-extra" #"""-
+ alias l='ls -lahF --color'
+ alias info='info --init-file /etc/infokey'
+"""#))
+
+(define ps1.sh
+ (plain-file "ps1.sh" #"""-
+ end="\033[0m"
+
+ gray() {
+ printf "\033[0;90m$1$end"
+ }
+
+ yellow() {
+ printf "\033[1;33m$1$end"
+ }
+
+ red() {
+ printf "\033[1;31m$1$end"
+ }
+
+ redl() {
+ printf "\033[0;31m$1$end"
+ }
+
+ error_marker() {
+ STATUS=$?
+ if [ "$STATUS" != 0 ]; then
+ red " (!! $STATUS !!) "
+ fi
+ }
+
+ export PS1='`error_marker`'$(gray '\T')' '$(yellow '\w/')'\n'$(redl '\\u@\h')'\$ '
+"""#))
+
+(define infokey
+ (plain-file "infokey" #"""-
+ ^e down-line
+ ^y up-line
+"""#))
+
+(define with-email.sh
+ (plain-file "with-email.sh" #"""-
+ #!/bin/sh
+ set -u
+
+ while getopts 's:' flag; do
+ case "$flag" in
+ s)
+ SUBJECT="$OPTARG"
+ ;;
+ *)
+ exit 2
+ ;;
+ esac
+ done
+ shift $((OPTIND - 1))
+
+ now() {
+ date '+%Y-%m-%dT%H:%M:%S%Z'
+ }
+
+ OUT="$(echo 'mkstemp(template)' | m4 -D template="${TMPDIR:-/tmp}/m4-tmpname")"
+ printf 'Running command: %s\nStarting at: %s\n\n' "$*" "$(now)" >> "$OUT"
+ ("$@" 2>&1) >> "$OUT"
+ STATUS="$?"
+ printf '\nFinished at: %s\n' "$(now)" >> "$OUT"
+
+ mail \
+ -a 'Content-Type: text/plain; charset=UTF-8' \
+ -a 'From:cron@arrobaponto.org' \
+ -s "(exit status: $STATUS) - ${SUBJECT:-NO SUBJECT}" \
+ eu@euandre.org < "$OUT"
+
+ cat "$OUT"
+"""#))
+
+(define backup.sh
+ (plain-file "backup.sh" #"""-
+ #!/bin/sh
+ set -eux
+ . /etc/profile-extra
+
+ finish() {
+ STATUS=$?
+ printf '\n>>>\n>>> exit status: %s\n>>>\n\n' "$STATUS" >&2
+ }
+ trap finish EXIT
+
+ borg init -e repokey-blake2 "$R:toph-borg" ||:
+ borg key export "$R:toph-borg" /opt/secrets/borg-key.txt
+
+ borg create \
+ --exclude /root/.cache/ \
+ --stats \
+ --compression lzma,9 \
+ "R$:toph-borg::{hostname}-{now}-${1:-cronjob}" \
+ /root/ \
+ /home/ \
+ /etc/letsencrypt/ \
+ /var/lib/certbot/ \
+ /var/lib/letsencrypt \
+ /opt/secrets/ \
+ /srv/
+
+ borg check \
+ --verbose \
+ "$R:toph-borg"
+
+ borg prune \
+ --verbose \
+ --list \
+ --keep-within=6m \
+ --keep-weekly=52 \
+ --keep-monthly=24 \
+ "$R:toph-borg"
+"""#))
+
+(define vi.exrc
+ (plain-file "vi.exrc" #"""-
+ " set number
+ set autoindent
+ set tabstop=8
+ set shiftwidth=8
+ set ruler
+ set showmode
+ set showmatch
+"""#))
+
+(define opensmtpd.conf
+ (plain-file "opensmtpd.conf" #"""-
+ listen on 0.0.0.0
+
+ action inbound mbox
+ match for local action inbound
+
+ action outbound relay
+ match from local for any action outbound
+"""#))
+
+(define opensmtpd.conf1
+ (plain-file "opensmtpd.conf" #"""-
+ table aliases file:/etc/aliases
+ table creds { }
+
+ pki mail.arrobaponto.org cert "/etc/letsencrypt/live/arrobaponto.org/fullchain.pem"
+ pki mail.arrobaponto.org key "/etc/letsencrypt/live/arrobaponto.org/privkey.pem"
+
+ listen on eth0
+
+ filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } junk
+ filter check_rdns phase connect match !rdns junk
+ filter check_fcrdns phase connect match !fcrdns junk
+
+ accept from any for domain "arrobaponto.org" alias <aliases> deliver to maildir
+ accept for local alias <aliases> deliver to maildir
+
+ accept for any relay
+"""#))
+
+
+(define cronjobs
+ (list
+ #~(job "0 30 * * 0" "guix gc -d 1m -F 10G")
+ ;; FIXME: wat!? There is a /root/dead.letter file!?
+ ;; #~(job "* * * * *" "sh /etc/with-email.sh -s '[CRON] toph: xablau' -- seq 10 20 >&1")
+ #~(job "* * * * *" "whoami")
+ #~(job "* * * * *" "seq 20 30 >&2")))
+
+
+(define admin-user "andreh")
+
+(define andreh-pk
+ "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")
+
+(define users
+ `(((#:username . "andreh")
+ (#:extra-groups . ("wheel"))
+ (#:public-keys . (,andreh-pk)))))
+
+(define user-accounts
+ (map (lambda (user)
+ (let ((name (assoc-ref user #:username))
+ (groups (or (assoc-ref user #:extra-groups) '())))
+ (user-account
+ (name name)
+ (comment name)
+ (group "users")
+ (supplementary-groups groups))))
+ users))
+
+(define authorized-keys
+ (map (lambda (user)
+ (let* ((name (assoc-ref user #:username))
+ (pk-strs (assoc-ref user #:public-keys))
+ (pk-files (map (cut plain-file "id_rsa.pub" <>) pk-strs)))
+ `(,name . ,pk-files)))
+ users))
+
+
+(define toph
+ (operating-system
+ (locale "fr_FR.utf8")
+ (timezone "America/Sao_Paulo")
+ (host-name "toph")
+ (users (append user-accounts %base-user-accounts))
+ (sudoers-file (plain-file "sudoers" #"""-
+ root ALL=(ALL) ALL
+ %wheel ALL=NOPASSWD: ALL
+ """#))
+ (packages
+ (append (map (compose list specification->package+output symbol->string)
+ '(nss-certs ; required for guix pull
+ git-minimal
+ borg
+ m4))
+ %base-packages))
+ (services
+ (append
+ (list
+ (service openssh-service-type
+ (openssh-configuration
+ (port-number 38123)
+ (openssh openssh-sans-x)
+ (password-authentication? #f)
+ (subsystems '())
+ (log-level 'verbose)
+ (authorized-keys authorized-keys)
+ (extra-content #"""-
+ ClientAliveInterval 30
+ ClientAliveCountMax 20
+ MaxSessions 20
+ """#)))
+ (service dhcp-client-service-type)
+ (service mcron-service-type
+ (mcron-configuration
+ (jobs cronjobs)))
+ (simple-service 'extra-etc-file etc-service-type
+ `(("backup.sh" ,backup.sh)
+ ("profile-extra" ,profile-extra)
+ ("bashrc-extra" ,bashrc-extra)
+ ("ps1.sh" ,ps1.sh)
+ ("vi.exrc" ,vi.exrc)
+ ("infokey" ,infokey)
+ ("with-email.sh" ,with-email.sh)))
+ (service certbot-service-type
+ (certbot-configuration
+ (email "eu@euandre.org")
+ (certificates
+ (list
+ (certificate-configuration
+ (domains '("arrobaponto.org"))
+ (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
+ (run-directory "/var/run/nginx")
+ (server-blocks
+ (list
+ (nginx-server-configuration
+ (server-name '("arrobaponto.org"))
+ (listen '("[::]:443 ssl http2" "443 ssl http2"))
+ (root "/srv/http/arrobaponto.org")
+ (ssl-certificate "/etc/letsencrypt/live/arrobaponto.org/fullchain.pem")
+ (ssl-certificate-key "/etc/letsencrypt/live/arrobaponto.org/privkey.pem")
+ (raw-content '(#"""-
+ autoindex on;
+ add_header Strict-Transport-Security 'max-age=86400; includeSubdomains' always;
+ """#)))))))
+ (service mail-aliases-service-type
+ `(("webmaster" ,admin-user)
+ ("abuse" ,admin-user)
+ ("root" ,admin-user)
+ ("postmaster" ,admin-user)))
+ (service opensmtpd-service-type
+ (opensmtpd-configuration
+ (config-file opensmtpd.conf))))
+ (modify-services
+ %base-services
+ (guix-service-type
+ config => (guix-configuration
+ (inherit config)
+ (authorized-keys
+ (append
+ (list
+ (local-file "/etc/guix/signing-key.pub"))
+ %default-authorized-guix-keys)))))))
+ (bootloader
+ (bootloader-configuration
+ (bootloader grub-bootloader)
+ (targets '("/dev/vda"))))
+ (swap-devices
+ (list
+ (swap-space
+ (target (uuid "30122738-f2f6-4f93-a37c-62023f56c73b")))))
+ (file-systems
+ (append
+ (list
+ (file-system
+ (mount-point "/")
+ (device
+ (uuid "455682d0-7ce7-4144-9728-a6d07beb049a"
+ 'btrfs))
+ (type "btrfs")))
+ %base-file-systems))))
+
+
+
+(list
+ (machine
+ (operating-system toph)
+ (environment managed-host-environment-type)
+ (configuration (machine-ssh-configuration
+ (host-name "toph")
+ (system "x86_64-linux")
+ (user admin-user)
+ (port 38123)
+ (host-key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoz1gFl6chY91vQ5SrZXSP5yHqRI3TdYy2ccEDpS7Z4")))))
+
+;; toph -> euandre.org
+;; kuvira -> euandreh.xyz
+;; ??? -> arrobaponto.org
+;; asami -> discussions.site
+;; zhu-li -> mediator.ht
+;; lily -> hinarioespirita.org ; musician
+;; kyoshi -> standardify.sh ; standardtized warriors
+;; suyin -> rsync.net ; city with a metal shell
+;; ??? -> amber.ht
+;; yangchen -> multipatch.xyz
+;; mai -> mailbug.xyz
diff --git a/src/infrastructure/rsync.net/authorized_keys b/src/infrastructure/rsync.net/authorized_keys
new file mode 100644
index 0000000..ba622d5
--- /dev/null
+++ b/src/infrastructure/rsync.net/authorized_keys
@@ -0,0 +1,4 @@
+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
+command="borg1 serve --append-only --restrict-to-repository toph-borg/",restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAIZpXwNI5HVH7lUOterphIdUlX/a0nCuhb/XxbdDvU0tj3wjT7wGhCU9T9oaY2aNg5XVuWtPzLv5oLMW4eATlXw65knii2dU1Tp28vfD99aqbampxbbNwsJZ62jRhQwGhsehNREDEiNo+5llJNY4tMXEIpHXlQohemORVBIYa99VymWNiVQhW7vpH4SsbXh2hc9bAU0WWTU3COBvrSJ1VDYlhE08NJsOkUrwrP17U6ZkVDuPHoBQBEwHuBvQd86ogiHnZijRoz7nAH4ZIWYrXL6f/itA6Fw6C3G7yOrJvNbuYg0N176/qGjw9RfyuE5001zWem41hP2Z7+TCvzrhH7MjvtPSjixXrObjYSY0DI8GuaK/1fTOeVgc7pTfJ6S1ZEUMcQlEKTPlce8Jkrx5M1JOP0nnfABb0ZQB/6gNjw7LCcMEJrKjxBffNuloGVQSt4wuGyDlNIe0zZXLXxJPgc0NgIgrRNif5lF/KO+cNm/XDQ548JdYXnwGb6Jxhg8s= andreh@toph
+command="borg1 serve --append-only --restrict-to-repository kuvira-borg/",restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCw3VGwWQNpGvu1OFdVzwixovSMadlu3QUqTFADYxdBOX0tilf37LJQElaoyNVLTYOTsDTgfcTfpB+Qo6pdyJhbFYV3DyGdssRSxWgFv3mCMlGm0QzDGLLt+ebYBcc67aQytXTaA10EVVmcs/yKTXsEwiI7kHnZlBOt+8llAg/li6Vd+oiLdnEw6Y6wPcY/WOO7/UtlH4pBxrhibV5i04ngd+IFOvsNRpVgCPnS4OuNquNFdm5xkiKN0nwkzFrKS3ECCbo4wxAirjAjAT+vRiu5mi32P5PDflWajrAzrruALD+htiDjm++jkYrKWQoDpH6Vqlow8kdSQEpUbykXn43beLOKvQ3GAC2AjjP+cZmyYp7Lag9hS4ROL68ckDE0W10i90sxO9E4+6e0nM4mEkOdj4791Xi0/8GSYDMptHFT+hHJ2w0xdgFYU275MWPYlhaOFx2ak4WVPkjyS6avIhLhRKlv6LkrK5v+Z5RwmK7fexjxm15e6HoN9/pxxqSXBY8= andreh@kuvira
+command="borg1 serve --append-only --restrict-to-repository iroh-borg/",restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJRGX6A/oXJ8hxE+JrUIalhsAZSk90CdQ28EkTN9Em4Aoa5sDX9u4lz9sVIPipAvKYsUZd35GArmGRMVjO2bXfPG0o0VKiHUtfy7Q422EvI9CSi3+FUTnDrrzeq73yFa5v2ANY+D0PeXzrWfltBTQMaLUYmfeQwYRyuWqftxMuGlxNeSkIJ2ySvHbdmfrWi55ae9Fs6xiB3ZdREmRse9RUWbgAL2FVRhDerDqHR1IGbtk4pfGIDsOB85i1TqnaaI9xIa4t6x0dsuoyb5UTGCXhUxBHi5kgEXHDNiL73OxJur7oAXW4I/x1QkXDZpOEsqVTiIVbwRmAerXMZBA8WTEB root@box.euandre.org
diff --git a/src/infrastructure/ssh.conf b/src/infrastructure/ssh.conf
new file mode 100644
index 0000000..790e5f0
--- /dev/null
+++ b/src/infrastructure/ssh.conf
@@ -0,0 +1,19 @@
+Host *
+ ServerAliveInterval 30
+ ServerAliveCountMax 20
+
+Host iroh *euandre.org
+ HostName euandre.org
+ User root
+
+Host kuvira *euandreh.xyz
+ HostName euandreh.xyz
+ Port 23841
+
+Host toph *arrobaponto.org
+ HostName arrobaponto.org
+ Port 38123
+
+Host suyin
+ HostName ch-s010.rsync.net
+ User 16686