chore: full stack stability and migration fixes, plus react UI progress
Some checks failed
CI / podman-build (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-03-18 09:01:38 +02:00
parent 38cab8c246
commit a66d908eff
142 changed files with 12210 additions and 3402 deletions

20
deploy/hetzner/Caddyfile Normal file
View File

@@ -0,0 +1,20 @@
{
email ${ACME_EMAIL}
}
*.${DOMAIN} {
tls {
dns hetzner ${HETZNER_DNS_API_TOKEN}
}
# Reverse proxy to the MadBase Proxy service
reverse_proxy proxy:8000
}
# Also handle the root domain if needed
${DOMAIN} {
tls {
dns hetzner ${HETZNER_DNS_API_TOKEN}
}
reverse_proxy proxy:8000
}

View File

@@ -0,0 +1,16 @@
[Unit]
Description=MadBase Application Stack
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/madbase
ExecStartPre=/usr/bin/podman network create madbase_net || true
ExecStart=/usr/bin/podman-compose up
ExecStop=/usr/bin/podman-compose down
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,104 @@
# MadBase - Server 1: Control & Monitoring
services:
system:
image: git.madapes.com/madbase/control:latest
container_name: madbase_system
restart: unless-stopped
ports:
- "8001:8001"
env_file: .env
environment:
- DATABASE_URL=postgres://admin:${CONTROL_DB_PASSWORD}@${SERVER2_IP}:5433/madbase_control
- DEFAULT_TENANT_DB_URL=postgres://postgres:${POSTGRES_PASSWORD}@${SERVER2_IP}:5433/postgres
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS}
- LOKI_URL=http://localhost:3100
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
proxy:
image: git.madapes.com/madbase/proxy:latest
container_name: madbase_proxy
restart: unless-stopped
ports:
- "80:8000"
- "443:8000"
env_file: .env
environment:
- CONTROL_UPSTREAM_URL=http://system:8001
- WORKER_UPSTREAM_URLS=http://${SERVER2_IP}:8002,http://${SERVER3_IP}:8002,http://${SERVER4_IP}:8002
- CONTROL_DB_URL=postgres://admin:${CONTROL_DB_PASSWORD}@${SERVER2_IP}:5433/madbase_control
depends_on:
- system
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
victoriametrics:
image: victoriametrics/victoria-metrics:v1.101.0
container_name: madbase_vm
ports:
- "8428:8428"
volumes:
- madbase_vm_data:/victoria-metrics-data
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- "--storageDataPath=/victoria-metrics-data"
- "--httpListenAddr=:8428"
- "--promscrape.config=/etc/prometheus/prometheus.yml"
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
loki:
image: grafana/loki:2.9.6
container_name: madbase_loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- madbase_loki_data:/loki
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
grafana:
image: grafana/grafana:10.4.2
container_name: madbase_grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin}
volumes:
- madbase_grafana_data:/var/lib/grafana
depends_on:
- victoriametrics
- loki
networks:
- madbase_net
volumes:
madbase_vm_data:
madbase_loki_data:
madbase_grafana_data:
networks:
madbase_net:
name: madbase_net
external: true

View File

@@ -0,0 +1,124 @@
# MadBase - Server 2: Pillar Node 1 (DB Primary + Worker)
services:
etcd1:
image: quay.io/coreos/etcd:v3.5.9
container_name: madbase_etcd1
environment:
- ETCD_NAME=etcd1
- ETCD_DATA_DIR=/etcd-data
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://${SERVER2_IP}:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://${SERVER2_IP}:2379
- ETCD_INITIAL_CLUSTER_TOKEN=madbase-autobase
- ETCD_INITIAL_CLUSTER=etcd1=http://${SERVER2_IP}:2380,etcd2=http://${SERVER3_IP}:2380,etcd3=http://${SERVER4_IP}:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- etcd1_data:/etcd-data
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
patroni1:
image: registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2
container_name: madbase_patroni1
environment:
- PATRONI_SCOPE=madbase-cluster
- PATRONI_NAME=patroni1
- PATRONI_ETCD3_HOSTS=${SERVER2_IP}:2379,${SERVER3_IP}:2379,${SERVER4_IP}:2379
- PATRONI_POSTGRESQL_PASSWORD=${POSTGRES_PASSWORD}
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_RESTAPI_CONNECT_ADDRESS=${SERVER2_IP}:8008
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=${SERVER2_IP}:5432
volumes:
- db_data:/var/lib/postgresql/data
depends_on:
- etcd1
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
worker1:
image: git.madapes.com/madbase/worker:latest
container_name: madbase_worker1
restart: unless-stopped
ports:
- "8002:8002"
env_file: .env
environment:
- DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@localhost:5433/postgres
- CONTROL_DB_URL=postgres://admin:${CONTROL_DB_PASSWORD}@localhost:5433/madbase_control
- REDIS_URL=redis://${SERVER3_IP}:6379
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
haproxy:
image: haproxy:2.8-alpine
container_name: madbase_haproxy
volumes:
- ./autobase-haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
ports:
- "5432:5433" # Access via HAProxy
depends_on:
- patroni1
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db_backup:
image: prodrigestivill/postgres-backup-s3:17-alpine
container_name: madbase_db_backup
restart: unless-stopped
env_file: .env
environment:
- POSTGRES_DATABASE=postgres,madbase_control
- POSTGRES_HOST=localhost
- POSTGRES_PORT=5433
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY}
- S3_SECRET_ACCESS_KEY=${S3_SECRET_KEY}
- S3_BUCKET=${S3_BACKUP_BUCKET}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_ENDPOINT=${S3_ENDPOINT}
- S3_S3_FORCE_PATH_STYLE=true
- SCHEDULE=${BACKUP_SCHEDULE:-@daily}
depends_on:
- haproxy
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
etcd1_data:
db_data:
networks:
madbase_net:
name: madbase_net
external: true

View File

@@ -0,0 +1,114 @@
# MadBase - Server 3: Pillar Node 2 (DB Replica + Worker + Redis)
services:
etcd2:
image: quay.io/coreos/etcd:v3.5.9
container_name: madbase_etcd2
environment:
- ETCD_NAME=etcd2
- ETCD_DATA_DIR=/etcd-data
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://${SERVER3_IP}:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://${SERVER3_IP}:2379
- ETCD_INITIAL_CLUSTER_TOKEN=madbase-autobase
- ETCD_INITIAL_CLUSTER=etcd1=http://${SERVER2_IP}:2380,etcd2=http://${SERVER3_IP}:2380,etcd3=http://${SERVER4_IP}:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- etcd2_data:/etcd-data
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
patroni2:
image: registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2
container_name: madbase_patroni2
environment:
- PATRONI_SCOPE=madbase-cluster
- PATRONI_NAME=patroni2
- PATRONI_ETCD3_HOSTS=${SERVER2_IP}:2379,${SERVER3_IP}:2379,${SERVER4_IP}:2379
- PATRONI_POSTGRESQL_PASSWORD=${POSTGRES_PASSWORD}
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_RESTAPI_CONNECT_ADDRESS=${SERVER3_IP}:8008
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=${SERVER3_IP}:5432
volumes:
- db_data:/var/lib/postgresql/data
depends_on:
- etcd2
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
worker2:
image: git.madapes.com/madbase/worker:latest
container_name: madbase_worker2
restart: unless-stopped
ports:
- "8002:8002"
env_file: .env
environment:
- DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@localhost:5433/postgres
- CONTROL_DB_URL=postgres://admin:${CONTROL_DB_PASSWORD}@localhost:5433/madbase_control
- REDIS_URL=redis://localhost:6379
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
haproxy:
image: haproxy:2.8-alpine
container_name: madbase_haproxy_v2
volumes:
- ./autobase-haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
ports:
- "5433:5433"
depends_on:
- patroni2
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:7-alpine
container_name: madbase_redis
command: redis-server --appendonly yes
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
etcd2_data:
db_data:
redis_data:
networks:
madbase_net:
name: madbase_net
external: true

View File

@@ -0,0 +1,96 @@
# MadBase - Server 4: Pillar Node 3 (DB Replica + Worker)
services:
etcd3:
image: quay.io/coreos/etcd:v3.5.9
container_name: madbase_etcd3
environment:
- ETCD_NAME=etcd3
- ETCD_DATA_DIR=/etcd-data
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://${SERVER4_IP}:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://${SERVER4_IP}:2379
- ETCD_INITIAL_CLUSTER_TOKEN=madbase-autobase
- ETCD_INITIAL_CLUSTER=etcd1=http://${SERVER2_IP}:2380,etcd2=http://${SERVER3_IP}:2380,etcd3=http://${SERVER4_IP}:2380
- ETCD_INITIAL_CLUSTER_STATE=new
volumes:
- etcd3_data:/etcd-data
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
patroni3:
image: registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2
container_name: madbase_patroni3
environment:
- PATRONI_SCOPE=madbase-cluster
- PATRONI_NAME=patroni3
- PATRONI_ETCD3_HOSTS=${SERVER2_IP}:2379,${SERVER3_IP}:2379,${SERVER4_IP}:2379
- PATRONI_POSTGRESQL_PASSWORD=${POSTGRES_PASSWORD}
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_RESTAPI_CONNECT_ADDRESS=${SERVER4_IP}:8008
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=${SERVER4_IP}:5432
volumes:
- db_data:/var/lib/postgresql/data
depends_on:
- etcd3
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
worker3:
image: git.madapes.com/madbase/worker:latest
container_name: madbase_worker3
restart: unless-stopped
ports:
- "8002:8002"
env_file: .env
environment:
- DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@localhost:5433/postgres
- CONTROL_DB_URL=postgres://admin:${CONTROL_DB_PASSWORD}@localhost:5433/madbase_control
- REDIS_URL=redis://${SERVER3_IP}:6379
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
haproxy:
image: haproxy:2.8-alpine
container_name: madbase_haproxy_v3
volumes:
- ./autobase-haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
ports:
- "5433:5433"
depends_on:
- patroni3
restart: unless-stopped
networks:
- madbase_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
etcd3_data:
db_data:
networks:
madbase_net:
name: madbase_net
external: true

View File

@@ -0,0 +1,47 @@
resource "hcloud_firewall" "madbase_firewall" {
name = "madbase-firewall"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
direction = "in"
protocol = "icmp"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
}
resource "hcloud_firewall_resource" "fw_server1" {
firewall_id = hcloud_firewall.madbase_firewall.id
server_id = hcloud_server.server1.id
}

45
deploy/terraform/main.tf Normal file
View File

@@ -0,0 +1,45 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
}
}
variable "hcloud_token" {
sensitive = true
}
variable "ssh_public_key_path" {
default = "~/.ssh/id_rsa.pub"
}
variable "location" {
default = "fsn1" # Falkenstein, Germany
}
variable "server_type" {
default = "cpx21" # 3 vCPU, 4GB RAM
}
provider "hcloud" {
token = var.hcloud_token
}
resource "hcloud_ssh_key" "default" {
name = "madbase-deploy-key"
public_key = file(var.ssh_public_key_path)
}
resource "hcloud_network" "madbase_net" {
name = "madbase-net"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "madbase_subnet" {
network_id = hcloud_network.madbase_net.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}

View File

@@ -0,0 +1,97 @@
resource "hcloud_placement_group" "madbase_pg" {
name = "madbase-placement-group"
type = "spread"
}
resource "hcloud_server" "server1" {
name = "madbase-server1"
image = "debian-12"
server_type = var.server_type
location = var.location
ssh_keys = [hcloud_ssh_key.default.id]
placement_group_id = hcloud_placement_group.madbase_pg.id
user_data = <<-EOT
#cloud-config
runcmd:
- apt-get update
- apt-get install -y podman podman-compose jq curl
EOT
}
resource "hcloud_server_network" "server1_net" {
server_id = hcloud_server.server1.id
network_id = hcloud_network.madbase_net.id
ip = "10.0.1.1"
}
resource "hcloud_server" "server2" {
name = "madbase-server2"
image = "debian-12"
server_type = var.server_type
location = var.location
ssh_keys = [hcloud_ssh_key.default.id]
placement_group_id = hcloud_placement_group.madbase_pg.id
user_data = <<-EOT
#cloud-config
runcmd:
- apt-get update
- apt-get install -y podman podman-compose jq curl
EOT
}
resource "hcloud_server_network" "server2_net" {
server_id = hcloud_server.server2.id
network_id = hcloud_network.madbase_net.id
ip = "10.0.1.2"
}
resource "hcloud_server" "server3" {
name = "madbase-server3"
image = "debian-12"
server_type = var.server_type
location = var.location
ssh_keys = [hcloud_ssh_key.default.id]
placement_group_id = hcloud_placement_group.madbase_pg.id
user_data = <<-EOT
#cloud-config
runcmd:
- apt-get update
- apt-get install -y podman podman-compose jq curl
EOT
}
resource "hcloud_server_network" "server3_net" {
server_id = hcloud_server.server3.id
network_id = hcloud_network.madbase_net.id
ip = "10.0.1.3"
}
resource "hcloud_server" "server4" {
name = "madbase-server4"
image = "debian-12"
server_type = var.server_type
location = var.location
ssh_keys = [hcloud_ssh_key.default.id]
placement_group_id = hcloud_placement_group.madbase_pg.id
user_data = <<-EOT
#cloud-config
runcmd:
- apt-get update
- apt-get install -y podman podman-compose jq curl
EOT
}
resource "hcloud_server_network" "server4_net" {
server_id = hcloud_server.server4.id
network_id = hcloud_network.madbase_net.id
ip = "10.0.1.4"
}
output "server_ips" {
value = {
server1 = hcloud_server.server1.ipv4_address
server2 = hcloud_server.server2.ipv4_address
server3 = hcloud_server.server3.ipv4_address
server4 = hcloud_server.server4.ipv4_address
}
}