aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2019-06-10 09:03:58 -0300
committerEuAndreh <eu@euandre.org>2019-06-10 09:11:27 -0300
commit95fb2c190a2143ab84be1b18fdd2ec587e54d05e (patch)
tree52737ee757a87edc88227c3da8c378e134183689
parentChange $TLD (diff)
downloadserver-95fb2c190a2143ab84be1b18fdd2ec587e54d05e.tar.gz
server-95fb2c190a2143ab84be1b18fdd2ec587e54d05e.tar.xz
Provision DNS entries using DigitalOcean instead of DNS registrar
This way we can implement dynamic (provision-time) Floating IP, instead of a hardcoded pre-created Floating IP address. Related changes: - remove =terraform-godaddy= provider, use =digitalocean_record= instead; - create =generated-known-hosts= after provisioning instead of during =setup.sh=: use the =$(terraform output public_floating_ip)= value to make this file dynamic; - remote the =$PINNED_IP= and =$TF_VAR_floating_ip= variables; - add type and descriptions to variable declarations in Terraform recipe.
-rw-r--r--TODOs.org101
-rw-r--r--default.nix16
-rwxr-xr-xscripts/ci/provision.sh3
-rwxr-xr-xscripts/ci/setup.sh1
-rw-r--r--secrets/envrc.shbin1987 -> 2052 bytes
-rw-r--r--vps.tf78
6 files changed, 135 insertions, 64 deletions
diff --git a/TODOs.org b/TODOs.org
index ebd5d51..b68ca58 100644
--- a/TODOs.org
+++ b/TODOs.org
@@ -100,24 +100,33 @@ CLOSED: [2019-06-05 Wed 19:28]
See if it is actually working as expected.
** DONE Use Digital Ocean's Volumes for persistent extended storage
CLOSED: [2019-06-05 Wed 20:38]
-** TODO Make VPS provisioning more robust
+** DONE Make VPS provisioning more robust
+CLOSED: [2019-06-10 Mon 09:01]
*** DONE Use Ansible (or an equivalent tool) instead of custom Bash scripts
CLOSED: [2019-06-05 Wed 16:41]
They are now more fragile, ad-hoc and imperative than I would like.
Today Terraform won't run the =deploy.sh= if no infrastructure changes are required. Split infrastructure provisioning from server configuration with somethong like Ansible or =nix copy closure= and add extra command in the pipeline run.
-*** TODO Always perform a blue/green infrastructure deployment with Terraform
+*** DONE Always perform a blue/green infrastructure deployment with Terraform
+CLOSED: [2019-06-10 Mon 09:01]
Recreate a new Droplet from scratch, even if no changes happened.
This way every deployment tests the code path of creating everything from scratch again, from the DNS public IP all the way to restoring backups.
-*** TODO Destroy and recreate the volume on deployment
+*** DONE Destroy and recreate the volume on deployment
+CLOSED: [2019-06-10 Mon 09:01]
Restore from the latest backup with:
#+BEGIN_SOURCE shell
borg list --short --sort-by timestamp | tail -n 1
#+END_SOURCE
-** WAITING Configure DNS from Terraform
-*** TODO Test provisioning DNS entries with other DNS registrars
-*** TODO Have dynamic Floating IP (a.k.a. =$PINNED_IP=)
+** DONE Configure DNS from Terraform
+Handling DNS with DigitalOcean did it. Namecheap and GoDaddy API are bad, and all I had to do manually was configure a [[https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars][custom nameserver to point to DigitalOcean's nameserver]].
+CLOSED: [2019-06-09 Sun 22:52]
+*** DONE Test provisioning DNS entries with other DNS registrars
+CLOSED: [2019-06-09 Sun 22:52]
+DNS registrar API are bad in general (from what I've seen). Using DigitalOcean's DNS was more straightforward.
+*** DONE Have dynamic Floating IP (a.k.a. =$PINNED_IP=)
+CLOSED: [2019-06-09 Sun 22:52]
+Floating IP is dynamically attached to the DNS entry in DigitalOcean itself.
** TODO Create snapshots before destroying resources
This way the previous good state can be reverted if the deployment fails or the backup can't be restored.
@@ -136,14 +145,13 @@ Right now, secrets are scattered between the two repositories. By moving I can c
CLOSED: [2019-06-05 Wed 19:48]
** TODO Store updated =.tfstate= even in case of deployment failure
Right now the script fails on Terraform commands before reaching git commands. I should trap the error, store on git and only then fail.
-** TODO Fix alias in =bash-profile.sh=
+** DONE Fix alias in =bash-profile.sh=
+CLOSED: [2019-06-10 Mon 09:01]
** TODO Email verbose (Ansible) log files in case of error
builds.sr.ht only emails the link. Should it be extended to support encrypted log attachments?
** TODO Use environment variables for SSH key paths and volume mounts
** TODO Don't allow backups to fail
-* Must
-** Fully deployable from code
-Use NixOps and Terraform to fully automate all of the configuration.
+** TODO Don't hardcode =/root/= paths: use =~/= instead to allow for different users
* Services
** DONE =$tld=: Static webhosting
CLOSED: [2019-05-26 Sun 10:17]
@@ -209,14 +217,6 @@ Test the Emacs Matrix client along with the server installation.
** WAITING =search.$tld=: Searx instance
Would it be actually more private?
* Questions
-** TODO How to dynamically handle Floating IPs?
-Right now the current Floating IP defined in =.envrc= was created manually in DigitalOcean's web UI and copied from it to the environment variable.
-
-If everything was teared down, I couldn't recreate everything from source, because the Floating IP would be different.
-
-The ultimate goal would be to upsert a Floating IP address? If no Floating IP address exists, create one. If one already exists (I don't how to get a reference to it), use it.
-
-In other words, I don't want any hardcoded IPs in the recipe. The IP address has to be fixed, and the same on the DNS registrar and DigitalOcean's Floating IP.
** TODO Critiques of Docker?
What does NixOps, DisNix and Dysnomia are trying to accomplish that overlap with Docker? Use sqldiff for NixOps?
@@ -229,6 +229,17 @@ Maybe rsync the contents of the Borg repository into S3. Should I restore backup
Can it do key rotation?
** TODO Can the =setup.sh= and =provision.sh= scripts be run inside a chroot or a NixOS contained environment?
Right now they can't simply be a derivation because =setup.sh= needs access to the disk and =provision.sh= needs to access the internet.
+** DONE How to dynamically handle Floating IPs?
+CLOSED: [2019-06-10 Mon 08:59]
+Right now the current Floating IP defined in =.envrc= was created manually in DigitalOcean's web UI and copied from it to the environment variable.
+
+If everything was teared down, I couldn't recreate everything from source, because the Floating IP would be different.
+
+The ultimate goal would be to upsert a Floating IP address? If no Floating IP address exists, create one. If one already exists (I don't how to get a reference to it), use it.
+
+In other words, I don't want any hardcoded IPs in the recipe. The IP address has to be fixed, and the same on the DNS registrar and DigitalOcean's Floating IP.
+*** Solution
+I provisioned both the Floating IP and the DNS A record in the same recipe. Now everything is recreated from scratch every time.
** DONE Do I want or need Docker? Should I use it?
CLOSED: [2019-05-25 Sat 18:1980]
It was a better path than sticking with NixOps and nixcloud-webservices. It's more widespread and has more things done for it.
@@ -241,7 +252,53 @@ This was I can compartimentalize the data storage to easily backup and duplicate
* Nice to have
** =euandreh.org= as =$tld=
** Nix Terraform provisioning
-** Upgrade =terraform-godaddy= to 0.12 to support looping over CNAME records
+** WAITING Upgrade =terraform-godaddy= to 0.12 to support looping over CNAME records
+When using =terraform-godaddy= this made sense:
+#+BEGIN_SRC hcl
+locals {
+ cname_subdomains = [
+ "${var.wallabag_tld_prefix}",
+ "${var.nextcloud_tld_prefix}",
+ ]
+}
+
+resource "godaddy_domain_record" "vps_tld" {
+ domain = "${var.tld}"
+ addresses = ["${var.floating_ip}"]
+
+ dynamic "record" {
+ for_each = local.cname_subdomains
+
+ content {
+ type = "CNAME"
+ name = tag
+ data = "${var.tld}"
+ }
+ }
+}
+#+END_SRC
+However, when transitioning to DNS provisioning using DigitalOcean, there's a catch: the =digitalocean_record= resource in Terraform lives on the toplevel, not nested. I tried doing a similar thing, but [[https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each][Terraform 0.12 doesn't support =for_each= loops on =resources=]]:
+
+: During the development of Terraform 0.12 we've also laid the groundwork for supporting for_each directly inside a resource or data block as a more convenient way to create a resource instance for each element in a list or map. Unfortunately we will not be able to fully complete this feature for the Terraform 0.12 initial release, but we plan to include this in a subsequent release to make it easier to dynamically construct multiple resource instances of the same type based on elements of a given map.
+
+The equivalent code should look like:
+#+BEGIN_SRC hcl
+locals {
+ cname_subdomains = [
+ "${var.wallabag_tld_prefix}",
+ "${var.nextcloud_tld_prefix}",
+ ]
+}
+
+resource "digitalocean_record" "subdomains" {
+ for_each = local.cname_subdomains
+
+ domain = "${digitalocean_domain.vps_tld.name}"
+ type = "CNAME"
+ name = each
+ value = "${digitalocean_domain.vps_tld.name}."
+}
+#+END_SRC
** Upgrade =docker-compose.yaml= file from version 2 to version 3
** Full blue/green deployments without downtime
Only when doing a voluntary restore from backup in a newly created volume.
@@ -298,4 +355,10 @@ Instead, explicitly call =ansible-playbook= after =terraform apply= finished run
This way we test the DNS A record -> Floating IP -> Droplet IP path. We can't do that inside Terraform declaration because the =local-exec= provisioning command runs before the =digitalocean_floating_ip_assignment= is created, and we can't create a cyclic dependency between the two resources.
We could use the raw Droplet IP instead of the DNS A record, but I prefer calling it later in order to always test the full DNS resolution.
+* COMMENT
+** DONE Must
+CLOSED: [2019-06-10 Mon 08:51]
+*** DONE Fully deployable from code
+CLOSED: [2019-06-10 Mon 08:51]
+Use +NixOps+ Ansible and Terraform to fully automate all of the configuration.
* Scrath
diff --git a/default.nix b/default.nix
index 35eead8..cc64016 100644
--- a/default.nix
+++ b/default.nix
@@ -1,16 +1,4 @@
-let
- pkgs = import <nixpkgs> { };
- terraform-godaddy = pkgs.buildGoModule rec {
- name = "terraform-godaddy-${version}";
- version = "1.6.4";
- src = pkgs.fetchFromGitHub {
- owner = "n3integration";
- repo = "terraform-godaddy";
- rev = "v${version}";
- sha256 = "00blqsan74s53dk9ab4hxi1kzxi46k57dr65dmbiradfa3yz3852";
- };
- modSha256 = "0p81wqw2n8vraxk20xwg717582ijwq2k7v5j3n13y4cd5bxd8hhz";
- };
+let pkgs = import <nixpkgs> { };
in rec {
utils = import ./utils.nix {
pkgs = pkgs;
@@ -18,7 +6,6 @@ in rec {
baseName = "vps";
};
subtasks = rec {
- terraformGodaddyBuild = terraform-godaddy;
formatTerraform = utils.baseTask.overrideAttrs (baseAttrs: {
name = "${baseAttrs.name}-format-terraform";
buildInputs = baseAttrs.buildInputs ++ [ pkgs.terraform ];
@@ -52,7 +39,6 @@ in rec {
git-crypt
gettext
terraform-providers.digitalocean
- terraform-godaddy
terraform
ansible
];
diff --git a/scripts/ci/provision.sh b/scripts/ci/provision.sh
index 3b2d912..b1d23e5 100755
--- a/scripts/ci/provision.sh
+++ b/scripts/ci/provision.sh
@@ -28,7 +28,6 @@ ssh "$TLD" /home/vps/create-backup.sh || echo "FAILED TO CREATE BACKUP."
echo "Done."
echo "Initializing Terraform..."
-ln -s "$(command -v terraform-godaddy)" terraform-provider-godaddy
terraform --version
terraform init
echo "Done."
@@ -66,6 +65,8 @@ popd
echo "Done."
echo "Running the Ansible playbook..."
+
+echo "${TLD},$(terraform output public_floating_ip) ssh-rsa $(awk '{print $2}' < ./secrets/ssh/vps-box-server.pub)" > ./generated-known-hosts.txt
ansible-playbook provision.yaml
echo "Done."
diff --git a/scripts/ci/setup.sh b/scripts/ci/setup.sh
index f134c6a..d9ac70c 100755
--- a/scripts/ci/setup.sh
+++ b/scripts/ci/setup.sh
@@ -33,7 +33,6 @@ export SSH_SERVER_PUBLIC_KEY
# https://stackoverflow.com/questions/24963705/is-there-an-escape-character-for-envsubst
export DOLLAR='$'
-echo "${TLD},${PINNED_IP} ssh-rsa $(echo "${SSH_SERVER_PUBLIC_KEY}" | awk '{print $2}')" > ./generated-known-hosts.txt
envsubst < ./ssh.env.conf >> ~/.ssh/config
envsubst < ./hosts.env > ./hosts
envsubst < ./docker-compose.env.yaml > ./docker-compose.yaml
diff --git a/secrets/envrc.sh b/secrets/envrc.sh
index 82f186a..94ec7e5 100644
--- a/secrets/envrc.sh
+++ b/secrets/envrc.sh
Binary files differ
diff --git a/vps.tf b/vps.tf
index 701c98d..badc698 100644
--- a/vps.tf
+++ b/vps.tf
@@ -1,9 +1,22 @@
-variable "floating_ip" {}
+variable "do_token" {
+ type = "string"
+ description = "DigitalOcean API token."
+}
+
+variable "tld" {
+ type = "string"
+ description = "Root Top-Level Domain. Subdomains will be derived from it."
+}
-variable "do_token" {}
-variable "tld" {}
-variable "wallabag_tld" {}
-variable "nextcloud_tld" {}
+variable "wallabag_tld_prefix" {
+ type = "string"
+ description = "DNS prefix used for the Wallabag installation. Does not contain a dot at the end."
+}
+
+variable "nextcloud_tld_prefix" {
+ type = "string"
+ description = "DNS prefix used for the Nextcloud installation. Does not contain a dot at the end."
+}
provider "digitalocean" {
token = "${var.do_token}"
@@ -42,11 +55,6 @@ resource "digitalocean_droplet" "vps" {
}
}
-resource "digitalocean_floating_ip_assignment" "vps" {
- ip_address = "${var.floating_ip}"
- droplet_id = "${digitalocean_droplet.vps.id}"
-}
-
resource "digitalocean_volume" "vps_persistent_volume" {
region = "nyc3"
name = "vps-persistent-volume"
@@ -60,26 +68,40 @@ resource "digitalocean_volume_attachment" "foobar" {
droplet_id = "${digitalocean_droplet.vps.id}"
}
-locals {
- cname_subdomains = [
- "${var.wallabag_tld}",
- "${var.nextcloud_tld}",
- ]
+resource "digitalocean_floating_ip" "vps_public_ip" {
+ region = "${digitalocean_droplet.vps.region}"
}
-resource "godaddy_domain_record" "vps_tld" {
- domain = "${var.tld}"
- addresses = ["${var.floating_ip}"]
+resource "digitalocean_floating_ip_assignment" "vps_public_ip_assignment" {
+ ip_address = "${digitalocean_floating_ip.vps_public_ip.id}"
+ droplet_id = "${digitalocean_droplet.vps.id}"
+}
- record {
- type = "CNAME"
- name = "${var.tld}"
- data = "${var.wallabag_tld}"
- }
+output "public_floating_ip" {
+ value = "${digitalocean_floating_ip.vps_public_ip.ip_address}"
+}
- record {
- type = "CNAME"
- name = "${var.tld}"
- data = "${var.nextcloud_tld}"
- }
+resource "digitalocean_domain" "vps_tld" {
+ name = "${var.tld}"
+}
+
+resource "digitalocean_record" "at_sign" {
+ domain = "${digitalocean_domain.vps_tld.name}"
+ type = "A"
+ name = "@"
+ value = "${digitalocean_floating_ip.vps_public_ip.ip_address}"
+}
+
+resource "digitalocean_record" "wallabag" {
+ domain = "${digitalocean_domain.vps_tld.name}"
+ type = "CNAME"
+ name = "${var.wallabag_tld_prefix}"
+ value = "${digitalocean_domain.vps_tld.name}."
+}
+
+resource "digitalocean_record" "nextcloud" {
+ domain = "${digitalocean_domain.vps_tld.name}"
+ type = "CNAME"
+ name = "${var.nextcloud_tld_prefix}"
+ value = "${digitalocean_domain.vps_tld.name}."
}