Compare commits

...

7 Commits
v0.2.0 ... main

8 changed files with 179 additions and 76 deletions

View File

@ -9,10 +9,10 @@ jobs:
name: Terraform
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hashicorp/setup-terraform@v1
- uses: actions/checkout@v3
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 0.13.0
terraform_version: 1.3.x
- run: terraform fmt -check -recursive
- run: terraform init
- run: terraform validate

64
.terraform.lock.hcl generated Normal file
View File

@ -0,0 +1,64 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/digitalocean/digitalocean" {
version = "2.25.2"
constraints = ">= 1.22.0"
hashes = [
"h1:z33HsVmHl5PH8NI1kY07+1Gc8+gLxGDa6xsKpwdUbJc=",
"zh:0accb40afb05425f20ff93426c69fa9585fd269f5a0caff9e03173ca3a0f66f0",
"zh:0e389b5ebfce42a9a1c78b576acffa6d4f1cfa421810537e6e096a254ff3fec8",
"zh:12441f028af172a823b452bb017721d7bf2f6f14e343ac90f361c7bb73ff0874",
"zh:18e04874d833d014617ee94971b8ef4638931a3ee7c572f86ee816b74911bcb5",
"zh:4e728375e24fdc37e791b3f234c991da342dbad8e1bd878531dd45ab6710c4fe",
"zh:4f76bea793d71ae85c72275bd1a5d28ce72afbb41e6cf51cc74d19a470b2c4dc",
"zh:588fd686e257b9d989427106e16b7d35a805cf6c1f532dca8fd61c09f19cc95a",
"zh:5b433b49869a45d96b95e921dd3cc713471dfa78157fe6f89f09d41c689256c2",
"zh:5de660180ab655b64e579564ec5f60f63d7c6633f47dfe4c8ac5a6718d19b5ea",
"zh:6395f4d9995f525469d88825f56c88f46b3466db26a3962a645c9a2e65e60dad",
"zh:7b04b9ca110f3876000616f9f3f046a974a20db93583786f26dccf10ed9372cf",
"zh:81b02a7247a0142075315cdbccd41138c01ed3327036c6b3b417859b06fdac0d",
"zh:99e4cf8818eed4e0516a939658ae89a8eefeb4dd9d49303b47b28dc844f983ac",
"zh:a85ddbfc6db67508a64c95edd333132efbc40ab7b4d6266023750dc7756f6bec",
"zh:b7e9ee035192e2f4d8db11d33e0dabd1969135901bae52d96001fce5f2a4dce8",
"zh:ec5d133c03319ec103c80d954be31dd673f44e9c93ec9ed951576e110549b59f",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.1"
hashes = [
"h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=",
"zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840",
"zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb",
"zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5",
"zh:74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238",
"zh:a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc",
"zh:c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970",
"zh:e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2",
"zh:e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5",
"zh:fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f",
"zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694",
]
}
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",
]
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Dan Buch
Copyright (c) 2023 Dan Buch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,4 @@
# `tf_digitalocean_spoke`
# `terraform-digitalocean-spoke`
<!-- WARNING: this file is generated -->
This is a terraform module that provisions a
@ -16,7 +16,7 @@ like this:
```hcl
module "digitalocean_spoke" {
source = "github.com/meatballhat/tf_digitalocean_spoke"
source = "hamfist/spoke/digitalocean"
server_name = "spoke.example.org"
base_url = "https://spoke.example.org"
@ -52,42 +52,58 @@ module "digitalocean_spoke" {
| Name | Version |
|------|---------|
| terraform | >= 0.13 |
| digitalocean | >= 1.22 |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13 |
| <a name="requirement_digitalocean"></a> [digitalocean](#requirement\_digitalocean) | >= 1.22 |
## Providers
| Name | Version |
|------|---------|
| digitalocean | >= 1.22 |
| null | n/a |
| random | n/a |
| <a name="provider_digitalocean"></a> [digitalocean](#provider\_digitalocean) | 2.25.2 |
| <a name="provider_null"></a> [null](#provider\_null) | 3.2.1 |
| <a name="provider_random"></a> [random](#provider\_random) | 3.4.3 |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [digitalocean_droplet.app](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/droplet) | resource |
| [digitalocean_firewall.app](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/firewall) | resource |
| [digitalocean_floating_ip.app](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/floating_ip) | resource |
| [digitalocean_ssh_key.app](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/ssh_key) | resource |
| [null_resource.app_provision](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [random_string.pg_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [random_string.session_secret](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| base\_url | Fully qualified https URL of the app | `string` | n/a | yes |
| cert\_certificate | Certificate with leaf and intermediates to pass to nginx | `string` | n/a | yes |
| cert\_private\_key | Certificate key to pass to nginx | `string` | n/a | yes |
| droplet\_image | Image to use when provisioning app droplet | `string` | `"ubuntu-20-04-x64"` | no |
| droplet\_size | Size value passed when provisioning app droplet | `string` | `"s-1vcpu-1gb"` | no |
| env | Arbitrary *additional* environment variables passed at build time and run time | `map(string)` | `{}` | no |
| node\_env | Value defined at build time and run time as NODE\_ENV | `string` | `"production"` | no |
| node\_options | Value defined at build time and run time as NODE\_OPTIONS | `string` | `"--max_old_space_size=8192"` | no |
| port | TCP port used to communicate between droplet and nginx | `string` | `"3000"` | no |
| region | Region in which all resources will be provisioned | `string` | `"nyc1"` | no |
| resource\_prefix | Prefix prepended to resource names | `string` | `"spoke-"` | no |
| server\_name | Server name used in nginx config | `string` | n/a | yes |
| spoke\_version | Git ref of MoveOnOrg/Spoke to deploy | `string` | `"v8.0"` | no |
| ssh\_keys | List of ssh public keys to pass to droplet provisioning | `list(string)` | n/a | yes |
| <a name="input_base_url"></a> [base\_url](#input\_base\_url) | Fully qualified https URL of the app | `string` | n/a | yes |
| <a name="input_cert_certificate"></a> [cert\_certificate](#input\_cert\_certificate) | Certificate with leaf and intermediates to pass to nginx | `string` | n/a | yes |
| <a name="input_cert_private_key"></a> [cert\_private\_key](#input\_cert\_private\_key) | Certificate key to pass to nginx | `string` | n/a | yes |
| <a name="input_droplet_image"></a> [droplet\_image](#input\_droplet\_image) | Image to use when provisioning app droplet | `string` | `"ubuntu-20-04-x64"` | no |
| <a name="input_droplet_size"></a> [droplet\_size](#input\_droplet\_size) | Size value passed when provisioning app droplet | `string` | `"s-1vcpu-1gb"` | no |
| <a name="input_env"></a> [env](#input\_env) | Arbitrary *additional* environment variables passed at build time and run time | `map(string)` | `{}` | no |
| <a name="input_nginx_site_override_conf"></a> [nginx\_site\_override\_conf](#input\_nginx\_site\_override\_conf) | Complete nginx site configuration override | `string` | `""` | no |
| <a name="input_node_env"></a> [node\_env](#input\_node\_env) | Value defined at build time and run time as NODE\_ENV | `string` | `"production"` | no |
| <a name="input_node_options"></a> [node\_options](#input\_node\_options) | Value defined at build time and run time as NODE\_OPTIONS | `string` | `"--max_old_space_size=8192"` | no |
| <a name="input_port"></a> [port](#input\_port) | TCP port used to communicate between droplet and nginx | `string` | `"3000"` | no |
| <a name="input_region"></a> [region](#input\_region) | Region in which all resources will be provisioned | `string` | `"nyc1"` | no |
| <a name="input_resource_prefix"></a> [resource\_prefix](#input\_resource\_prefix) | Prefix prepended to resource names | `string` | `"spoke-"` | no |
| <a name="input_server_name"></a> [server\_name](#input\_server\_name) | Server name used in nginx config | `string` | n/a | yes |
| <a name="input_spoke_version"></a> [spoke\_version](#input\_spoke\_version) | Git ref of MoveOnOrg/Spoke to deploy | `string` | `"v8.0"` | no |
| <a name="input_ssh_keys"></a> [ssh\_keys](#input\_ssh\_keys) | List of ssh public keys to pass to droplet provisioning | `list(string)` | n/a | yes |
## Outputs
| Name | Description |
|------|-------------|
| droplet\_ipv4\_address | ipv4 address of the droplet |
| droplet\_urn | urn of the droplet suitable for adding to project resources |
| floating\_ip\_address | floating IP address assigned to the droplet suitable for creating a DNS A record |
| floating\_ip\_urn | urn of the floating IP address assigned to the droplet suitable for adding to project resources |
| <a name="output_droplet_ipv4_address"></a> [droplet\_ipv4\_address](#output\_droplet\_ipv4\_address) | ipv4 address of the droplet |
| <a name="output_droplet_urn"></a> [droplet\_urn](#output\_droplet\_urn) | urn of the droplet suitable for adding to project resources |
| <a name="output_floating_ip_address"></a> [floating\_ip\_address](#output\_floating\_ip\_address) | floating IP address assigned to the droplet suitable for creating a DNS A record |
| <a name="output_floating_ip_urn"></a> [floating\_ip\_urn](#output\_floating\_ip\_urn) | urn of the floating IP address assigned to the droplet suitable for adding to project resources |

92
main.tf
View File

@ -1,5 +1,5 @@
/**
* # `tf_digitalocean_spoke`
* # `terraform-digitalocean-spoke`
* <!-- WARNING: this file is generated -->
*
* This is a terraform module that provisions a
@ -17,7 +17,7 @@
*
* ```hcl
* module "digitalocean_spoke" {
* source = "github.com/meatballhat/tf_digitalocean_spoke"
* source = "hamfist/spoke/digitalocean"
*
* server_name = "spoke.example.org"
* base_url = "https://spoke.example.org"
@ -65,92 +65,97 @@ terraform {
}
}
variable server_name {
variable "server_name" {
description = "Server name used in nginx config"
type = string
}
variable base_url {
variable "base_url" {
description = "Fully qualified https URL of the app"
type = string
}
variable resource_prefix {
variable "resource_prefix" {
description = "Prefix prepended to resource names"
default = "spoke-"
type = string
}
variable node_options {
variable "nginx_site_override_conf" {
description = "Complete nginx site configuration override"
default = ""
}
variable "node_options" {
description = "Value defined at build time and run time as NODE_OPTIONS"
default = "--max_old_space_size=8192"
type = string
}
variable node_env {
variable "node_env" {
description = "Value defined at build time and run time as NODE_ENV"
default = "production"
type = string
}
variable port {
variable "port" {
description = "TCP port used to communicate between droplet and nginx"
default = "3000"
type = string
}
variable droplet_image {
variable "droplet_image" {
description = "Image to use when provisioning app droplet"
default = "ubuntu-20-04-x64"
type = string
}
variable droplet_size {
variable "droplet_size" {
description = "Size value passed when provisioning app droplet"
default = "s-1vcpu-1gb"
type = string
}
variable region {
variable "region" {
description = "Region in which all resources will be provisioned"
default = "nyc1"
type = string
}
variable spoke_version {
variable "spoke_version" {
description = "Git ref of MoveOnOrg/Spoke to deploy"
default = "v8.0"
default = "12.3.0"
type = string
}
variable ssh_keys {
variable "ssh_keys" {
type = list(string)
description = "List of ssh public keys to pass to droplet provisioning"
}
variable cert_private_key {
variable "cert_private_key" {
description = "Certificate key to pass to nginx"
type = string
}
variable cert_certificate {
variable "cert_certificate" {
description = "Certificate with leaf and intermediates to pass to nginx"
type = string
}
variable env {
variable "env" {
description = "Arbitrary *additional* environment variables passed at build time and run time"
default = {}
type = map(string)
}
resource digitalocean_ssh_key app {
resource "digitalocean_ssh_key" "app" {
count = length(var.ssh_keys)
name = "${var.resource_prefix}app-${count.index}"
public_key = element(var.ssh_keys, count.index)
}
resource digitalocean_droplet app {
resource "digitalocean_droplet" "app" {
image = var.droplet_image
name = "${var.resource_prefix}app"
region = var.region
@ -159,17 +164,17 @@ resource digitalocean_droplet app {
ssh_keys = digitalocean_ssh_key.app[*].id
}
resource digitalocean_floating_ip app {
resource "digitalocean_floating_ip" "app" {
droplet_id = digitalocean_droplet.app.id
region = digitalocean_droplet.app.region
}
resource digitalocean_firewall app {
resource "digitalocean_firewall" "app" {
name = "pghdsa-spoke-app"
droplet_ids = [digitalocean_droplet.app.id]
dynamic inbound_rule {
dynamic "inbound_rule" {
for_each = ["22", "80", "443"]
content {
protocol = "tcp"
@ -183,7 +188,7 @@ resource digitalocean_firewall app {
source_addresses = ["0.0.0.0/0", "::/0"]
}
dynamic outbound_rule {
dynamic "outbound_rule" {
for_each = ["tcp", "udp"]
content {
protocol = outbound_rule.value
@ -198,12 +203,12 @@ resource digitalocean_firewall app {
}
}
resource random_string session_secret {
resource "random_string" "session_secret" {
length = 199
special = false
}
resource random_string pg_password {
resource "random_string" "pg_password" {
length = 31
}
@ -231,7 +236,7 @@ locals {
}, var.env)
}
resource null_resource app_provision {
resource "null_resource" "app_provision" {
triggers = {
droplet_id = digitalocean_droplet.app.id
provision_script_sha1 = filesha1("${path.module}/spoke-app-provision")
@ -251,17 +256,17 @@ resource null_resource app_provision {
host = digitalocean_droplet.app.ipv4_address
}
provisioner file {
provisioner "file" {
source = "${path.module}/spoke-app-provision"
destination = "/tmp/spoke-app-provision"
}
provisioner file {
provisioner "file" {
source = "${path.module}/spoke-app-run"
destination = "/tmp/spoke-app-run"
}
provisioner file {
provisioner "file" {
content = templatefile("${path.module}/nginx-sites-default.conf.tpl", {
server_name = var.server_name,
port = var.port,
@ -269,17 +274,22 @@ resource null_resource app_provision {
destination = "/tmp/nginx-sites-default.conf"
}
provisioner file {
provisioner "file" {
content = var.nginx_site_override_conf
destination = "/tmp/nginx-sites-default-override.conf"
}
provisioner "file" {
content = var.cert_certificate
destination = "/tmp/spoke.crt"
}
provisioner file {
provisioner "file" {
content = var.cert_private_key
destination = "/tmp/spoke.key"
}
provisioner file {
provisioner "file" {
content = <<-ENVTMPL
%{for key, value in local.env_map~}
${key}='${value}'
@ -289,36 +299,32 @@ ENVTMPL
destination = "/tmp/app.env"
}
provisioner file {
provisioner "file" {
source = "${path.module}/spoke.service"
destination = "/tmp/spoke.service"
}
provisioner remote-exec {
inline = [
"bash /tmp/spoke-app-provision system0",
"sudo -H -u spoke bash /tmp/spoke-app-provision spoke0",
"bash /tmp/spoke-app-provision system1",
]
provisioner "remote-exec" {
script = "${path.module}/spoke-app-provision-wrapper"
}
}
output droplet_urn {
output "droplet_urn" {
description = "urn of the droplet suitable for adding to project resources"
value = digitalocean_droplet.app.urn
}
output droplet_ipv4_address {
output "droplet_ipv4_address" {
description = "ipv4 address of the droplet"
value = digitalocean_droplet.app.ipv4_address
}
output floating_ip_address {
output "floating_ip_address" {
description = "floating IP address assigned to the droplet suitable for creating a DNS A record"
value = digitalocean_floating_ip.app.ip_address
}
output floating_ip_urn {
output "floating_ip_urn" {
description = "urn of the floating IP address assigned to the droplet suitable for adding to project resources"
value = digitalocean_floating_ip.app.urn
}

View File

@ -40,15 +40,15 @@ _run_system0() {
redis
dpkg --get-selections || true |
if ! grep -qE '^postgresql-client-11.+install$'; then
if ! grep -qE '^postgresql-client-13.+install$'; then
echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" |
sudo tee /etc/apt/sources.list.d/pgdg.list &>/dev/null
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update -y
sudo apt-get install -y postgresql-11 postgresql-client-11
sudo apt-get install -y postgresql-13 postgresql-client-13
fi
pg_ctlcluster 11 main start
pg_ctlcluster 13 main start
sudo -H -u postgres bash <<PGSETUP
set -o allexport
# shellcheck source=/dev/null
@ -80,6 +80,9 @@ PGSETUP
chmod 0600 /home/spoke/spoke.crt /home/spoke/spoke.key
chown spoke /home/spoke/spoke.crt /home/spoke/spoke.key
cp -v /tmp/nginx-sites-default.conf /etc/nginx/sites-available/default
if [[ -s /tmp/nginx-sites-default-override.conf ]]; then
cp -v /tmp/nginx-sites-default-override.conf /etc/nginx/sites-available/default
fi
ln -svf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
sha1sum /etc/nginx/sites-available/default
systemctl restart nginx

11
spoke-app-provision-wrapper Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -o errexit
set -o pipefail
main() {
bash /tmp/spoke-app-provision system0
sudo -H -u spoke bash /tmp/spoke-app-provision spoke0
bash /tmp/spoke-app-provision system1
}
main "${@}"

View File

@ -7,6 +7,9 @@ main() {
# shellcheck source=/dev/null
source ~/.nvm/nvm.sh
nvm use
if [[ -f ./build/server/server/index.js ]]; then
exec node ./build/server/server
fi
exec npm start
}