aboutsummaryrefslogtreecommitdiff
path: root/terraform
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2018-12-04 21:31:01 -0800
committerJakob Odersky <jakob@odersky.com>2018-12-04 21:39:07 -0800
commit9588e9366d3455f203e5482a41f712777595bb13 (patch)
tree272aeababb1b68f477301d67198a82c80d044c01 /terraform
parentdb27247dd7d7209ab93419eb33d2ecb21e74c1ec (diff)
downloadinfra-9588e9366d3455f203e5482a41f712777595bb13.tar.gz
infra-9588e9366d3455f203e5482a41f712777595bb13.tar.bz2
infra-9588e9366d3455f203e5482a41f712777595bb13.zip
Simplify terraform and provisioning scripts. Move away from config packages.
Diffstat (limited to 'terraform')
-rwxr-xr-xterraform/deploy3
-rw-r--r--[-rwxr-xr-x]terraform/main.tf126
-rw-r--r--terraform/mount_volume/main.tf104
-rwxr-xr-xterraform/provision/provision81
-rw-r--r--terraform/provision/rootfs/etc/apt/apt.conf.d/20auto-upgrades2
-rw-r--r--terraform/provision/rootfs/etc/cgitrc.d/crashbox63
-rw-r--r--terraform/provision/rootfs/etc/gh-mirror4
-rw-r--r--terraform/provision/rootfs/etc/nginx/conf.d/ssl.conf15
-rw-r--r--terraform/provision/rootfs/etc/nginx/sites-enabled/default.conf9
-rw-r--r--terraform/provision/rootfs/etc/nginx/sites-enabled/git.conf33
-rw-r--r--terraform/provision/rootfs/etc/nginx/sites-enabled/ip.conf13
-rwxr-xr-xterraform/provision/rootfs/usr/bin/gh-mirror59
-rwxr-xr-xterraform/provision/rootfs/usr/bin/gh-mirror-all7
-rw-r--r--terraform/provision/rootfs/var/lib/git/www/about.md5
-rw-r--r--terraform/provision/rootfs/var/lib/git/www/crashbox.svg84
-rw-r--r--terraform/provision/rootfs/var/lib/git/www/instagram.pngbin0 -> 44502 bytes
-rw-r--r--terraform/role/README.md4
-rw-r--r--terraform/role/main.tf91
-rw-r--r--terraform/root-ssh-key1
-rw-r--r--terraform/stdvps/README.md2
-rw-r--r--terraform/stdvps/main.tf148
-rw-r--r--terraform/test/.gitignore1
-rw-r--r--terraform/test/Makefile62
-rw-r--r--terraform/test/README.md26
-rwxr-xr-xterraform/test/vm/customize.sh21
-rwxr-xr-xterraform/tf3
26 files changed, 702 insertions, 265 deletions
diff --git a/terraform/deploy b/terraform/deploy
new file mode 100755
index 0000000..a7fdf57
--- /dev/null
+++ b/terraform/deploy
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+terraform apply -var-file=<(pass infra/terraform)
diff --git a/terraform/main.tf b/terraform/main.tf
index b25598e..e29cf6a 100755..100644
--- a/terraform/main.tf
+++ b/terraform/main.tf
@@ -16,36 +16,128 @@ provider "cloudflare" {
}
provider "acme" {
- #server_url = "https://acme-staging-v02.api.letsencrypt.org/directory"
server_url = "https://acme-v02.api.letsencrypt.org/directory"
}
-################################################################################
+############################################################
resource "hcloud_ssh_key" "root" {
name = "root"
- public_key = "${file("~/.ssh/id_rsa.pub")}"
+ public_key = "${file("root-ssh-key")}"
}
-module "vps" {
- source = "./stdvps"
- location = "nbg1"
- ssh_key_name = "${hcloud_ssh_key.root.name}"
+resource "tls_private_key" "private_key" {
+ algorithm = "RSA"
+}
+
+resource "acme_registration" "registration" {
+ account_key_pem = "${tls_private_key.private_key.private_key_pem}"
+ email_address = "jakob@odersky.com"
+}
+
+resource "acme_certificate" "certificate" {
+ account_key_pem = "${acme_registration.registration.account_key_pem}"
+ common_name = "crashbox.io"
+
+ subject_alternative_names = [
+ "ip.crashbox.io",
+ "git.crashbox.io",
+ ]
+
+ dns_challenge {
+ provider = "cloudflare"
+
+ config {
+ CLOUDFLARE_EMAIL = "jakob@odersky.com"
+ CLOUDFLARE_API_KEY = "${var.secret_cloudflare_token}"
+ }
+ }
+}
+
+resource "cloudflare_record" "record_caa" {
+ domain = "crashbox.io"
+ name = "crashbox.io"
+
+ data = {
+ flags = "0"
+ tag = "issue"
+ value = "letsencrypt.org"
+ }
+
+ type = "CAA"
+}
+
+resource "random_id" "peter" {
+ prefix = "peter-"
+ byte_length = 2
+}
+
+resource "hcloud_server" "peter" {
+ name = "${random_id.peter.hex}"
+ image = "debian-9"
+ server_type = "cx11"
+ location = "nbg1"
+ ssh_keys = ["${hcloud_ssh_key.root.name}"]
+
+ provisioner "file" {
+ content = "${acme_certificate.certificate.private_key_pem}"
+ destination = "/etc/ssl/private/server.key.pem"
+ }
+
+ provisioner "file" {
+ content = "${acme_certificate.certificate.certificate_pem}"
+ destination = "/etc/ssl/server.cert.pem"
+ }
+
+ provisioner "file" {
+ content = "${acme_certificate.certificate.issuer_pem}"
+ destination = "/etc/ssl/issuer.cert.pem"
+ }
+
+ provisioner "file" {
+ source = "./provision"
+ destination = "/usr/local/share/"
+ }
+
+ provisioner "remote-exec" {
+ inline = [
+ "chmod +x /usr/local/share/provision/provision",
+ "/usr/local/share/provision/provision --force",
+ ]
+ }
+}
+
+module "peter_mount_volume" {
+ source = "./mount_volume"
volume_name = "master"
+ host = "${hcloud_server.peter.ipv4_address}"
+ server_id = "${hcloud_server.peter.id}"
+}
+
+resource "cloudflare_record" "peter_a" {
+ domain = "crashbox.io"
+ name = "${hcloud_server.peter.name}"
+ value = "${hcloud_server.peter.ipv4_address}"
+ type = "A"
}
-module "roles" {
- source = "./role"
- secret_cloudflare_token = "${var.secret_cloudflare_token}"
- host = "${module.vps.fqdn}"
- id = "${module.vps.id}"
- roles = ["ip", "git"]
+resource "cloudflare_record" "peter_aaaa" {
+ domain = "crashbox.io"
+ name = "${hcloud_server.peter.name}"
+ value = "${hcloud_server.peter.ipv6_address}1"
+ type = "AAAA"
}
-output "vps_address" {
- value = "${module.vps.fqdn}"
+resource "cloudflare_record" "record_ip" {
+ domain = "crashbox.io"
+ name = "ip"
+ value = "${cloudflare_record.peter_a.hostname}"
+ type = "CNAME"
}
-output "vps_roles" {
- value = "${join(" ", module.roles.roles)}"
+resource "cloudflare_record" "record_git" {
+ domain = "crashbox.io"
+ name = "git"
+ value = "${cloudflare_record.peter_a.hostname}"
+ type = "CNAME"
}
diff --git a/terraform/mount_volume/main.tf b/terraform/mount_volume/main.tf
new file mode 100644
index 0000000..aed5324
--- /dev/null
+++ b/terraform/mount_volume/main.tf
@@ -0,0 +1,104 @@
+variable "volume_name" {
+ type = "string"
+}
+
+variable "host" {
+ type = "string"
+}
+
+variable "server_id" {
+ type = "string"
+}
+
+# volumes contain persistent storage and thus need to be initialized
+# manually
+data "hcloud_volume" "master" {
+ name = "${var.volume_name}"
+}
+
+resource "hcloud_volume_attachment" "master_attachment" {
+ volume_id = "${data.hcloud_volume.master.id}"
+ server_id = "${var.server_id}"
+}
+
+resource "null_resource" "volume_mount" {
+ triggers = {
+ attachement_id = "${hcloud_volume_attachment.master_attachment.id}"
+ }
+
+ connection {
+ host = "${var.host}"
+ }
+
+ provisioner "remote-exec" {
+ inline = ["mkdir -p /mnt/storage"]
+ }
+
+ provisioner "file" {
+ content = <<EOF
+[Unit]
+Description=Mount /mnt/storage directory
+
+[Mount]
+What=${data.hcloud_volume.master.linux_device}
+Where=/mnt/storage
+Type=ext4
+Options=defaults
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+ destination = "/etc/systemd/system/mnt-storage.mount"
+ }
+
+ provisioner "file" {
+ content = <<EOF
+[Unit]
+Description=Mount /srv to persistent volume storage
+After=mnt-storage.mount
+BindsTo=mnt-storage.mount
+
+[Mount]
+What=/mnt/storage/srv
+Where=/srv
+Type=ext4
+Options=bind
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+ destination = "/etc/systemd/system/srv.mount"
+ }
+
+ provisioner "file" {
+ content = <<EOF
+[Unit]
+Description=Mount /home to persistent volume storage
+After=mnt-storage.mount
+BindsTo=mnt-storage.mount
+
+[Mount]
+What=/mnt/storage/home
+Where=/home
+Type=ext4
+Options=bind
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+ destination = "/etc/systemd/system/home.mount"
+ }
+
+ provisioner "remote-exec" {
+ inline = [
+ "systemctl daemon-reload",
+ "systemctl enable mnt-storage.mount",
+ "systemctl enable srv.mount",
+ "systemctl enable home.mount",
+ "systemctl reboot",
+ ]
+ }
+}
diff --git a/terraform/provision/provision b/terraform/provision/provision
new file mode 100755
index 0000000..3a32a63
--- /dev/null
+++ b/terraform/provision/provision
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+panic() {
+ echo "$1" >&2
+ echo "Aborting."
+ exit 1
+}
+
+[[ $1 == "--force" ]] || panic "Must be run with --force"
+[[ $(id --user) -eq 0 ]] || panic "This script must be run as root."
+
+log() {
+ echo "provision: $1" >&2
+}
+
+log "install and configure most essential packages"
+apt-get update --quiet=2
+apt-get install --yes --quiet=2 ufw
+ufw allow 22/tcp
+ufw default deny
+ufw --force enable
+
+log "install service packages"
+apt-get install --yes --quiet=2 \
+ adduser \
+ apt-listchanges \
+ ca-certificates \
+ cgit \
+ curl \
+ fcgiwrap \
+ git-core \
+ jq \
+ nginx \
+ openssl \
+ python3-markdown \
+ python3-pygments \
+ rsync \
+ ssl-cert \
+ sudo \
+ ufw \
+ unattended-upgrades \
+ wget
+
+log "copy package configurations"
+rsync -r /usr/local/share/provision/rootfs/ /
+
+log "ensure certificate bundle exists"
+# the ceritifcate bundle should be provisioned by terraform, however
+# for testing purposes (such as in a vm) this copies the default
+# "snakeoil" test certificates to the appropriate locations if they do
+# not already exist
+if [[ ! -r /etc/ssl/private/server.key.pem ]] \
+ || [[ ! -r /etc/ssl/server.cert.pem ]] \
+ || [[ ! -r /etc/ssl/issuer.cert.pem ]]; then
+ ln -f -s /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/private/server.key.pem
+ ln -f -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/server.cert.pem
+ ln -f -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/issuer.cert.pem
+ log "WARNING: no certificates found, falling back to snakeoil certificates!"
+fi
+
+log "configure nginx"
+rm -r /etc/nginx/sites-enabled/default
+usermod --append --groups ssl-cert www-data
+ufw allow 80/tcp
+ufw allow 443/tcp
+
+log "configure git"
+adduser --group --system --home /var/lib/git git
+mkdir -p /srv/git
+chown -R git:git /srv/git
+mkdir -p /var/lib/git/www/
+ln -s /usr/share/cgit/cgit.css /var/lib/git/www/cgit.css
+ln -s /usr/share/cgit/robots.txt /var/lib/git/www/robots.txt
+
+log "configure shell accounts"
+adduser --uid 1000 --disabled-password --gecos "" jodersky
+
+log "restart services"
+systemctl restart nginx
+
+log "configuration complete!"
diff --git a/terraform/provision/rootfs/etc/apt/apt.conf.d/20auto-upgrades b/terraform/provision/rootfs/etc/apt/apt.conf.d/20auto-upgrades
new file mode 100644
index 0000000..8d6d7c8
--- /dev/null
+++ b/terraform/provision/rootfs/etc/apt/apt.conf.d/20auto-upgrades
@@ -0,0 +1,2 @@
+APT::Periodic::Update-Package-Lists "1";
+APT::Periodic::Unattended-Upgrade "1";
diff --git a/terraform/provision/rootfs/etc/cgitrc.d/crashbox b/terraform/provision/rootfs/etc/cgitrc.d/crashbox
new file mode 100644
index 0000000..fdafab6
--- /dev/null
+++ b/terraform/provision/rootfs/etc/cgitrc.d/crashbox
@@ -0,0 +1,63 @@
+#
+# cgit config
+# see cgitrc(5) for details
+#
+# https://git.zx2c4.com/cgit/tree/cgitrc.5.txt
+
+favicon=/crashbox.svg
+logo=/crashbox.svg
+root-title=git.crashbox.io
+root-desc=Git repositories hosted at crashbox.io
+root-readme=/var/lib/git/www/about.md
+clone-url=https://git.crashbox.io/$CGIT_REPO_URL
+
+## List of common mimetypes
+mimetype.gif=image/gif
+mimetype.html=text/html
+mimetype.jpg=image/jpeg
+mimetype.jpeg=image/jpeg
+mimetype.pdf=application/pdf
+mimetype.png=image/png
+mimetype.svg=image/svg+xml
+mimetype-file=/etc/mime.types
+
+# Don't show owner on index page
+enable-index-owner=0
+
+# Enable blame page and create links to it from tree page
+enable-blame=1
+
+# Enable ASCII art commit history graph on the log pages
+enable-commit-graph=1
+
+# Show extra links for each repository on the index page
+enable-index-links=1
+
+# Show number of affected files per commit on the log pages
+enable-log-filecount=1
+
+# Show number of added/removed lines per commit on the log pages
+enable-log-linecount=1
+
+# Allow download of tar.gz, tar.bz2 and zip-files
+snapshots=tar.gz tar.bz2 zip
+
+# Highlight code
+source-filter=/usr/lib/cgit/filters/syntax-highlighting.py
+
+# Format "about" files such as markdown readmes
+about-filter=/usr/lib/cgit/filters/about-formatting.sh
+readme=master:README.md
+
+# nginx handles negotiating git clones
+enable-http-clone=0
+
+section-from-path=-1
+
+# Remove ".git" suffix in listings
+remove-suffix=1
+
+# Base URL
+virtual-root=/
+
+scan-path=/srv/git
diff --git a/terraform/provision/rootfs/etc/gh-mirror b/terraform/provision/rootfs/etc/gh-mirror
new file mode 100644
index 0000000..4fc987b
--- /dev/null
+++ b/terraform/provision/rootfs/etc/gh-mirror
@@ -0,0 +1,4 @@
+users jodersky /srv/git/mirrors/github/jodersky
+orgs project-condor /srv/git/mirrors/github/project-condor
+orgs driver-oss /srv/git/mirrors/github/driver-oss
+orgs johnandjohn /srv/git/mirrors/github/johnandjohn
diff --git a/terraform/provision/rootfs/etc/nginx/conf.d/ssl.conf b/terraform/provision/rootfs/etc/nginx/conf.d/ssl.conf
new file mode 100644
index 0000000..bb96ec7
--- /dev/null
+++ b/terraform/provision/rootfs/etc/nginx/conf.d/ssl.conf
@@ -0,0 +1,15 @@
+# The configuration below can be obtained with the Mozilla SSL
+# Configuration Generator at
+# https://mozilla.github.io/server-side-tls/ssl-config-generator/
+
+ssl_certificate /etc/ssl/server.cert.pem;
+ssl_certificate_key /etc/ssl/private/server.key.pem;
+ssl_session_timeout 1d;
+ssl_session_cache shared:SSL:50m;
+ssl_session_tickets off;
+
+ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
+
+ssl_stapling on;
+ssl_stapling_verify on;
+ssl_trusted_certificate /etc/ssl/issuer.cert.pem;
diff --git a/terraform/provision/rootfs/etc/nginx/sites-enabled/default.conf b/terraform/provision/rootfs/etc/nginx/sites-enabled/default.conf
new file mode 100644
index 0000000..e10725d
--- /dev/null
+++ b/terraform/provision/rootfs/etc/nginx/sites-enabled/default.conf
@@ -0,0 +1,9 @@
+# Default catch-all configuration, applied when no other configuration matches
+server {
+ server_name _;
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ # close the connection without sending a response
+ return 444;
+} \ No newline at end of file
diff --git a/terraform/provision/rootfs/etc/nginx/sites-enabled/git.conf b/terraform/provision/rootfs/etc/nginx/sites-enabled/git.conf
new file mode 100644
index 0000000..7210dbc
--- /dev/null
+++ b/terraform/provision/rootfs/etc/nginx/sites-enabled/git.conf
@@ -0,0 +1,33 @@
+server {
+ server_name git.*;
+ listen 80;
+ listen [::]:80;
+ listen 443 ssl;
+ listen [::]:443 ssl;
+
+ root /var/lib/git/www;
+
+ # requests that should to go to git-http-backend
+ location ~ ^.*/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {
+ root /srv/git;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
+ fastcgi_param GIT_PROJECT_ROOT /srv/git;
+ fastcgi_param GIT_HTTP_EXPORT_ALL "";
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param CGIT_CONFIG /etc/cgitrc.d/crashbox;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+
+ location / {
+ try_files $uri @cgit;
+ }
+
+}
diff --git a/terraform/provision/rootfs/etc/nginx/sites-enabled/ip.conf b/terraform/provision/rootfs/etc/nginx/sites-enabled/ip.conf
new file mode 100644
index 0000000..2f3ab1e
--- /dev/null
+++ b/terraform/provision/rootfs/etc/nginx/sites-enabled/ip.conf
@@ -0,0 +1,13 @@
+# Echo remote IP address
+# https://michael.lustfield.net/nginx/simple-ip-echo
+server {
+ server_name ip.*;
+ listen 80;
+ listen [::]:80;
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ location = / {
+ default_type text/plain;
+ echo $remote_addr;
+ }
+} \ No newline at end of file
diff --git a/terraform/provision/rootfs/usr/bin/gh-mirror b/terraform/provision/rootfs/usr/bin/gh-mirror
new file mode 100755
index 0000000..54985cb
--- /dev/null
+++ b/terraform/provision/rootfs/usr/bin/gh-mirror
@@ -0,0 +1,59 @@
+#!/bin/bash
+# Mirror repositories from GitHub
+#
+# Arguments: (users|orgs) <name> <output_directory>
+#
+# Clones (or updates) all repositories of a GitHub user or
+# organization. Repositories are created as children of the given
+# output directory.
+#
+# Example:
+# gh-mirror users jodersky mirrors/github/jodersky
+#
+# This script uses GitHub's API, version 3
+# https://developer.github.com/v3/repos/#list-user-repositories
+set -o errexit
+
+account_type="$1"
+account_name="$2"
+out_dir="${3:-.}"
+mkdir -p "$out_dir"
+
+if [[ -z $account_type ]] || [[ -z $account_name ]]; then
+ echo "Usage: (users|orgs) <name> <output_directory>" >&2
+ exit 1
+fi
+
+tmp="$(mktemp /tmp/mirror-XXXXXXXXXXXX)"
+url="https://api.github.com/$account_type/$account_name/repos?per_page=100"
+
+function finish {
+ echo "An error was encountered." >&2
+ echo "curl headers are saved in $tmp" >&2
+}
+trap finish ERR
+
+while [[ ! -z "$url" ]]; do
+ echo "Fetching $url..." >&2
+
+ mapfile -t repo_data < <(curl --dump-header "$tmp" "$url" | jq --compact-output '.[]')
+ url="$(< "$tmp" grep Link | grep -oE "[a-zA-Z0-9:/?=.&_]*>; rel=.next" | cut -d'>' -f1)"
+
+ for repo in "${repo_data[@]}"; do
+ clone_url="$(echo "$repo" | jq -r .clone_url)"
+ project="$(basename "$clone_url")"
+ description=$(echo "$repo" | jq -r .description)
+
+ git_dir="$out_dir/$project"
+
+ if [ -d "$git_dir" ]; then
+ echo "updating $project" >&2
+ git -C "$git_dir" fetch --prune
+ else
+ echo "mirroring new $project" >&2
+ git clone --mirror "$clone_url" "$git_dir"
+ fi
+ echo "$description" > "$git_dir/description"
+ done
+done
+rm "$tmp"
diff --git a/terraform/provision/rootfs/usr/bin/gh-mirror-all b/terraform/provision/rootfs/usr/bin/gh-mirror-all
new file mode 100755
index 0000000..fa9054f
--- /dev/null
+++ b/terraform/provision/rootfs/usr/bin/gh-mirror-all
@@ -0,0 +1,7 @@
+#!/bin/bash
+mapfile -t lines < /etc/gh-mirror
+
+for line in "${lines[@]}"; do
+ read -r type name dir <<< "$line"
+ gh-mirror "$type" "$name" "$dir"
+done
diff --git a/terraform/provision/rootfs/var/lib/git/www/about.md b/terraform/provision/rootfs/var/lib/git/www/about.md
new file mode 100644
index 0000000..55e68fa
--- /dev/null
+++ b/terraform/provision/rootfs/var/lib/git/www/about.md
@@ -0,0 +1,5 @@
+Tracking of various git repositories.
+
+![instagram](instagram.png)
+
+<https://xkcd.com/1150/>
diff --git a/terraform/provision/rootfs/var/lib/git/www/crashbox.svg b/terraform/provision/rootfs/var/lib/git/www/crashbox.svg
new file mode 100644
index 0000000..87ff69c
--- /dev/null
+++ b/terraform/provision/rootfs/var/lib/git/www/crashbox.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64"
+ height="64"
+ viewBox="0 0 16.933333 16.933334"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="crashbox.svg"
+ inkscape:export-filename="/home/jodersky/.background.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8.1454544"
+ inkscape:cx="49.880572"
+ inkscape:cy="41.270535"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1080"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-smooth-nodes="true"
+ units="px"
+ inkscape:snap-page="true" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-280.06665)">
+ <g
+ id="g885"
+ transform="matrix(0,0.57000354,-0.57000354,0,156.29365,249.65986)"
+ style="fill:#cccccc">
+ <path
+ id="path871"
+ d="m 53.344914,259.34398 c 0,-0.44557 7.040667,-12.63991 7.426581,-12.86269 0.385913,-0.22278 14.467808,-0.22279 14.853711,0 0.385913,0.22279 7.427142,12.41711 7.427142,12.86269 1e-5,0.44557 -7.041229,12.6399 -7.427142,12.86269 -0.385903,0.22279 -14.467798,0.22278 -14.853711,-10e-6 -0.385914,-0.22278 -7.426581,-12.41711 -7.426581,-12.86268 z m 0.938299,1e-5 c 0,0.41739 6.596177,11.84071 6.957712,12.0494 0.361535,0.20871 13.553877,0.20872 13.915412,0 0.361535,-0.20869 6.957712,-11.63201 6.957712,-12.04941 0,-0.4174 -6.596177,-11.84072 -6.957712,-12.04943 -0.361535,-0.20869 -13.553877,-0.2087 -13.915412,0 -0.361535,0.2087 -6.957712,11.63202 -6.957712,12.04944 z"
+ style="fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.17204435;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path873"
+ d="m 68.986516,259.38836 c 0,-0.34087 5.34873,-9.60935 5.70206,-9.88448 0.53173,0.60355 5.66767,9.50847 5.66765,9.84278 10e-6,0.3409 -5.35009,9.61103 -5.70248,9.88449 -0.53273,-0.60539 -5.66719,-9.50851 -5.66723,-9.84275 z"
+ style="fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:0.15927917;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/terraform/provision/rootfs/var/lib/git/www/instagram.png b/terraform/provision/rootfs/var/lib/git/www/instagram.png
new file mode 100644
index 0000000..dcaff14
--- /dev/null
+++ b/terraform/provision/rootfs/var/lib/git/www/instagram.png
Binary files differ
diff --git a/terraform/role/README.md b/terraform/role/README.md
deleted file mode 100644
index 11e2e21..0000000
--- a/terraform/role/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Role-based configuration for standalone hosts.
-
-Applying a role to a host will install corresponding config packages
-and create a role CNAME record to the host.
diff --git a/terraform/role/main.tf b/terraform/role/main.tf
deleted file mode 100644
index e85fd3b..0000000
--- a/terraform/role/main.tf
+++ /dev/null
@@ -1,91 +0,0 @@
-variable "host" {
- type = "string"
-}
-
-variable "id" {
- type = "string"
-}
-
-variable "roles" {
- type = "list"
-}
-
-variable "secret_cloudflare_token" {
- type = "string"
-}
-
-resource "tls_private_key" "private_key" {
- algorithm = "RSA"
-}
-
-resource "acme_registration" "reg" {
- account_key_pem = "${tls_private_key.private_key.private_key_pem}"
- email_address = "jakob@odersky.com"
-}
-
-resource "acme_certificate" "certificate" {
- account_key_pem = "${acme_registration.reg.account_key_pem}"
- common_name = "${var.host}"
- subject_alternative_names = "${formatlist("%s.crashbox.io", var.roles)}"
-
- dns_challenge {
- provider = "cloudflare"
-
- config {
- CLOUDFLARE_EMAIL = "jakob@odersky.com"
- CLOUDFLARE_API_KEY = "${var.secret_cloudflare_token}"
- }
- }
-}
-
-resource "cloudflare_record" "role_cname" {
- count = "${length(var.roles)}"
-
- domain = "crashbox.io"
- name = "${element(var.roles, count.index)}"
- value = "${var.host}"
- type = "CNAME"
-}
-
-resource "null_resource" "role_config" {
- triggers = {
- host_id = "${var.id}"
- config_packages = "${join(" ", sort(formatlist("crashbox-%s-config", var.roles)))}"
- }
-
- connection {
- host = "${var.host}"
- }
-
- provisioner "file" {
- content = "${acme_certificate.certificate.certificate_pem}"
- destination = "/etc/ssl/server.cert.pem"
- }
-
- provisioner "file" {
- content = "${acme_certificate.certificate.issuer_pem}"
- destination = "/etc/ssl/issuer.cert.pem"
- }
-
- provisioner "file" {
- content = "${acme_certificate.certificate.private_key_pem}"
- destination = "/etc/ssl/private/server.key.pem"
- }
-
- provisioner "file" {
- source = "${path.root}/../packages/target/archive"
- destination = "/usr/local/share/"
- }
-
- provisioner "remote-exec" {
- inline = [
- "echo deb [trusted=yes] file:/usr/local/share/archive ./ > /etc/apt/sources.list.d/local-archive.list",
- "apt update --quiet=2",
- "apt install --quiet=2 --yes ${null_resource.role_config.triggers.config_packages}",
- ]
- }
-}
-
-output "roles" {
- value = "${var.roles}"
-}
diff --git a/terraform/root-ssh-key b/terraform/root-ssh-key
new file mode 100644
index 0000000..11a7a78
--- /dev/null
+++ b/terraform/root-ssh-key
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDG5x/YyeCRcIV9TsdyahDpSKQkn/78967VVFvMNjBxDj1/NekINjTgNep7QUxiT16wKH2HFc9ahRGHZ/vK71JDHGZUic+DgvC8lHTTv/Y6aBtstZ9uEv9lz1OOgUDz2sfxLfajCr+40LbgBv8CAJSLBkB0PD5CypUcOtjaD07E6xQD4lMTL1/wsgbLcx4tIQRdQjUziT5d0Zlx9geV/cR4Z3YMNILKMk6vsh4vf3P7qebXv9XOjXE091EIgkfbawYmaB6zcBXoWYDdjPQ+YybqgLFTIjM2OiBmOI+eQFSFlALhP6HUjgbjOub+DnZDGWhH8SlCft6gVMK1lD+Sgud+57R4pXMHIPrIyOJcBF4cWX8biTPg4iLk01sF8hE/5J8fEArs48IhAFj91WW9O/l5torwB5P2g1lM9JxIyqZWSAS63oV4G+8CkgP4TdLYT/LA08F+a40CRateP5WvRGlk1WosMaN+IVHRYd8iFtJBT78hnPyFx3d9UoTCCFskigAueedNzw545nZOJJ4oXbw4WvlTkS/bPDBrj62XRPyrlFPimXLrfGKuY/N7f2wiWbV1Al2EkNurXVUg9sekXEd5cSYyghI14Qp5CyN+D2xkqwNKxC+WL0svIF1wZu39KZaQ2tOFy4ZYae0vut0LjFhJejo+TILLCSeyWf1vVPFrPw== cardno:000605258808
diff --git a/terraform/stdvps/README.md b/terraform/stdvps/README.md
deleted file mode 100644
index 316641c..0000000
--- a/terraform/stdvps/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# stdvps
-A basic cloud virtual machine.
diff --git a/terraform/stdvps/main.tf b/terraform/stdvps/main.tf
deleted file mode 100644
index 2e94f1d..0000000
--- a/terraform/stdvps/main.tf
+++ /dev/null
@@ -1,148 +0,0 @@
-variable "ssh_key_name" {
- type = "string"
-}
-
-variable "location" {
- type = "string"
-}
-
-variable "volume_name" {
- type = "string"
- default = ""
-}
-
-resource "random_id" "server" {
- prefix = "peter-"
- byte_length = 2
-}
-
-resource "hcloud_server" "server" {
- name = "${random_id.server.hex}.crashbox.io"
- image = "debian-9"
- server_type = "cx11"
- location = "${var.location}"
- ssh_keys = ["${var.ssh_key_name}"]
-}
-
-resource "cloudflare_record" "record_a" {
- domain = "crashbox.io"
- name = "${hcloud_server.server.name}"
- value = "${hcloud_server.server.ipv4_address}"
- type = "A"
-}
-
-resource "cloudflare_record" "record_aaaa" {
- domain = "crashbox.io"
- name = "${hcloud_server.server.name}"
- value = "${hcloud_server.server.ipv6_address}1"
- type = "AAAA"
-}
-
-resource "hcloud_volume" "master" {
- count = "${var.volume_name == "" ? 0 : 1}"
- name = "${var.volume_name}"
- size = 50
- server_id = "${hcloud_server.server.id}"
-}
-
-# volumes contain persistent storage and thus need to be initialized manually
-resource "null_resource" "volume_mount" {
- count = "${var.volume_name == "" ? 0 : 1}"
-
- triggers = {
- server_id = "${hcloud_server.server.id}"
- volume_id = "${hcloud_volume.master.id}"
- }
-
- connection {
- host = "${hcloud_server.server.ipv4_address}"
- }
-
- provisioner "remote-exec" {
- inline = ["mkdir -p /mnt/storage"]
- }
-
- provisioner "file" {
- content = <<EOF
-[Unit]
-Description=Mount /mnt/storage directory
-
-[Mount]
-What=${hcloud_volume.master.linux_device}
-Where=/mnt/storage
-Type=ext4
-Options=defaults
-
-[Install]
-WantedBy=multi-user.target
-EOF
- destination = "/etc/systemd/system/mnt-storage.mount"
- }
-
- provisioner "file" {
- content = <<EOF
-[Unit]
-Description=Mount /srv to persistent volume storage
-After=mnt-storage.mount
-BindsTo=mnt-storage.mount
-
-[Mount]
-What=/mnt/storage/srv
-Where=/srv
-Type=ext4
-Options=bind
-
-[Install]
-WantedBy=multi-user.target
-EOF
- destination = "/etc/systemd/system/srv.mount"
- }
-
- provisioner "file" {
- content = <<EOF
-[Unit]
-Description=Mount /home to persistent volume storage
-After=mnt-storage.mount
-BindsTo=mnt-storage.mount
-
-[Mount]
-What=/mnt/storage/home
-Where=/home
-Type=ext4
-Options=bind
-
-[Install]
-WantedBy=multi-user.target
-EOF
- destination = "/etc/systemd/system/home.mount"
- }
-
- provisioner "remote-exec" {
- inline = [
- "systemctl daemon-reload",
- "systemctl enable --now mnt-storage.mount",
- "systemctl enable --now srv.mount",
- "systemctl enable --now home.mount"
- ]
- }
-}
-
-output "ipv4" {
- value = "${hcloud_server.server.ipv4_address}"
-}
-
-output "ipv6" {
- value = "${hcloud_server.server.ipv6_address}"
-}
-
-output "fqdn" {
- value = "${cloudflare_record.record_aaaa.hostname}"
-}
-
-output "id" {
- value = "${hcloud_server.server.id}"
-}
-
-output "name" {
- value = "${hcloud_server.server.name}"
-}
diff --git a/terraform/test/.gitignore b/terraform/test/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/terraform/test/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/terraform/test/Makefile b/terraform/test/Makefile
new file mode 100644
index 0000000..7f484e0
--- /dev/null
+++ b/terraform/test/Makefile
@@ -0,0 +1,62 @@
+all: run
+
+# Create a base Debian image.
+#
+# The base image will configure apt to use apt-cacher-ng, which is
+# required to be installed on the host.
+target/base.img:
+ mkdir -p target
+ sudo vmdebootstrap \
+ --verbose \
+ --owner=$(shell whoami) \
+ --size=3G \
+ --mirror=http://127.0.0.1:3142/debian \
+ --apt-mirror=http://10.0.2.2:3142/debian \
+ --configure-apt \
+ --distribution=buster \
+ --sudo \
+ --grub \
+ --serial-console \
+ --customize=./vm/customize.sh \
+ --image $@
+
+# Create a copy-on-write snapshot of the base image.
+# VMs will use this image to enable quick testing and fast roll-back.
+target/snapshot.qcow2: target/base.img
+ mkdir -p target
+ rm -f $@
+ qemu-img create \
+ -f qcow2 \
+ -b $(notdir $<) $@
+
+# Start a VM for testing config packages
+run: target/snapshot.qcow2
+ qemu-system-x86_64 \
+ -enable-kvm \
+ -machine q35,accel=kvm,kernel-irqchip=split \
+ -m 1024 \
+ -smp 2 \
+ -device intel-iommu,intremap=on \
+ -netdev user,\
+ hostfwd=tcp::10022-:22,\
+ hostfwd=tcp::10080-:80,\
+ hostfwd=tcp::10443-:443,\
+ id=net0 \
+ -device e1000,netdev=net0 \
+ -virtfs local,\
+ path=../provision,\
+ mount_tag=host0,\
+ security_model=mapped,\
+ id=host0 \
+ -drive format=qcow2,file=$< \
+ -nographic \
+ -monitor none \
+ -serial stdio
+
+clean:
+ rm -rf target/snapshot.qcow2
+
+dist-clean:
+ rm -rf target
+
+.PHONY: all run clean dist-clean
diff --git a/terraform/test/README.md b/terraform/test/README.md
new file mode 100644
index 0000000..f418b36
--- /dev/null
+++ b/terraform/test/README.md
@@ -0,0 +1,26 @@
+# Test Utilities for Provisioning Scripts
+
+## Dependencies
+
+```bash
+apt install \
+ apt-cacher-ng \
+ build-essential \
+ qemu-kvm \
+ vmdebootstrap
+```
+## Example
+
+1. `make run`: starts a virtual machine and mounts provisioning
+ scripts. Note that running this the first time will build a base
+ virtual machine image, requiring significatnt time and
+ bandwidth. Any changes applied to the filesystem from within a
+ running VM will be contained in a copy-on-write snapshot image.
+
+2. login with `root` (no password)
+
+3. `/usr/local/share/provision/provision --force` to run provisioning
+ scripts
+
+4. back on the host, visit `https://ip.localhost:10443` to confirm the
+ service is running
diff --git a/terraform/test/vm/customize.sh b/terraform/test/vm/customize.sh
new file mode 100755
index 0000000..8d18de0
--- /dev/null
+++ b/terraform/test/vm/customize.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+abort() {
+ echo "$1" >&2
+ exit 1
+}
+
+rootdir="$1"
+
+# avoid messing with host system, in case this script is run by accident
+[[ -n $rootdir ]] || abort "root directory is not set"
+
+mkdir -p $rootdir/usr/local/share/provision
+# mount local provision script directory from host on startup
+echo 9p >> $rootdir/etc/initramfs-tools/modules
+echo 9pnet >> $rootdir/etc/initramfs-tools/modules
+echo 9pnet_virtio >> $rootdir/etc/initramfs-tools/modules
+echo "host0 /usr/local/share/provision 9p trans=virtio,version=9p2000.L 0 0" >> $rootdir/etc/fstab
+
+# boot immediately
+sed --in-place 's/GRUB_TIMEOUT=[0-9]\+/GRUB_TIMEOUT=0/g' $rootdir/etc/default/grub
diff --git a/terraform/tf b/terraform/tf
deleted file mode 100755
index 888e681..0000000
--- a/terraform/tf
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-exec terraform "$@" -var-file=<(pass infra/terraform) \ No newline at end of file