diff --git a/.sops.yaml b/.sops.yaml index 83a51dca8a5a93e1b98bb41dfe640ef049ea296b..b05213c370bbef80d711a58be13eb8721490cc5e 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -4,3 +4,6 @@ creation_rules: pgp: >- 286791FB6648539775DB31B8FCB98C2A3EC6F601, B137EE1549DFAF960DD1E2B15147025FB9F09E07 + - path_regex: terraform/.*\.tfvars + pgp: >- + 286791FB6648539775DB31B8FCB98C2A3EC6F601 diff --git a/renovate.json b/renovate.json index 58190e3170942e20c78027ebdafa8cffb6459343..dfe6c34698c040c46f36fd633b402049d5619989 100644 --- a/renovate.json +++ b/renovate.json @@ -31,6 +31,14 @@ "datasourceTemplate": "docker", "versioningTemplate": "docker" }, + { + "fileMatch": ["terraform/hedgedoc-demo/templates/docker-compose.tpl$"], + "matchStrings": [ + "# renovate:\\n\\s+image: \"?'?(?<depName>[^:\\s]+?):(?<currentValue>[^\"]*?)\"?'?\\s" + ], + "datasourceTemplate": "docker", + "versioningTemplate": "docker" + }, { "fileMatch": ["Chart\\.yaml$"], "matchStrings": [ diff --git a/terraform/hedgedoc-demo/.terraform.lock.hcl b/terraform/hedgedoc-demo/.terraform.lock.hcl new file mode 100644 index 0000000000000000000000000000000000000000..dc7101c95c6e588038a4c69133a4450bfe92603d --- /dev/null +++ b/terraform/hedgedoc-demo/.terraform.lock.hcl @@ -0,0 +1,104 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/cloudflare/cloudflare" { + version = "3.34.0" + constraints = "3.34.0" + hashes = [ + "h1:4jOI2ZUyp/EvIVE9rNvCMTqrIaG7j7XxUD1i43xD0Jg=", + "zh:03729b0fcf189e732aca54452a105d82fec839580cb5d0137317af9163e0e4dd", + "zh:121b16a779e9f2fe8c96e98f32514ee9228346fc240ce12c3fb440958b93d127", + "zh:14509f521845eedd57a8791d76958e50bea4928760a152cd853e43f2c81a329b", + "zh:273336ec2bc59ab90916706c074be27f3fe6ab42addc61a354a0ef5e10c2efa5", + "zh:54931c30f71666856c5d749698264c15196103667c87d961f3d293ff8a5c3237", + "zh:58a35eea3b9e1d2f39d7b5b1c6cf107b70eacdf5891017d6667902903db3bd94", + "zh:5ec958afe392a76a1fea262d9070df839c4d811fc6ffd613a37f8b939ab159ef", + "zh:7c24c0572aa9beee20a33cb18ac54d5088a09653e94664a9f74a9af2ae0e3554", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:9248c43f795dbe54e07c6dbc2fb8e2f20aeac8f21ec91373d52b9975f285ba7e", + "zh:b09abd506601b7c3e0b3bfde0b8b9e1aed7f52b5ad629ef2865b8321852409c7", + "zh:e00032df4cd4aad12adf3b7955fca3d1baa8bff9436c775588417da171a4e1d9", + "zh:e4a8812770914d6ce9d1f8399d702e3fb0ecc4bfd6220ba015fcb3884b243c69", + "zh:f2ad0991ef0820b3fc5bd0a500be4dceffe0b5b2ac6c9c5fd17cbb350f2f1209", + "zh:fea3a9dfb1e752dc2864028049a4af05fabf7b62eb57fff26d139a424e3476fd", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.3.0" + constraints = "2.3.0" + hashes = [ + "h1:+l9ZTDGmGdwnuYI5ftUjwP8UgoLw4f4V9xoCzal4LW0=", + "zh:1f1920b3f78c31c6b69cdfe1e016a959667c0e2d01934e1a084b94d5a02cd9d2", + "zh:550a3cdae0ddb350942624e7b2e8b31d28bc15c20511553432413b1f38f4b214", + "zh:68d1d9ccbfce2ce56b28a23b22833a5369d4c719d6d75d50e101a8a8dbe33b9b", + "zh:6ae3ad6d865a906920c313ec2f413d080efe32c230aca711fd106b4cb9022ced", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a0f413d50f54124057ae3dcd9353a797b84e91dc34bcf85c34a06f8aef1f9b12", + "zh:a2ac6d4088ceddcd73d88505e18b8226a6e008bff967b9e2d04254ef71b4ac6b", + "zh:a851010672e5218bdd4c4ea1822706c9025ef813a03da716d647dd6f8e2cffb0", + "zh:aa797561755041ef2fad99ee9ffc12b5e724e246bb019b21d7409afc2ece3232", + "zh:c6afa960a20d776f54bb1fc260cd13ead17280ebd87f05b9abcaa841ed29d289", + "zh:df0975e86b30bb89717b8c8d6d4690b21db66de06e79e6d6cfda769f3304afe6", + "zh:f0d3cc3da72135efdbe8f4cfbfb0f2f7174827887990a5545e6db1981f0d3a7c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.4.3" + hashes = [ + "h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=", + "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752", + "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b", + "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3", + "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5", + "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda", + "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6", + "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1", + "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d", + "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8", + "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93", + ] +} + +provider "registry.terraform.io/hashicorp/template" { + version = "2.2.0" + constraints = "2.2.0" + hashes = [ + "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", + "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", + "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", + "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603", + "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16", + "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776", + "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451", + "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae", + "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde", + "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d", + "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", + ] +} + +provider "registry.terraform.io/hetznercloud/hcloud" { + version = "1.36.2" + constraints = "1.36.2" + hashes = [ + "h1:VO/dl+g5NfJd436hmT+9NOMQk6oRU4Z9TSJJJrNlN0M=", + "zh:0498ef4209924b30ce7b4a232dd6aee08feab2ebbc90064db699adc10c16707e", + "zh:292e3c0c55d320cf164cdd431ee31580dd86f435aec99721597204bab5de3970", + "zh:3ce8558658baa7c4b9f1eeb92427665b4b930e5b157fbf352977778c90e11aaa", + "zh:46abd0bdeeba46b86832ed31338ad837b584f7b2152f8a9bfa6c3802f481a6da", + "zh:5804e71d411577f06abc0986c8c2e475c49042a192efce5936e4d5bdd874fc22", + "zh:7cef0782e8198346bfe7b61601e1cf8f2158280a5cf665140b72838545ca3127", + "zh:be81782af391ff4cc0859d976637aa00e6fe34061fe4f1df1f5ab5d62ef94f82", + "zh:bf2660e70edf758305085698fc9d05306b174b99559cd0f3f61c0b705ba22275", + "zh:caf727b0a378dc8c9c3594bbf176865f87aa732077820ff045eb352f5a48aeed", + "zh:cf95fc3121b358c7b7b667193ab36b8cb6140e2f6dfbf6f1b4c55b7fec1bb6ef", + "zh:d6d3119f8b971e982b6421dfa3b86314ccaeceaf047a3b6505f79e1a30f8301e", + "zh:e6f7f65dced2e88e3082c57ddcd118412595678cf3c7289bc7e12c724b3bd892", + "zh:f41f59ca511ab1a591d5abdc7f6d32d2e03a1d6087d206a741f95b7b0dd2ea17", + "zh:fbe59fbb5f272a6b206a380f6dbf49837b199960dd038afca2e89b11f72fdfda", + ] +} diff --git a/terraform/hedgedoc-demo/README.md b/terraform/hedgedoc-demo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2349a9295a39c076777666a00173040ba8b39ead --- /dev/null +++ b/terraform/hedgedoc-demo/README.md @@ -0,0 +1,55 @@ +Hedgedoc Demo Terraform Repository +=== + +This directory contains the deployment of the [Hedgedoc](https://hedgedoc.org) [Demo instance](https://demo.hedgedoc.org). + +The demo instance is hosted on [Hetzner Cloud](https://hetzner.cloud) in combination with [Cloudflare](https://cloudflare.com) as DNS, CDN and WAF provider and [Backblaze](https://backblaze.com) as Backup storage. + +The Terraform setup itself is slightly opinionated and allows reproduction of the instance by replacing the existing one, which includes a minor downtime, until the new instance has downloaded all images as started the containers. + +Features +--- + +This setup provides with some features that weren't available or limited before: + +1. Fully reproducible instance creation +2. Automated update installation +3. Automatic nightly deployment +4. Automatic nightly backups including monitoring +5. Fully self-contained setup -> All components (hedgedoc, s3, postgresql, reverse-proxy) of the demo instance run on the host + +To be done +--- + +- [x] Automated installation of Hedgedoc +- [x] Automated docker-compose revisions +- [x] Automated updates to latest nightly image +- [x] Fully restricted network access to backend host +- [x] Automated backup of Hedgedoc +- [x] Automated backup monitoring +- [ ] Automated restore of Hedgedoc +- [ ] Add safeguard preventing volume deletion +- [ ] Add safeguard preventing destruction while backup process in progress + +Usage +--- + +Be aware, that without the `.tfstate` this repository will produce another demo instance, not modify the existing one. + +1. Initialise terraform using `terraform init -upgrade` +2. Decrypt or fill/adjust the `terraform.tfvars` file to your needs. (all variables are described in the `variables.tf` file, if you want to start from scratch) +3. Run `terraform apply` and review the changes +4. Initiate deployment of the changes by typing `yes`. + +Technology reference +--- + +- [terraform](https://developer.hashicorp.com/terraform/intro) +- [rockylinux](https://docs.rockylinux.org/) +- [docker](https://docs.docker.com) +- [restic](https://restic.readthedocs.io/en/stable/) +- [hedgedoc](https://docs.hedgedoc.org) +- [traefik](https://docs.traefik.io/) +- [postgresql](https://www.postgresql.org/docs/) +- [minio](https://min.io/docs/minio/container/index.html) +- [watchtower](https://containrrr.dev/watchtower/) \ No newline at end of file diff --git a/terraform/hedgedoc-demo/dns.tf b/terraform/hedgedoc-demo/dns.tf new file mode 100644 index 0000000000000000000000000000000000000000..d0fd2d343e0aa9d2b066652955969a880fa134a2 --- /dev/null +++ b/terraform/hedgedoc-demo/dns.tf @@ -0,0 +1,17 @@ +resource "cloudflare_record" "demo-instance" { + count = 1 + zone_id = var.dns_zone_id + name = "demo" + value = module.hedgedoc.server_names[0] + type = "CNAME" + proxied = true +} + +resource "cloudflare_record" "s3-storage" { + count = 1 + zone_id = var.dns_zone_id + name = "s3" + value = module.hedgedoc.server_names[0] + type = "CNAME" + proxied = true +} \ No newline at end of file diff --git a/terraform/hedgedoc-demo/firewall.tf b/terraform/hedgedoc-demo/firewall.tf new file mode 100644 index 0000000000000000000000000000000000000000..43ecaca490870315b45aca38d234d338165cd827 --- /dev/null +++ b/terraform/hedgedoc-demo/firewall.tf @@ -0,0 +1,62 @@ +data "cloudflare_ip_ranges" "cloudflare" {} + + +resource "hcloud_firewall" "ingress-web" { + name = "ingress-web" + apply_to { + label_selector = "firewall.hetzner.si-infra.de/web=true" + } + + # ICMP is always a good idea + # + # Network reachability tests + rule { + direction = "in" + protocol = "icmp" + source_ips = [ + "0.0.0.0/0", + "::/0" + ] + } + + # Allow HTTP and HTTPS traffic in + rule { + direction = "in" + protocol = "tcp" + port = "80" + source_ips = data.cloudflare_ip_ranges.cloudflare.cidr_blocks + } + rule { + direction = "in" + protocol = "tcp" + port = "443" + source_ips = data.cloudflare_ip_ranges.cloudflare.cidr_blocks + } +} + +resource "hcloud_firewall" "ingress-ssh" { + name = "ingress-ssh" + apply_to { + label_selector = "firewall.hetzner.si-infra.de/ssh=true" + } + + # ICMP is always a good idea + # + # Network reachability tests + rule { + direction = "in" + protocol = "icmp" + source_ips = [ + "0.0.0.0/0", + "::/0" + ] + } + + # Allow SSH traffic in + rule { + direction = "in" + protocol = "tcp" + port = "22" + source_ips = var.ssh_firewall_cidr + } +} diff --git a/terraform/hedgedoc-demo/main.tf b/terraform/hedgedoc-demo/main.tf new file mode 100644 index 0000000000000000000000000000000000000000..845c89820863c49ba3173379ebedf8346023a8a6 --- /dev/null +++ b/terraform/hedgedoc-demo/main.tf @@ -0,0 +1,41 @@ +module "hedgedoc" { + source = "../modules/hcloud_instance" + instance_count = 1 + location = var.location + name = "srv" + dns_domain = var.dns_domain + dns_zone_id = var.dns_zone_id + dns_record_aaaa = false + image = "rocky-9" + user_data = templatefile("templates/cloud-init.tpl", { + docker_compose_file = templatefile("templates/docker-compose.tpl", { + hedgedoc_db_user = "${var.hedgedoc_db_user}", + hedgedoc_db_password = "${var.hedgedoc_db_password}", + + hedgedoc_session_secret = "${var.hedgedoc_session_secret}", + + hedgedoc_minio_accesskey = "${var.hedgedoc_minio_accesskey}", + hedgedoc_minio_secretkey = "${var.hedgedoc_minio_secretkey}", + hedgedoc_minio_endpoint = "${var.hedgedoc_minio_endpoint}", + hedgedoc_minio_bucketname = "${var.hedgedoc_minio_bucketname}", + + hedgedoc_github_clientid = "${var.hedgedoc_github_clientid}", + hedgedoc_github_clientsecret = "${var.hedgedoc_github_clientsecret}", + letsencrypt_email = "${var.letsencrypt_email}", + }), + restic_b2_account_id = "${var.restic_b2_account_id}", + restic_b2_account_key = "${var.restic_b2_account_key}", + restic_repo_url = "${var.restic_repo_url}", + restic_repo_password = "${var.restic_repo_password}", + restic_monitoring_url = "${var.restic_monitoring_url}", + hedgedoc_db_user = "${var.hedgedoc_db_user}", + }) + ssh_keys = data.hcloud_ssh_keys.all_keys.ssh_keys.*.name + server_type = "cx11" + labels = { + "firewall.hetzner.si-infra.de/web" = "true", + "firewall.hetzner.si-infra.de/ssh" = "true", + } + volume = true + volume_size = 45 +} diff --git a/terraform/hedgedoc-demo/provider.tf b/terraform/hedgedoc-demo/provider.tf new file mode 100644 index 0000000000000000000000000000000000000000..657bd302699a2536e7b7c210583f7f685667ff9c --- /dev/null +++ b/terraform/hedgedoc-demo/provider.tf @@ -0,0 +1,16 @@ +provider "cloudflare" { + api_token = var.cloudflare_api_token +} + +provider "hcloud" { + token = var.hcloud_token +} + +provider "template" { +} + +provider "local" { +} + +provider "random" { +} diff --git a/terraform/hedgedoc-demo/ssh.tf b/terraform/hedgedoc-demo/ssh.tf new file mode 100644 index 0000000000000000000000000000000000000000..db794f29d0e7b7cc56cb41a7ce3a1eedfa203cd5 --- /dev/null +++ b/terraform/hedgedoc-demo/ssh.tf @@ -0,0 +1,2 @@ +data "hcloud_ssh_keys" "all_keys" { +} diff --git a/terraform/hedgedoc-demo/templates/cloud-init.tpl b/terraform/hedgedoc-demo/templates/cloud-init.tpl new file mode 100644 index 0000000000000000000000000000000000000000..66c3d2de053c405bb6d491c0d439339ca83afe75 --- /dev/null +++ b/terraform/hedgedoc-demo/templates/cloud-init.tpl @@ -0,0 +1,169 @@ +#!/bin/bash + +set -e + +dnf upgrade -y + +# Enable automatic updates +dnf install -y dnf-automatic + +systemctl enable --now dnf-automatic-install.timer + +# Install docker +dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +systemctl enable --now docker + +# Setup volume +echo "/dev/sdb /srv xfs discard,nofail,defaults 0 0" >> /etc/fstab +mount /dev/sdb /srv + +mountpoint /srv + +# Create docker-compose backup +if -e /srv/docker-compose.yaml; then + mv /srv/docker-compose.yaml /srv/docker-compose.yaml.1 +fi + +CUR=1 +for i in $(seq 2 10); do + if -e "/srv/docker-compose.yaml.$${CUR}"; then + mv "/srv/docker-compose.yaml.$${CUR}" "/srv/docker-compose.yaml.$${i}" + else + break + fi + CUR="$${i}" +done + +cat > /srv/docker-compose.yaml <<EOF +${docker_compose_file} +EOF + +cd /srv + +docker compose up -d + +dnf install -y epel-release +dnf install -y restic + +mkdir -p /root/.cache/ + +cat > /etc/sysconfig/restic <<EOF +BACKUP_PATHS="/srv" +B2_ACCOUNT_ID=${restic_b2_account_id} +B2_ACCOUNT_KEY=${restic_b2_account_key} +RETENTION_DAYS=7 +RETENTION_WEEKS=2 +RETENTION_MONTHS=3 +RETENTION_YEARS=1 +RESTIC_REPOSITORY=${restic_repo_url} +RESTIC_PASSWORD=${restic_repo_password} +RESTIC_CACHE_DIR=/root/.cache/restic +EOF + +set -a +source /etc/sysconfig/restic +set +a + +if ! restic snapshots; then + restic init +fi + +# Inspired by https://fedoramagazine.org/automate-backups-with-restic-and-systemd/ +# Inspired by https://www.mavjs.org/post/automatic-backup-restic-systemd-service/ +cat > /etc/systemd/system/offsite-backup.service <<EOF +[Unit] +Description=Offsite backup with restic +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +# Low CPU priority +CPUShares=512 +EnvironmentFile=/etc/sysconfig/restic +ExecStart=/usr/local/libexec/restic-backup-hedgedoc +EOF + +# Inspired by https://github.com/mhw/restic-backup-scripts/blob/e3fe81ba25acc5ae3ccdb28807eea8b0be38ce20/postgresql-backup.sh +cat > /usr/local/libexec/restic-backup-hedgedoc <<EOF +#!/bin/bash +set -e +set -o pipefail + +# Run general backup, excluding the database +/usr/bin/restic backup /srv \ + --exclude=/srv/database/* \ + --one-file-system \ + --tag systemd.timer \ + --tag persistentvolume + +cd /srv + +# Run database backup separately by dumping the database for consistency +docker compose exec database pg_dump --create --clean --if-exists --no-owner --no-privileges -U ${hedgedoc_db_user} hedgedoc | \ + gzip --rsyncable | \ + restic backup \ + --stdin --stdin-filename postgresql/hedgedoc.sql.gz \ + --tag "systemd.timer" \ + --tag "hedgedoc-database" + +# Notify monitoring about success +curl --retry-all-errors --retry 10 '${restic_monitoring_url}' +EOF +chmod +x /usr/local/libexec/restic-backup-hedgedoc + +cat > /etc/systemd/system/offsite-backup-prune.service <<EOF +[Unit] +Description=Restic backup service (data pruning) +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +EnvironmentFile=/etc/sysconfig/restic +ExecStart=/usr/bin/restic forget --prune \ + --tag systemd.timer \ + --group-by "paths,tags" \ + --keep-daily \$RETENTION_DAYS \ + --keep-weekly \$RETENTION_WEEKS \ + --keep-monthly \$RETENTION_MONTHS \ + --keep-yearly \$RETENTION_YEARS +EOF + +cat > /etc/systemd/system/offsite-backup.timer <<EOF +[Unit] +Description=Offsite backup with restic +After=network-online.target +Wants=network-online.target + +[Timer] +OnCalendar=daily +Persistent=true +RandomizedDelaySec=600 + +[Install] +WantedBy=timers.target +EOF + +cat > /etc/systemd/system/offsite-backup-prune.timer <<EOF +[Unit] +Description=Prune data from the restic repository monthly +After=network-online.target +Wants=network-online.target + +[Timer] +OnCalendar=*-*-* 4:00:00 +Persistent=true +RandomizedDelaySec=600 + +[Install] +WantedBy=timers.target +EOF + +systemctl daemon-reload +systemctl enable --now offsite-backup.timer offsite-backup-prune.timer + +sleep 60 + +reboot diff --git a/terraform/hedgedoc-demo/templates/docker-compose.tpl b/terraform/hedgedoc-demo/templates/docker-compose.tpl new file mode 100644 index 0000000000000000000000000000000000000000..67b411ac2c14689b4dda35eed9dde52690e7da07 --- /dev/null +++ b/terraform/hedgedoc-demo/templates/docker-compose.tpl @@ -0,0 +1,165 @@ +version: '2' +services: + database: + # renovate: + image: docker.io/library/postgres:13 + mem_limit: 896mb + memswap_limit: 1024mb + read_only: true + tmpfs: + - /run/postgresql:size=512K + - /tmp:size=128K + stop_grace_period: 2m + stop_signal: SIGINT + environment: + - POSTGRES_USER=${hedgedoc_db_user} + - POSTGRES_PASSWORD=${hedgedoc_db_password} + - POSTGRES_DB=hedgedoc + volumes: + - ./database:/var/lib/postgresql/data:z + networks: + backend: + restart: always + + hedgedoc: + # renovate: + image: quay.io/hedgedoc/hedgedoc-nightly:debian + mem_limit: 1gb + memswap_limit: 1536mb + read_only: true + tmpfs: + - /tmp:size=10M + depends_on: + - database + environment: + - "CMD_DB_URL=postgres://${hedgedoc_db_user}:${hedgedoc_db_password}@database:5432/hedgedoc" + - "CMD_DOMAIN=demo.hedgedoc.org" + - "CMD_PROTOCOL_USESSL=true" + - "CMD_URL_ADDPORT=false" + - "CMD_SESSION_SECRET=${hedgedoc_session_secret}" + - "CMD_MINIO_PORT=443" + - "CMD_MINIO_ENDPOINT=${hedgedoc_minio_endpoint}" + - "CMD_MINIO_ACCESS_KEY=${hedgedoc_minio_accesskey}" + - "CMD_MINIO_SECRET_KEY=${hedgedoc_minio_secretkey}" + - "CMD_MINIO_SECURE=true" + - "CMD_IMAGE_UPLOAD_TYPE=minio" + - "CMD_S3_BUCKET=${hedgedoc_minio_bucketname}" + - "CMD_GITHUB_CLIENTID=${hedgedoc_github_clientid}" + - "CMD_GITHUB_CLIENTSECRET=${hedgedoc_github_clientsecret}" + - "CMD_USECDN=false" + - "CMD_ALLOW_FREE_URL=false" + - "CMD_EMAIL=true" + - "CMD_ALLOW_PDF_EXPORT=false" + - "CMD_CSP_REPORTURI=https://hedgedoc.report-uri.com/r/d/csp/enforce" + - "CMD_OPENID=true" + - "CMD_TOOBUSY_LAG=250" + - "CMD_CSP_ALLOW_FRAMING=false" + - "CMD_CSP_ADD_DISQUS=false" + - "CMD_CSP_ADD_GOOGLE_ANALYTICS=false" + + labels: + - "traefik.http.routers.hedgedoc_demo.rule=Host(\`demo.hedgedoc.org\`) && PathPrefix(\`/\`) && ! PathPrefix(\`/metrics\`)" + - "traefik.http.routers.hedgedoc_demo.entrypoints=websecure" + - "traefik.http.routers.hedgedoc_demo.tls=true" + - "traefik.http.routers.hedgedoc_demo.tls.certresolver=letsencrypt_http" + - "traefik.http.routers.hedgedoc_demo.middlewares=hedgedoc_demo" + - "traefik.http.routers.hedgedoc_demo.service=hedgedoc_demo" + - "traefik.http.services.hedgedoc_demo.loadbalancer.server.port=3000" + - "traefik.http.middlewares.hedgedoc_demo.headers.sslredirect=true" + - "traefik.http.middlewares.hedgedoc_demo.headers.stsSeconds=63072000" + - "traefik.http.middlewares.hedgedoc_demo.headers.browserXssFilter=true" + - "traefik.http.middlewares.hedgedoc_demo.headers.contentTypeNosniff=true" + + - "traefik.enable=true" + - "traefik.port=3000" + - "traefik.docker.network=proxy" + + + volumes: + - './privacy.md:/hedgedoc/public/docs/privacy.md:ro,z' + + networks: + backend: + proxy: + restart: always + + minio: + # renovate: + image: docker.io/minio/minio:latest + mem_limit: 256mb + memswap_limit: 512mb + read_only: true + tmpfs: + - /root/.minio:size=128K + environment: + - "MINIO_ACCESS_KEY=${hedgedoc_minio_accesskey}" + - "MINIO_SECRET_KEY=${hedgedoc_minio_secretkey}" + - "MINIO_BROWSER=off" + - "MINIO_DOMAIN=${hedgedoc_minio_endpoint}" + volumes: + - "./s3-storage:/data:z" + command: ["server", "/data"] + restart: always + labels: + - "traefik.http.routers.minio_hedgedoc.rule=Host(\`${hedgedoc_minio_endpoint}\`) && PathPrefix(\`/\`)" + - "traefik.http.routers.minio_hedgedoc.entrypoints=websecure" + - "traefik.http.routers.minio_hedgedoc.tls=true" + - "traefik.http.routers.minio_hedgedoc.tls.certresolver=letsencrypt_http" + - "traefik.http.routers.minio_hedgedoc.middlewares=minio_hedgedoc" + - "traefik.http.routers.minio_hedgedoc.service=minio_hedgedoc" + - "traefik.http.services.minio_hedgedoc.loadbalancer.server.port=9000" + - "traefik.http.middlewares.minio_hedgedoc.headers.sslredirect=true" + - "traefik.http.middlewares.minio_hedgedoc.headers.stsSeconds=63072000" + - "traefik.http.middlewares.minio_hedgedoc.headers.referrerPolicy=no-referrer" + - "traefik.http.middlewares.minio_hedgedoc.headers.contentTypeNosniff=true" + - "traefik.http.middlewares.minio_hedgedoc.headers.browserXssFilter=true" + - "traefik.enable=true" + - "traefik.port=9000" + - "traefik.docker.network=proxy" + + networks: + proxy: + + proxy: + # renovate: + image: docker.io/library/traefik:v2.9 + security_opt: + - "label:disable" + read_only: true + restart: always + + ports: + - "80:80" + - "443:443" + + volumes: + - "./acme:/etc/traefik/acme:z" + - "/var/run/docker.sock:/var/run/docker.sock" + + command: + - "--entryPoints.web.address=:80" + - "--entryPoints.websecure.address=:443" + - "--accesslog=false" + - "--providers.docker=true" + - "--certificatesResolvers.letsencrypt_http.acme.email=${letsencrypt_email}" + - "--certificatesResolvers.letsencrypt_http.acme.storage=/etc/traefik/acme/acme-v2.json" + - "--certificatesResolvers.letsencrypt_http.acme.httpChallenge.entrypoint=web" + + networks: + proxy: + + watchtower: + # renovate: + image: docker.io/containrrr/watchtower:latest + restart: always + security_opt: + - "label:disable" + environment: + - "WATCHTOWER_CLEANUP=true" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + +networks: + proxy: + name: proxy + backend: \ No newline at end of file diff --git a/terraform/hedgedoc-demo/terraform.enc.tfvars b/terraform/hedgedoc-demo/terraform.enc.tfvars new file mode 100644 index 0000000000000000000000000000000000000000..7a71db85abe6b52f4d2af3c3b8603ca3b7a292f7 --- /dev/null +++ b/terraform/hedgedoc-demo/terraform.enc.tfvars @@ -0,0 +1,21 @@ +{ + "data": "ENC[AES256_GCM,data:AvfL4Gvv1+/YJi/7Qcq42x24lK9irGWnTmo8cvazsYgMOciIyCz7Jru6LItoCMrALgXSfuT6v2ezgQJ++shTBllfOnGfvyk4nIjNeeJDwellVoardgiwBO+xxUSyHG6exbuODFw0xE0m3N1hSOyyy1wbOpG6pFGDfkZYtzD6F5MVzyGBpgg+py3/nggIMtWwrm03frSxWESggJ+bLQWsOmVqXVq9fWEAsB4tXfsaR3QaALcyAEeyVNB+111abhxuSLn/+jQHuzovQky8ORpLBGRlR7VtWj2Z6xnJ+KsSWtONJn1qdZddi24ybrJ33R8OCDX2ISQx0wV/XG5APAquAxeNsSik3nOBIqhHjWM9gTNrZohyfGc044WO9xF6zKqSKpuZ8zqnEqd/0qcWUvvFeFL4UEXtYugAQm1LbSj21ulYLEkO0KSMwHTLdeysa53KECBL9rkRh2FjXFtxva7ctDjE5uyfE4sqEB3VpdRu3Frd3nDTrKbI5qtf99Ujbv8fwCkrxKMEkYmHXGBRtBndabafknugUPTCfWk0eLLzxAJ/hSWJ/tFciALFPujqEdtM78zpBvYbmr68DuqO3zq8i2r+7tFhZKHYk6Sk/+kWB202I4RoO5d/MV1tx7ZSvFVeYxJJfRb+ZHR7RfEofLv+EE/Ku7YCb8ABzFPvnTZpqaLxc8BLnl5ahoZzvyhrngu5ZIwVjW9s/SkjynslPC0l2PH7gHpVLXH6rz3ggUU8siOW1885Khwp/Par3vG2C7wXGjXSVitzVM+ssRSI55TVr81ar2DJtriujPBR7vUJnQ1yexOOyPMwg0Q804MjXsFE0N4POB0zIOU390LscyEzLKblqUhi843yrZX+Y4n0AbmZpOUMsDgqqa9BhdrHtMGRGWd6xT+bDwc4mcysHS1VHr+oDaAU4oNh/Px4168DFsLhXexPBtneCV3AEZ1LscL0DjGxVlaNKD6hcl1JqIGwIM+6QbDmpAhjIPNFHR2PYBmYJ9Q92WYUkQw3/CubZ7amY7ItANERBPyYQ5uB1Oq/2LniL4QmLiBFZTOse6amunzwJMMkfEaKqHs+ELjPVSl3qCJjGtHVKDOybbRTX+k4Q22xMI8Vbk71ix60zkUNxnxxpwTaOPE/HhNsPWF3KuXJwXC+xlnG4SPjQAgk0qKyIGNBs0YFV8WVw5mKbSUTxNPndbU2Y1KYnU9L6vyKYBsNST9wg0ehiZPpZlmaRdHrnS7hTX6220nXuUM59ef0cBzZqrbGmVVTgxjI1KA+Cw7xJwr9iMf0JY6vb+s3V7gw9XZ5xJY20JRZsgrAzOAMBWBCtFjEt7UqqcloGQ6zDIsGhnMUdJC/Xxaf/PmxuD/RjUBk8mlaSfIAX43xKVJsRpcX96k+3GtF/lRGnu+AOprwsdqtx0mmdi1/GL8ph7/le+WZHvmyTZ00Y5VQ9mgX/BloY2dK+Yem6zCN/cxlwzTX0ZTdOZ93COs=,iv:wl3rX28j9AxiIldid/fnnbLUEW22WI+KpyH7i+GGJnM=,tag:aDwZAuHYWXhA3gpVIHG25g==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2023-03-01T00:12:49Z", + "mac": "ENC[AES256_GCM,data:0ooMWpnFkMGj2qI+ZrStExZoxRCkKLa00BqQLFIiG09Qq3JKrbixpR2a/AP7zloauB8TtSyl5DvXHSvpRvWMjvMhA61aUk6w3WQ0I3mdTZv7TLzP172lPo6DzvG35e8TAujH+fEUNfFW0zutMff/Z/9zWJJ5H9f6Z6IusrD1qkA=,iv:4YdQREC5g6chGJjFBKLW0RzZXIpsRHS3SxVEKixCgJU=,tag:AVNiRyZLwLmdesdyWwqp1g==,type:str]", + "pgp": [ + { + "created_at": "2023-03-01T00:12:49Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nwcFMA7kpg2bgzVHcAQ/+MdRgsdX6mCEGY3Tuqy4Kf2HrCpV1eNLJIgEjMJXupTVZ\nqSVhT5TRDt1kSW7D0czHJTS/Nm9nD6Y0+MFY+UMK2VqFkLPatfkyMHli5JIskpR1\nyi+UsZ5o3UQBjccM7m91kaF3me/+92h2EE2monVFPYNsABlXmNjS9yqei2Jm63ry\nXVCWCpKPLmDDOpCZk7CnJggKO2f+gdgT3JGapkSU0j8to3JoleLuszSDxmiE8S7/\nrmMNTzRzOynN6lLZkS6nQQpYBM9fYzpyxN1AexeX2qzKT18oajcu7uws9Rq5Nr7Z\naS4sPmnJfkIwG5PVBlDg/5GKt4QiMIPHXFl86QW64Du2Roe4T01G3af2FzniDrxi\npL3CgXx+HpcievUI9+fs3as77r0DTIpHZOyTmTW4PDczqdNjviTIlCse/2GiYPhJ\ngHlBkGUoY1CWpJgU4bYZjeJ9DFWSVSkYvjLUO146TyiK+AmNCykff9ipXjH5TKto\nktT80Tgd2rgzgq+536aswr7a86X7PuYXjqJ4qXYM3F+7sRJF5ZEVcuOnsWzpZ8Rh\nAi5tuwWUmCR7bfJHShp381iZbkfT6TWwa4hOx9sXNLtebgf8DOynhPMP9itCIUnE\nJxg/1EtxOp4AvyjqLv0LgRv/tZa58HEGVSKBdWyR96Vq4JQqkVNF1S4PLUHuA+TS\nUQEBoS5+5Zo8nJp/LnZ7k1rzodu7MjZnwbnbkaXTjMZKl0gi2n2NLeITghfgWyQE\nqMUiOZAp+wM676u1r8Mnj8wILofk2EejikMK45bDp7BGQw==\n=xjMK\n-----END PGP MESSAGE-----", + "fp": "286791FB6648539775DB31B8FCB98C2A3EC6F601" + } + ], + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file diff --git a/terraform/hedgedoc-demo/variables.tf b/terraform/hedgedoc-demo/variables.tf new file mode 100644 index 0000000000000000000000000000000000000000..5192f852ca98d6a27cf127d3653ffda2f08e283d --- /dev/null +++ b/terraform/hedgedoc-demo/variables.tf @@ -0,0 +1,137 @@ +variable "dns_domain" { + type = string + description = "Name of the Cloudflare domain" + nullable = false +} + +variable "dns_zone_id" { + type = string + description = "Zone ID of the Cloudflare domain" + nullable = false +} + +variable "location" { + type = string + description = "Region used to deploy the Hetzner Cloud instance. See: https://docs.hetzner.cloud/#locations" + default = "nbg1" + nullable = false +} + +variable "ssh_firewall_cidr" { + type = list(string) + description = "List of CIDRs addresses that are allow to SSH into the host" + default = ["127.0.0.1/32"] + nullable = false +} + +variable "hedgedoc_db_user" { + type = string + description = "Username used to authenticate to the PostgreSQL database. See: https://docs.hedgedoc.org/configuration/#hedgedoc-basics" + nullable = false +} + +variable "hedgedoc_db_password" { + type = string + description = "Password used to authenticate to the PostgreSQL database. See: https://docs.hedgedoc.org/configuration/#hedgedoc-basics" + sensitive = true + nullable = false +} + +variable "hedgedoc_session_secret" { + type = string + description = "Secret used for securing the Hedgedoc session token, this ensures after a restart users stay authenticated. See: https://docs.hedgedoc.org/configuration/#users-and-privileges" + sensitive = true + nullable = false +} + +variable "hedgedoc_minio_accesskey" { + type = string + description = "S3/Minio access key/ id for authentication. See: https://docs.hedgedoc.org/guides/minio-image-upload/" + sensitive = true + nullable = false +} + +variable "hedgedoc_minio_secretkey" { + type = string + description = "S3/Minio secret key for authentication. See: https://docs.hedgedoc.org/guides/minio-image-upload/" + sensitive = true + nullable = false +} + +variable "hedgedoc_minio_endpoint" { + type = string + description = "Connection Endpoint used for uploading files to S3/Minio backend. See: https://docs.hedgedoc.org/guides/minio-image-upload/" + nullable = false +} + +variable "hedgedoc_minio_bucketname" { + type = string + description = "Name of the S3/Minio Bucket. See: https://docs.hedgedoc.org/guides/minio-image-upload/" + nullable = false +} + +variable "hedgedoc_github_clientid" { + type = string + description = "GitHub Client ID used for GitHub SSO. See: https://docs.hedgedoc.org/guides/auth/github/" + sensitive = true + nullable = false +} + +variable "hedgedoc_github_clientsecret" { + type = string + description = "GitHub Client Secret used for GitHub SSO. See: https://docs.hedgedoc.org/guides/auth/github/" + sensitive = true + nullable = false +} + +variable "restic_b2_account_id" { + type = string + description = "ID to access B2 backup storage. See: https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#backblaze-b2" + sensitive = true + nullable = false +} + +variable "restic_b2_account_key" { + type = string + description = "Token to access B2 backup storage. See: https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#backblaze-b2" + sensitive = true + nullable = false +} + +variable "restic_repo_url" { + type = string + description = "URL for restric backup location, should start with b2:. See: https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#backblaze-b2" + nullable = false +} + +variable "restic_repo_password" { + type = string + description = "Password used to secure the restic backups. See: https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html" + sensitive = true + nullable = false +} + +variable "restic_monitoring_url" { + type = string + description = "URL that curl will call after finishing a backup, allows push monitoring" + default = "http://example.com" + sensitive = true + nullable = false +} + +variable "letsencrypt_email" { + description = "E-Mail address used for Let's Encrypt certificates" + nullable = false +} + +variable "cloudflare_api_token" { + description = "API token for Cloudflare authentication. See: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/" + sensitive = true + nullable = false +} + +variable "hcloud_token" { + description = "API token for Hetzner Cloud authentication. See: https://docs.hetzner.cloud/#getting-started" + sensitive = true + nullable = false +} diff --git a/terraform/hedgedoc-demo/versions.tf b/terraform/hedgedoc-demo/versions.tf new file mode 120000 index 0000000000000000000000000000000000000000..8bd0ff1400c92ad24a8bebe8b67a5fbcbae252b2 --- /dev/null +++ b/terraform/hedgedoc-demo/versions.tf @@ -0,0 +1 @@ +../versions.tf \ No newline at end of file