Initial infra setup: Terraform, Ansible, backup roles
This commit is contained in:
4
terraform/.gitignore
vendored
Normal file
4
terraform/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.terraform/
|
||||
terraform.tfvars
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
49
terraform/.terraform.lock.hcl
generated
Normal file
49
terraform/.terraform.lock.hcl
generated
Normal file
@@ -0,0 +1,49 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/cloudflare/cloudflare" {
|
||||
version = "4.52.7"
|
||||
constraints = "~> 4.0"
|
||||
hashes = [
|
||||
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
||||
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
||||
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
||||
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
||||
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
||||
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
||||
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
||||
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
||||
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
||||
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
||||
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
||||
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
||||
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
||||
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/digitalocean/digitalocean" {
|
||||
version = "2.81.0"
|
||||
constraints = "~> 2.0"
|
||||
hashes = [
|
||||
"h1:47cuHN8MQlnexcfUS5Toyvcg/9hk+EvHr2uNbfkmPfU=",
|
||||
"zh:0a35eca6ee12b78f4a080b02f1f77b51159d919cfddc15aea0855b41d3632013",
|
||||
"zh:0f871a3f513b789be86403c8b0568f86425fd3c4c3acb971f1f01c8ff165aafc",
|
||||
"zh:5b15aa1cc7cbfdb12f2b97b7bd55f1e77dac844d7312919b9727ba11f4a92e56",
|
||||
"zh:9be812992b0720161ec5f0518957a3b406728dbc31b437f0a336781eb6915714",
|
||||
"zh:9f2bf1509893ebcc4659408c1aff4f7337646a0369863c6d762998ec8f025d0f",
|
||||
"zh:a297f7c3d1192efb0f16ca5d9d5df4ac074f8d0f474b1c7d259884dd56998b26",
|
||||
"zh:a81e51fead5aac3e060cbe58f1bb8e4bc32e030668bf6a0511496a4a2a8c60ee",
|
||||
"zh:cc224fbe556281319cd2e525368b6c90b360e2ad1c58771eeb2dfd7ce2153ab9",
|
||||
"zh:d40bc07848e8bbce99fe66a6da12d279cb91caedd0ec61c6948b57f2e076f359",
|
||||
"zh:d470e974fb520fe2b462f15a44069915636cbbb937a80584414357b045c8b910",
|
||||
"zh:db4c728d0f26bf24c4d4c0f8000de73f79ef35a16b96c3306b6bccb91abf4b16",
|
||||
"zh:df02c98612152e31aac9d4d894134949608fa6d666da338ef76e4eec40be45c3",
|
||||
"zh:e8f47d8cc609e53a290e73064bb6efb9d5ff576e8389e53919e140fbafff9f1c",
|
||||
"zh:eb1942471dfb434ac96ed5c6e7d7360ed1e314b8c0fa9e4bbfb38c1f83b9f33e",
|
||||
"zh:fc1f47533813c1abf7888fd8f84a15876d9f7acd62b6746de2db1ca6816bf1e9",
|
||||
"zh:ff57bb5b47460e2c7cde320ad50851ca0ae3931d3ab21bfc2af0a8f1fb5cdd9c",
|
||||
]
|
||||
}
|
||||
165
terraform/main.tf
Normal file
165
terraform/main.tf
Normal file
@@ -0,0 +1,165 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
endpoints = {
|
||||
s3 = "https://nyc3.digitaloceanspaces.com"
|
||||
}
|
||||
bucket = "lunarfront-infra"
|
||||
key = "terraform/gitea.tfstate"
|
||||
region = "us-east-1" # required by S3 backend, ignored by Spaces
|
||||
skip_credentials_validation = true
|
||||
skip_metadata_api_check = true
|
||||
skip_region_validation = true
|
||||
skip_requesting_account_id = true
|
||||
force_path_style = true
|
||||
}
|
||||
}
|
||||
|
||||
provider "digitalocean" {
|
||||
token = var.do_token
|
||||
}
|
||||
|
||||
provider "cloudflare" {
|
||||
api_token = var.cloudflare_api_token
|
||||
}
|
||||
|
||||
# ─── Cloudflare zone lookup ───────────────────────────────────────────────────
|
||||
|
||||
data "cloudflare_zone" "main" {
|
||||
name = var.domain
|
||||
}
|
||||
|
||||
# ─── Droplet ──────────────────────────────────────────────────────────────────
|
||||
|
||||
data "digitalocean_ssh_key" "main" {
|
||||
name = var.ssh_key_name
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "gitea" {
|
||||
name = "gitea"
|
||||
region = var.region
|
||||
size = var.droplet_size
|
||||
image = "ubuntu-24-04-x64"
|
||||
ssh_keys = [data.digitalocean_ssh_key.main.id]
|
||||
|
||||
tags = ["infra", "gitea"]
|
||||
}
|
||||
|
||||
# ─── Firewall ─────────────────────────────────────────────────────────────────
|
||||
|
||||
# Cloudflare IPv4 ranges — http://www.cloudflare.com/ips-v4
|
||||
locals {
|
||||
cloudflare_ipv4 = [
|
||||
"173.245.48.0/20",
|
||||
"103.21.244.0/22",
|
||||
"103.22.200.0/22",
|
||||
"103.31.4.0/22",
|
||||
"141.101.64.0/18",
|
||||
"108.162.192.0/18",
|
||||
"190.93.240.0/20",
|
||||
"188.114.96.0/20",
|
||||
"197.234.240.0/22",
|
||||
"198.41.128.0/17",
|
||||
"162.158.0.0/15",
|
||||
"104.16.0.0/13",
|
||||
"104.24.0.0/14",
|
||||
"172.64.0.0/13",
|
||||
"131.0.72.0/22",
|
||||
]
|
||||
cloudflare_ipv6 = [
|
||||
"2400:cb00::/32",
|
||||
"2606:4700::/32",
|
||||
"2803:f800::/32",
|
||||
"2405:b500::/32",
|
||||
"2405:8100::/32",
|
||||
"2a06:98c0::/29",
|
||||
"2c0f:f248::/32",
|
||||
]
|
||||
}
|
||||
|
||||
resource "digitalocean_firewall" "gitea" {
|
||||
name = "gitea-firewall"
|
||||
droplet_ids = [digitalocean_droplet.gitea.id]
|
||||
|
||||
# SSH — your IP only
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "22"
|
||||
source_addresses = ["${var.admin_ip}/32"]
|
||||
}
|
||||
|
||||
# HTTP/HTTPS — Cloudflare IPs only
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "80"
|
||||
source_addresses = concat(local.cloudflare_ipv4, local.cloudflare_ipv6)
|
||||
}
|
||||
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "443"
|
||||
source_addresses = concat(local.cloudflare_ipv4, local.cloudflare_ipv6)
|
||||
}
|
||||
|
||||
# Gitea SSH for git push/pull — your IP only
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "2222"
|
||||
source_addresses = ["${var.admin_ip}/32"]
|
||||
}
|
||||
|
||||
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"]
|
||||
}
|
||||
}
|
||||
|
||||
# ─── DNS records ──────────────────────────────────────────────────────────────
|
||||
|
||||
resource "cloudflare_record" "gitea" {
|
||||
zone_id = data.cloudflare_zone.main.id
|
||||
name = "git"
|
||||
type = "A"
|
||||
value = digitalocean_droplet.gitea.ipv4_address
|
||||
proxied = true
|
||||
ttl = 1
|
||||
}
|
||||
|
||||
resource "cloudflare_record" "vaultwarden" {
|
||||
zone_id = data.cloudflare_zone.main.id
|
||||
name = "vault"
|
||||
type = "A"
|
||||
value = digitalocean_droplet.gitea.ipv4_address
|
||||
proxied = true
|
||||
ttl = 1
|
||||
}
|
||||
|
||||
# ─── Project assignment ───────────────────────────────────────────────────────
|
||||
|
||||
data "digitalocean_project" "infra" {
|
||||
name = var.do_project_name
|
||||
}
|
||||
|
||||
resource "digitalocean_project_resources" "infra" {
|
||||
project = data.digitalocean_project.infra.id
|
||||
resources = [
|
||||
digitalocean_droplet.gitea.urn,
|
||||
]
|
||||
}
|
||||
4
terraform/outputs.tf
Normal file
4
terraform/outputs.tf
Normal file
@@ -0,0 +1,4 @@
|
||||
output "gitea_ip" {
|
||||
description = "Public IP of the Gitea droplet"
|
||||
value = digitalocean_droplet.gitea.ipv4_address
|
||||
}
|
||||
6
terraform/terraform.tfvars.example
Normal file
6
terraform/terraform.tfvars.example
Normal file
@@ -0,0 +1,6 @@
|
||||
do_token = "your-digitalocean-api-token"
|
||||
ssh_key_name = "your-key-name-in-do"
|
||||
region = "nyc3"
|
||||
droplet_size = "s-1vcpu-2gb"
|
||||
cloudflare_api_token = "your-cloudflare-api-token"
|
||||
domain = "example.com"
|
||||
44
terraform/variables.tf
Normal file
44
terraform/variables.tf
Normal file
@@ -0,0 +1,44 @@
|
||||
variable "do_token" {
|
||||
description = "DigitalOcean API token"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ssh_key_name" {
|
||||
description = "Name of the SSH key uploaded to DigitalOcean"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "DigitalOcean region"
|
||||
type = string
|
||||
default = "nyc3"
|
||||
}
|
||||
|
||||
variable "droplet_size" {
|
||||
description = "Droplet size slug"
|
||||
type = string
|
||||
default = "s-1vcpu-2gb"
|
||||
}
|
||||
|
||||
variable "cloudflare_api_token" {
|
||||
description = "Cloudflare API token (needs Zone:DNS:Edit permission)"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "domain" {
|
||||
description = "Root domain managed in Cloudflare (e.g. example.com)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "admin_ip" {
|
||||
description = "Your public IP for SSH and git access (without /32)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "do_project_name" {
|
||||
description = "DigitalOcean project name to assign resources to"
|
||||
type = string
|
||||
default = "lunarfront-infra"
|
||||
}
|
||||
Reference in New Issue
Block a user