220 lines
6.0 KiB
HCL
220 lines
6.0 KiB
HCL
variable "wait_on" {
|
|
type = any
|
|
description = "Resources to wait on"
|
|
default = true
|
|
}
|
|
|
|
variable "server_ip" {
|
|
description = "Target server IP"
|
|
type = string
|
|
}
|
|
|
|
variable "ssh_private_key_path" {
|
|
description = "Path to SSH private key"
|
|
type = string
|
|
default = "~/.ssh/id_rsa"
|
|
}
|
|
|
|
variable "app_name" {
|
|
description = "Name of the application"
|
|
type = string
|
|
}
|
|
|
|
variable "image" {
|
|
description = "Container image"
|
|
type = string
|
|
}
|
|
|
|
variable "ports" {
|
|
description = "List of port mappings (e.g., ['8080:80', '8443:443'])"
|
|
type = list(string)
|
|
default = []
|
|
}
|
|
|
|
variable "volumes" {
|
|
description = "List of volume mounts (e.g., ['/host/path:/container/path:Z'])"
|
|
type = list(string)
|
|
default = []
|
|
}
|
|
|
|
variable "environment" {
|
|
description = "Environment variables as key-value pairs"
|
|
type = map(string)
|
|
default = {}
|
|
}
|
|
|
|
variable "command" {
|
|
description = "Command to run in container (list of strings)"
|
|
type = list(string)
|
|
default = []
|
|
}
|
|
|
|
variable "haproxy_services" {
|
|
description = "Multiple HAProxy service configurations"
|
|
type = list(object({
|
|
name = string
|
|
domain = string
|
|
port = string
|
|
host = optional(string, "127.0.0.1")
|
|
tls = optional(bool, false)
|
|
}))
|
|
default = []
|
|
}
|
|
|
|
variable "depends_on_services" {
|
|
description = "List of systemd services this app depends on"
|
|
type = list(string)
|
|
default = []
|
|
}
|
|
|
|
variable "restart_policy" {
|
|
description = "Systemd restart policy"
|
|
type = string
|
|
default = "always"
|
|
}
|
|
|
|
variable "healthcmd" {
|
|
default = ""
|
|
}
|
|
|
|
locals {
|
|
# Build all HAProxy labels for multiple services
|
|
haproxy_labels = flatten([
|
|
for svc in var.haproxy_services : [
|
|
"Label=haproxy.${svc.name}.enable=true",
|
|
"Label=haproxy.${svc.name}.domain=${svc.domain}",
|
|
"Label=haproxy.${svc.name}.port=${svc.port}",
|
|
"Label=haproxy.${svc.name}.host=${svc.host}",
|
|
"Label=haproxy.${svc.name}.tls=${svc.tls}"
|
|
]
|
|
])
|
|
}
|
|
|
|
resource "null_resource" "deploy_quadlet_app" {
|
|
depends_on = [var.wait_on]
|
|
triggers = {
|
|
app_name = var.app_name
|
|
image = var.image
|
|
server_ip = var.server_ip
|
|
ports = jsonencode(var.ports)
|
|
volumes = jsonencode(var.volumes)
|
|
environment = jsonencode(var.environment)
|
|
command = jsonencode(var.command)
|
|
haproxy_services = jsonencode(var.haproxy_services)
|
|
depends_on_services = jsonencode(var.depends_on_services)
|
|
ssh_private_key_path = var.ssh_private_key_path
|
|
restart_policy = var.restart_policy
|
|
}
|
|
|
|
provisioner "remote-exec" {
|
|
inline = compact(flatten([
|
|
[
|
|
# Wait for cloud-init to complete before proceeding
|
|
"cloud-init status --wait || true",
|
|
|
|
# Verify the user systemd session is ready and linger is enabled
|
|
"timeout 60 bash -c 'until loginctl show-user fourlights | grep -q \"Linger=yes\"; do sleep 2; done'",
|
|
|
|
# Create base quadlet file
|
|
"cat > /tmp/${var.app_name}.container << 'EOF'",
|
|
"[Unit]",
|
|
"Description=${var.app_name} Service",
|
|
"After=network-online.target",
|
|
"",
|
|
"[Container]",
|
|
"Image=${var.image}",
|
|
],
|
|
|
|
# Add ports (only if not empty)
|
|
length(var.ports) > 0 ? formatlist("PublishPort=127.0.0.1:%s", var.ports) : [],
|
|
|
|
# Add volumes (only if not empty)
|
|
length(var.volumes) > 0 ? formatlist("Volume=%s", var.volumes) : [],
|
|
|
|
# Add environment variables (only if not empty)
|
|
length(var.environment) > 0 ? formatlist("Environment=%s=%s", keys(var.environment), values(var.environment)) : [],
|
|
|
|
# Add command (only if not empty)
|
|
length(var.command) > 0 ? ["Exec=${join(" ", var.command)}"] : [],
|
|
|
|
# Add pre-computed HAProxy labels (only if not empty)
|
|
length(local.haproxy_labels) > 0 ? local.haproxy_labels : [],
|
|
# Add health checks if not empty
|
|
var.healthcmd != "" ? ["HealthCmd=${var.healthcmd}"] : [],
|
|
|
|
[
|
|
"",
|
|
"[Service]",
|
|
"Restart=${var.restart_policy}",
|
|
"",
|
|
"[Install]",
|
|
"WantedBy=default.target",
|
|
"EOF",
|
|
|
|
# Create volume directory
|
|
"mkdir -p /opt/storage/data/${var.app_name}",
|
|
|
|
# Move and activate
|
|
# Create directory more robustly
|
|
"test -d ~/.config/containers/systemd || mkdir -p ~/.config/containers/systemd",
|
|
"cp /tmp/${var.app_name}.container ~/.config/containers/systemd/${var.app_name}.container",
|
|
"systemctl --user daemon-reload",
|
|
"timeout 60 bash -c 'until systemctl --user list-unit-files | grep -q \"^${var.app_name}.service\"; do sleep 2; systemctl --user daemon-reload; done'",
|
|
|
|
"systemctl --user start ${var.app_name}",
|
|
"systemctl --user status ${var.app_name} --no-pager",
|
|
]
|
|
]))
|
|
|
|
|
|
connection {
|
|
type = "ssh"
|
|
host = var.server_ip
|
|
user = "fourlights"
|
|
agent = true
|
|
agent_identity = var.ssh_private_key_path
|
|
}
|
|
}
|
|
|
|
provisioner "remote-exec" {
|
|
when = destroy
|
|
inline = [
|
|
# Stop and remove the service
|
|
"systemctl --user stop ${self.triggers.app_name} || true",
|
|
|
|
# Remove the .container file
|
|
"rm -f ~/.config/containers/systemd/${self.triggers.app_name}.container",
|
|
|
|
# Reload systemd to remove the generated service
|
|
"systemctl --user daemon-reload",
|
|
|
|
# Force remove any lingering containers
|
|
"podman rm -f ${self.triggers.app_name} || true"
|
|
]
|
|
connection {
|
|
type = "ssh"
|
|
host = self.triggers.server_ip
|
|
user = "fourlights"
|
|
agent = true
|
|
agent_identity = self.triggers.ssh_private_key_path
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
output "app_name" {
|
|
value = var.app_name
|
|
}
|
|
|
|
output "service_status" {
|
|
value = "${var.app_name} deployed"
|
|
}
|
|
|
|
output "app_urls" {
|
|
value = [for svc in var.haproxy_services : format("%s://%s", (svc.tls == true ? "https" : "http"), svc.domain)]
|
|
}
|
|
|
|
output "installed" {
|
|
value = true
|
|
depends_on = [null_resource.deploy_quadlet_app]
|
|
} |