Files
tasknote/terraform/main.tf
T
rmcampos ebe74fdfcb
CI / Build Backend (pull_request) Successful in 46s
CI / Build Frontend (pull_request) Successful in 3m40s
chore: drop old references and remove lingering files
2026-07-22 22:14:26 +02:00

543 lines
14 KiB
Terraform

terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0.0"
}
}
backend "s3" {
bucket = "tasknote"
key = "kubernetes/terraform.tfstate"
region = "auto"
endpoints = { s3 = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com" }
skip_credentials_validation = true
skip_region_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
skip_s3_checksum = true
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
variable "db_user" {
type = string
sensitive = true
}
variable "db_password" {
type = string
sensitive = true
}
variable "db_name" {
type = string
sensitive = true
}
variable "security_key" {
type = string
sensitive = true
}
variable "mailgun_apikey" {
type = string
sensitive = true
}
variable "r2_access_key" {
type = string
sensitive = true
}
variable "r2_secret_key" {
type = string
sensitive = true
}
variable "r2_bucket_name" {
type = string
default = "tasknote-backups"
}
variable "r2_endpoint" {
type = string
default = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com"
}
variable "cors_allowed_origins" {
type = string
default = "https://tasknote.darkroasted.vps-kinghost.net,https://tasknote.cc,https://www.tasknote.cc"
}
variable "root_log_level" {
type = string
default = "INFO"
}
variable "backend_image" {
type = string
default = "rmcampos/tasknote-api:latest"
}
variable "frontend_image" {
type = string
default = "rmcampos/tasknote-app:latest"
}
resource "kubernetes_namespace_v1" "tasknote" {
metadata {
name = "tasknote"
}
}
resource "kubernetes_secret_v1" "tasknote_secrets" {
metadata {
name = "tasknote-secrets"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
data = {
postgres_user = var.db_user
postgres_password = var.db_password
postgres_db = var.db_name
security_key = var.security_key
mailgun_apikey = var.mailgun_apikey
}
}
resource "kubernetes_persistent_volume_claim_v1" "tasknote_db_data" {
metadata {
name = "postgres-data-pvc"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = "1Gi"
}
}
}
}
resource "kubernetes_deployment_v1" "tasknote_db" {
metadata {
name = "tasknote-db"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
replicas = 1
selector { match_labels = { app = "tasknote-db" } }
template {
metadata { labels = { app = "tasknote-db" } }
spec {
container {
image = "postgres:15.8-bookworm"
name = "postgres"
volume_mount {
name = "postgres-storage"
mount_path = "/var/lib/postgresql/data"
}
env {
name = "POSTGRES_USER"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_user"
}
}
}
env {
name = "POSTGRES_PASSWORD"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_password"
}
}
}
env {
name = "POSTGRES_DB"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_db"
}
}
}
port { container_port = 5432 }
}
volume {
name = "postgres-storage"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim_v1.tasknote_db_data.metadata[0].name
}
}
}
}
}
}
resource "kubernetes_service_v1" "tasknote_db_svc" {
metadata {
name = "tasknote-db-svc"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
selector = { app = "tasknote-db" }
port { port = 5432 }
type = "ClusterIP"
}
}
resource "kubernetes_deployment_v1" "tasknote_backend" {
metadata {
name = "tasknote-backend"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
replicas = 1
selector { match_labels = { app = "tasknote-backend" } }
template {
metadata { labels = { app = "tasknote-backend" } }
spec {
container {
image = var.backend_image
name = "backend"
env {
name = "POSTGRES_DB"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_db"
}
}
}
env {
name = "POSTGRES_HOST"
value = "tasknote-db-svc"
}
env {
name = "POSTGRES_USER"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_user"
}
}
}
env {
name = "POSTGRES_PASSWORD"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_password"
}
}
}
env {
name = "POSTGRES_PORT"
value = "5432"
}
env {
name = "CORS_ALLOWED_ORIGINS"
value = var.cors_allowed_origins
}
env {
name = "SERVER_SERVLET_CONTEXT_PATH"
value = "/"
}
env {
name = "ROOT_LOG_LEVEL"
value = var.root_log_level
}
env {
name = "SECURITY_KEY"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "security_key"
}
}
}
env {
name = "TARGET_ENV"
value = "production"
}
env {
name = "MAILGUN_APIKEY"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "mailgun_apikey"
}
}
}
resources {
limits = { memory = "256Mi", cpu = "500m" }
requests = { memory = "256Mi", cpu = "250m" }
}
}
}
}
}
}
resource "kubernetes_service_v1" "tasknote_backend_svc" {
metadata {
name = "tasknote-backend-svc"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
selector = { app = "tasknote-backend" }
port {
port = 8585
target_port = 8585
}
}
}
resource "kubernetes_deployment_v1" "tasknote_frontend" {
metadata {
name = "tasknote-frontend"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
replicas = 1
selector { match_labels = { app = "tasknote-app" } }
template {
metadata { labels = { app = "tasknote-app" } }
spec {
container {
image = var.frontend_image
name = "frontend"
port { container_port = 5000 }
env {
name = "VITE_BACKEND_SERVER"
value = "https://tasknoteapi.darkroasted.vps-kinghost.net"
}
}
}
}
}
}
resource "kubernetes_service_v1" "tasknote_frontend_svc" {
metadata {
name = "tasknote-frontend-svc"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
selector = { app = "tasknote-app" }
port {
port = 5000
target_port = 5000
}
type = "ClusterIP"
}
}
# Unified Ingress for App and API
resource "kubernetes_ingress_v1" "tasknote_ingress" {
metadata {
name = "tasknote-ingress"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
annotations = {
"kubernetes.io/ingress.class" = "traefik"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
}
}
spec {
tls {
hosts = [
"tasknote.darkroasted.vps-kinghost.net",
"tasknoteapi.darkroasted.vps-kinghost.net",
"tasknote.cc",
"www.tasknote.cc"
]
secret_name = "tasknote-tls-certs"
}
rule {
host = "tasknote.darkroasted.vps-kinghost.net"
http {
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service_v1.tasknote_frontend_svc.metadata[0].name
port { number = 5000 }
}
}
}
}
}
rule {
host = "tasknoteapi.darkroasted.vps-kinghost.net"
http {
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service_v1.tasknote_backend_svc.metadata[0].name
port { number = 8585 }
}
}
}
}
}
rule {
host = "tasknote.cc"
http {
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service_v1.tasknote_frontend_svc.metadata[0].name
port { number = 5000 }
}
}
}
}
}
rule {
host = "www.tasknote.cc"
http {
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service_v1.tasknote_frontend_svc.metadata[0].name
port { number = 5000 }
}
}
}
}
}
}
}
resource "kubernetes_secret_v1" "r2_backup_secrets" {
metadata {
name = "r2-backup-secrets"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
data = {
access_key = var.r2_access_key
secret_key = var.r2_secret_key
}
}
resource "kubernetes_cron_job_v1" "tasknote_db_backup" {
metadata {
name = "tasknote-db-backup"
namespace = kubernetes_namespace_v1.tasknote.metadata[0].name
}
spec {
schedule = "0 0,12 * * *"
job_template {
metadata {
labels = {
app = "taknote-db-backup"
}
}
spec {
template {
metadata {
labels = {
app = "tasknote-db-backup"
}
}
spec {
container {
name = "backup"
image = "postgres:15.8-alpine"
command = ["/bin/sh", "-c"]
args = [
<<-EOT
apk add --no-cache aws-cli
export PGPASSWORD=$POSTGRES_PASSWORD
FILENAME="backup-$(date +%Y%m%d%H%M%S).sql.gz"
echo "Starting backup of $POSTGRES_DB to $FILENAME..."
pg_dump -h $DB_HOST -U $POSTGRES_USER $POSTGRES_DB | gzip > /tmp/$FILENAME
echo "Uploading to R2..."
AWS_ACCESS_KEY_ID=$R2_ACCESS_KEY AWS_SECRET_ACCESS_KEY=$R2_SECRET_KEY \
aws s3 cp /tmp/$FILENAME s3://$R2_BUCKET/ --endpoint-url $R2_ENDPOINT
echo "Backup completed successfully."
EOT
]
env {
name = "DB_HOST"
value = "tasknote-db-svc"
}
env {
name = "POSTGRES_USER"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_user"
}
}
}
env {
name = "POSTGRES_PASSWORD"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_password"
}
}
}
env {
name = "POSTGRES_DB"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.tasknote_secrets.metadata[0].name
key = "postgres_db"
}
}
}
env {
name = "R2_ACCESS_KEY"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.r2_backup_secrets.metadata[0].name
key = "access_key"
}
}
}
env {
name = "R2_SECRET_KEY"
value_from {
secret_key_ref {
name = kubernetes_secret_v1.r2_backup_secrets.metadata[0].name
key = "secret_key"
}
}
}
env {
name = "R2_BUCKET"
value = var.r2_bucket_name
}
env {
name = "R2_ENDPOINT"
value = var.r2_endpoint
}
}
restart_policy = "OnFailure"
}
}
}
}
}
}