1
0
Fork 0

initial commit

Signed-off-by: Jason Hall <imjasonh@gmail.com>
This commit is contained in:
Jason Hall 2026-05-07 20:02:59 -04:00
commit 4dc1b58f2f
20 changed files with 1398 additions and 0 deletions

19
terraform/backups.tf Normal file
View file

@ -0,0 +1,19 @@
resource "google_storage_bucket" "backups" {
name = "${var.project_id}-forgejo-backups"
location = var.region
storage_class = "STANDARD"
uniform_bucket_level_access = true
lifecycle_rule {
condition {
age = 30
}
action {
type = "Delete"
}
}
versioning {
enabled = false
}
}

9
terraform/dns.tf Normal file
View file

@ -0,0 +1,9 @@
resource "google_dns_record_set" "forgejo" {
count = var.manage_dns ? 1 : 0
name = "${var.domain}."
type = "A"
ttl = 300
managed_zone = var.dns_managed_zone
rrdatas = [google_compute_address.forgejo.address]
}

32
terraform/iam.tf Normal file
View file

@ -0,0 +1,32 @@
resource "google_service_account" "forgejo" {
account_id = "forgejo-vm"
display_name = "Forgejo VM service account"
}
resource "google_secret_manager_secret_iam_member" "forgejo_secrets" {
for_each = toset(["forgejo-secret-key", "forgejo-internal-token"])
project = var.project_id
secret_id = each.value
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.forgejo.email}"
}
resource "google_storage_bucket_iam_member" "backups_writer" {
bucket = google_storage_bucket.backups.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.forgejo.email}"
}
resource "google_iap_tunnel_instance_iam_member" "ssh_admin" {
project = var.project_id
zone = var.zone
instance = google_compute_instance.forgejo.name
role = "roles/iap.tunnelResourceAccessor"
member = "user:${var.admin_email}"
}
resource "google_project_iam_member" "ssh_os_login" {
project = var.project_id
role = "roles/compute.osLogin"
member = "user:${var.admin_email}"
}

62
terraform/main.tf Normal file
View file

@ -0,0 +1,62 @@
resource "google_compute_disk" "forgejo_data" {
name = "forgejo-data"
type = "pd-standard"
size = 20
zone = var.zone
lifecycle {
prevent_destroy = true
}
}
resource "google_compute_instance" "forgejo" {
name = "forgejo"
machine_type = "e2-micro"
zone = var.zone
tags = ["forgejo"]
boot_disk {
initialize_params {
image = "cos-cloud/cos-stable"
size = 10
type = "pd-standard"
}
}
attached_disk {
source = google_compute_disk.forgejo_data.id
device_name = "forgejo-data"
}
network_interface {
network = "default"
access_config {
nat_ip = google_compute_address.forgejo.address
}
}
metadata = {
user-data = templatefile("${path.module}/../cloud-init/user-data.yaml.tpl", {
domain = var.domain
forgejo_image = var.forgejo_image
caddy_image = var.caddy_image
gcs_backup_bucket = google_storage_bucket.backups.name
project_id = var.project_id
})
google-logging-enabled = "true"
cos-update-strategy = "update_enabled"
enable-oslogin = "TRUE"
}
service_account {
email = google_service_account.forgejo.email
scopes = ["cloud-platform"]
}
allow_stopping_for_update = true
depends_on = [
google_secret_manager_secret_iam_member.forgejo_secrets,
google_storage_bucket_iam_member.backups_writer,
]
}

32
terraform/network.tf Normal file
View file

@ -0,0 +1,32 @@
resource "google_compute_address" "forgejo" {
name = "forgejo-ip"
region = var.region
}
resource "google_compute_firewall" "https" {
name = "allow-https"
network = "default"
direction = "INGRESS"
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["forgejo"]
}
resource "google_compute_firewall" "iap_ssh" {
name = "allow-iap-ssh"
network = "default"
direction = "INGRESS"
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["35.235.240.0/20"]
target_tags = ["forgejo"]
}

14
terraform/outputs.tf Normal file
View file

@ -0,0 +1,14 @@
output "static_ip" {
value = google_compute_address.forgejo.address
description = "Point your domain's A record at this address"
}
output "ssh_command" {
value = "gcloud compute ssh forgejo --zone=${var.zone} --tunnel-through-iap"
description = "Admin SSH via IAP tunnel"
}
output "backup_bucket" {
value = google_storage_bucket.backups.name
description = "GCS bucket holding nightly backups"
}

10
terraform/secrets.tf Normal file
View file

@ -0,0 +1,10 @@
# Secrets are created out-of-band by scripts/bootstrap-secrets.sh.
# This file only declares them as data sources; the IAM bindings live in iam.tf.
data "google_secret_manager_secret" "secret_key" {
secret_id = "forgejo-secret-key"
}
data "google_secret_manager_secret" "internal_token" {
secret_id = "forgejo-internal-token"
}

50
terraform/variables.tf Normal file
View file

@ -0,0 +1,50 @@
variable "project_id" {
type = string
description = "GCP project ID"
}
variable "region" {
type = string
default = "us-central1"
description = "GCP region (use us-west1, us-central1, or us-east1 for the always-free e2-micro)"
}
variable "zone" {
type = string
default = "us-central1-a"
description = "GCP zone within region"
}
variable "domain" {
type = string
description = "Domain name for the Forgejo instance (e.g. git.example.com)"
}
variable "admin_email" {
type = string
description = "Google account that gets IAP SSH access"
}
variable "forgejo_image" {
type = string
default = "codeberg.org/forgejo/forgejo:11"
description = "Forgejo container image, pinned to a major version"
}
variable "caddy_image" {
type = string
default = "caddy:2-alpine"
description = "Caddy container image, pinned to a major version"
}
variable "manage_dns" {
type = bool
default = false
description = "If true, manage an A record in Cloud DNS. Requires dns_managed_zone."
}
variable "dns_managed_zone" {
type = string
default = ""
description = "Cloud DNS managed zone name (only used when manage_dns = true)"
}

16
terraform/versions.tf Normal file
View file

@ -0,0 +1,16 @@
terraform {
required_version = ">= 1.6"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
zone = var.zone
}