recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer. - Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders. - Removed plans/ and planned-updates/ (deployment-planning docs) so no client/ deployment domains appear in the public repo. - All other secret stores were already gitignored. - docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
.testenv
|
||||
@@ -0,0 +1,93 @@
|
||||
# Terraform test infrastructure
|
||||
|
||||
Provisions a DigitalOcean droplet for running Co-op Cloud recipe tests. The droplet gets Debian 13, Docker, Docker Swarm, a `proxy` overlay network, and UFW/fail2ban — everything needed to deploy recipes with `abra`.
|
||||
|
||||
A reserved IP is assigned to the droplet so DNS survives droplet destroy/recreate. DNS is managed externally (not by Terraform). The default domain is `t1cc.commoninternet.net` with wildcard DNS pointing to the reserved IP.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.0
|
||||
- A DigitalOcean API token ([create one here](https://cloud.digitalocean.com/account/api/tokens))
|
||||
- SSH key pair at `../test-ssh/test-ssh-keys/nptest` (the public key must already be uploaded to DigitalOcean as `nptest.pub`)
|
||||
|
||||
## Quick start (setup.sh)
|
||||
|
||||
The easiest way to provision is with the `setup.sh` wrapper, which handles reserved IP allocation and tfvars generation automatically:
|
||||
|
||||
```bash
|
||||
# 1. Create .testenv with your DO token
|
||||
echo 'DO_TOKEN=dop_v1_...' > terraform/.testenv
|
||||
|
||||
# 2. Run setup (allocates reserved IP on first run, reuses it after)
|
||||
./terraform/setup.sh
|
||||
```
|
||||
|
||||
This runs `terraform init` + `terraform apply`, then prints the reserved IP for DNS.
|
||||
|
||||
## Quick start (manual)
|
||||
|
||||
```bash
|
||||
cd terraform
|
||||
|
||||
# 1. Create your tfvars file
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
# Edit terraform.tfvars — set do_token and reserved_ip
|
||||
|
||||
# 2. Initialize and apply
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
After apply, deploy Traefik before deploying recipes — use `abra` for that.
|
||||
|
||||
## Connecting
|
||||
|
||||
After apply, get the SSH command:
|
||||
|
||||
```bash
|
||||
terraform output ssh_command
|
||||
```
|
||||
|
||||
## What gets provisioned
|
||||
|
||||
| Resource | Description |
|
||||
|---|---|
|
||||
| `digitalocean_ssh_key` (data) | Looks up the `nptest.pub` key already on DigitalOcean |
|
||||
| `digitalocean_droplet` | Debian 13, 2 vCPU / 8 GB, AMS3 |
|
||||
| `digitalocean_reserved_ip_assignment` | Assigns a pre-allocated reserved IP to the droplet |
|
||||
| `digitalocean_firewall` | Allows all inbound/outbound TCP, UDP, and ICMP |
|
||||
|
||||
Cloud-init installs Docker, configures UFW (allow all) and fail2ban, initializes Swarm, and creates the `proxy` overlay network.
|
||||
|
||||
## Variables
|
||||
|
||||
All variables have defaults except `do_token` and `reserved_ip`. See `terraform.tfvars.example` for the full list.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `do_token` | *(required)* | DigitalOcean API token |
|
||||
| `reserved_ip` | *(required)* | Pre-allocated reserved IP to assign to the droplet |
|
||||
| `domain` | `t1cc.commoninternet.net` | Base domain for test instances |
|
||||
| `droplet_name` | `coopcloud-test` | Droplet name |
|
||||
| `region` | `ams3` | DO region |
|
||||
| `size` | `s-2vcpu-8gb-amd` | Droplet size |
|
||||
| `image` | `debian-13-x64` | OS image |
|
||||
| `ssh_private_key_path` | `../test-ssh/test-ssh-keys/nptest` | SSH private key for provisioner connection |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
|---|---|
|
||||
| `droplet_ip` | Droplet's ephemeral IPv4 (use reserved_ip for DNS) |
|
||||
| `reserved_ip` | Static reserved IP — point DNS here |
|
||||
| `domain` | Base domain for test instances |
|
||||
| `ssh_command` | SSH command to connect to the droplet |
|
||||
|
||||
## Tearing down
|
||||
|
||||
```bash
|
||||
cd terraform
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
This deletes the droplet, reserved IP assignment, and firewall. The reserved IP itself is kept (managed outside Terraform via `setup.sh`).
|
||||
@@ -0,0 +1,37 @@
|
||||
#cloud-config
|
||||
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
packages:
|
||||
- curl
|
||||
- git
|
||||
- ufw
|
||||
- fail2ban
|
||||
|
||||
write_files:
|
||||
# Docker daemon config for Swarm (live-restore must be false for swarm mode)
|
||||
- path: /etc/docker/daemon.json
|
||||
content: |
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
}
|
||||
}
|
||||
|
||||
runcmd:
|
||||
# Install Docker
|
||||
- curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Configure firewall
|
||||
- ufw default allow incoming
|
||||
- ufw default allow outgoing
|
||||
- ufw --force enable
|
||||
|
||||
# Initialize Docker Swarm
|
||||
- docker swarm init --advertise-addr $(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
|
||||
|
||||
# Create the proxy network used by Traefik and all recipes
|
||||
- docker network create --driver overlay --attachable proxy
|
||||
@@ -0,0 +1,72 @@
|
||||
data "digitalocean_ssh_key" "test" {
|
||||
name = "nptest.pub"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "test" {
|
||||
name = var.droplet_name
|
||||
region = var.region
|
||||
size = var.size
|
||||
image = var.image
|
||||
ssh_keys = [data.digitalocean_ssh_key.test.id]
|
||||
|
||||
user_data = file("${path.module}/cloud-init.yaml")
|
||||
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = self.ipv4_address
|
||||
user = "root"
|
||||
private_key = file(var.ssh_private_key_path)
|
||||
port = 22
|
||||
}
|
||||
|
||||
# Wait for cloud-init to finish before considering the droplet ready
|
||||
provisioner "remote-exec" {
|
||||
inline = ["cloud-init status --wait"]
|
||||
}
|
||||
}
|
||||
|
||||
# --- Reserved IP (created outside Terraform, passed in as variable) ---
|
||||
|
||||
resource "digitalocean_reserved_ip_assignment" "test" {
|
||||
ip_address = var.reserved_ip
|
||||
droplet_id = digitalocean_droplet.test.id
|
||||
}
|
||||
|
||||
# --- Firewall ---
|
||||
|
||||
resource "digitalocean_firewall" "test" {
|
||||
name = "${var.droplet_name}-fw"
|
||||
droplet_ids = [digitalocean_droplet.test.id]
|
||||
|
||||
# Allow all inbound
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "1-65535"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
inbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "1-65535"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
inbound_rule {
|
||||
protocol = "icmp"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# Allow all outbound
|
||||
outbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "1-65535"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
outbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "1-65535"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
outbound_rule {
|
||||
protocol = "icmp"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
output "droplet_ip" {
|
||||
description = "Droplet's ephemeral IPv4 (use reserved_ip for DNS)"
|
||||
value = digitalocean_droplet.test.ipv4_address
|
||||
}
|
||||
|
||||
output "reserved_ip" {
|
||||
description = "Static reserved IP — point t1cc.commoninternet.net DNS here"
|
||||
value = var.reserved_ip
|
||||
}
|
||||
|
||||
output "domain" {
|
||||
description = "Base domain for test instances"
|
||||
value = var.domain
|
||||
}
|
||||
|
||||
output "ssh_command" {
|
||||
description = "SSH command to connect to the droplet"
|
||||
value = "ssh -i ${var.ssh_private_key_path} root@${var.domain}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "digitalocean" {
|
||||
token = var.do_token
|
||||
}
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Provision the terraform test droplet end-to-end.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Create terraform/.testenv with your DO token:
|
||||
# DO_TOKEN=dop_v1_...
|
||||
# 2. Run: ./terraform/setup.sh
|
||||
#
|
||||
# On first run this allocates a DigitalOcean reserved IP and saves it
|
||||
# to .testenv as RESERVED_IP. On subsequent runs it reuses that IP.
|
||||
# The reserved IP survives droplet destroy/recreate — set your DNS once.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TESTENV="$SCRIPT_DIR/.testenv"
|
||||
|
||||
# --- Source .testenv ---
|
||||
|
||||
if [[ ! -f "$TESTENV" ]]; then
|
||||
echo "ERROR: $TESTENV not found."
|
||||
echo ""
|
||||
echo "Create it with your DigitalOcean API token:"
|
||||
echo " echo 'DO_TOKEN=dop_v1_...' > $TESTENV"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$TESTENV"
|
||||
|
||||
if [[ -z "${DO_TOKEN:-}" ]]; then
|
||||
echo "ERROR: DO_TOKEN is not set in $TESTENV"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Ensure a reserved IP exists ---
|
||||
|
||||
if [[ -z "${RESERVED_IP:-}" ]]; then
|
||||
echo "No RESERVED_IP in .testenv, allocating one..."
|
||||
RESERVED_IP="$(curl -s -X POST \
|
||||
-H "Authorization: Bearer $DO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"region":"ams3"}' \
|
||||
https://api.digitalocean.com/v2/reserved_ips \
|
||||
| grep -o '"ip":"[^"]*"' | head -1 | cut -d'"' -f4)"
|
||||
|
||||
if [[ -z "$RESERVED_IP" ]]; then
|
||||
echo "ERROR: Failed to allocate reserved IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "RESERVED_IP=$RESERVED_IP" >> "$TESTENV"
|
||||
echo "Allocated and saved reserved IP: $RESERVED_IP"
|
||||
else
|
||||
echo "Using existing reserved IP: $RESERVED_IP"
|
||||
fi
|
||||
|
||||
# --- Generate terraform.tfvars ---
|
||||
|
||||
TFVARS="$SCRIPT_DIR/terraform.tfvars"
|
||||
|
||||
cat > "$TFVARS" <<EOF
|
||||
do_token = "$DO_TOKEN"
|
||||
reserved_ip = "$RESERVED_IP"
|
||||
EOF
|
||||
|
||||
echo "Wrote $TFVARS"
|
||||
|
||||
# --- Terraform init + apply ---
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if [[ ! -d .terraform ]]; then
|
||||
echo "Running terraform init..."
|
||||
terraform init
|
||||
fi
|
||||
|
||||
echo "Running terraform apply..."
|
||||
terraform apply -auto-approve
|
||||
|
||||
# --- Grab outputs ---
|
||||
|
||||
DOMAIN="$(terraform output -raw domain)"
|
||||
|
||||
echo ""
|
||||
echo "Reserved IP: $RESERVED_IP"
|
||||
echo "Domain: $DOMAIN"
|
||||
echo ""
|
||||
echo "Point DNS for $DOMAIN (A + wildcard) to: $RESERVED_IP"
|
||||
|
||||
# --- Switch repo to terraform mode ---
|
||||
|
||||
cd "$REPO_DIR"
|
||||
./switch-test-instance.sh t1cc
|
||||
|
||||
echo ""
|
||||
echo "Setup complete. You can now SSH with:"
|
||||
echo " cd test-ssh && ssh -F ssh-config $DOMAIN"
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copy to terraform.tfvars and fill in values
|
||||
#
|
||||
# Get a DO API token from: https://cloud.digitalocean.com/account/api/tokens
|
||||
# If using setup.sh, these are generated automatically from .testenv.
|
||||
|
||||
do_token = "dop_v1_..."
|
||||
reserved_ip = "x.x.x.x"
|
||||
|
||||
# Optional overrides (defaults shown):
|
||||
# domain = "t1cc.commoninternet.net"
|
||||
# droplet_name = "coopcloud-test"
|
||||
# region = "ams3"
|
||||
# size = "s-2vcpu-4gb"
|
||||
# image = "debian-13-x64"
|
||||
# ssh_private_key_path = "../test-ssh/test-ssh-keys/nptest"
|
||||
@@ -0,0 +1,46 @@
|
||||
variable "do_token" {
|
||||
description = "DigitalOcean API token"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "domain" {
|
||||
description = "Base domain for test instances (e.g. test.example.com). Each recipe gets a subdomain like <recipe>.test.example.com"
|
||||
type = string
|
||||
default = "t1cc.commoninternet.net"
|
||||
}
|
||||
|
||||
variable "droplet_name" {
|
||||
description = "Name of the DigitalOcean droplet"
|
||||
type = string
|
||||
default = "coopcloud-test"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "DigitalOcean region"
|
||||
type = string
|
||||
default = "ams3"
|
||||
}
|
||||
|
||||
variable "size" {
|
||||
description = "Droplet size (slug)"
|
||||
type = string
|
||||
default = "s-2vcpu-8gb-amd"
|
||||
}
|
||||
|
||||
variable "image" {
|
||||
description = "Droplet image (OS)"
|
||||
type = string
|
||||
default = "debian-13-x64"
|
||||
}
|
||||
|
||||
variable "ssh_private_key_path" {
|
||||
description = "Path to the SSH private key for provisioner connection"
|
||||
type = string
|
||||
default = "../test-ssh/test-ssh-keys/nptest"
|
||||
}
|
||||
|
||||
variable "reserved_ip" {
|
||||
description = "Pre-allocated DigitalOcean reserved IP to assign to the droplet"
|
||||
type = string
|
||||
}
|
||||
Reference in New Issue
Block a user