Compare commits
No commits in common. "dce4fa60222b9cfcd575d3316820ed7b544e6e2d" and "d6e33250f413c7cd2e476247de1e8844c8d23940" have entirely different histories.
dce4fa6022
...
d6e33250f4
|
|
@ -11,10 +11,6 @@ bootstrap/host/inventory/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
infra/gitea/data/
|
infra/gitea/data/
|
||||||
infra/matrix-server/synapse-data/
|
|
||||||
infra/matrix-server/postgres-data/
|
|
||||||
infra/matrix-server/.env
|
|
||||||
infra/omniroute/.env
|
|
||||||
infra/youtube-backup/.env
|
infra/youtube-backup/.env
|
||||||
infra/youtube-backup/cookies.txt
|
infra/youtube-backup/cookies.txt
|
||||||
infra/youtube-backup/archive.txt
|
infra/youtube-backup/archive.txt
|
||||||
|
|
@ -25,8 +21,6 @@ infra/youtube-backup/logs/
|
||||||
*.decrypted.yaml
|
*.decrypted.yaml
|
||||||
*.plain.yaml
|
*.plain.yaml
|
||||||
*.secret.local.yaml
|
*.secret.local.yaml
|
||||||
*.secret.yaml.plain
|
|
||||||
*.secret.plain
|
|
||||||
.age-key.txt
|
.age-key.txt
|
||||||
sops-age.key
|
sops-age.key
|
||||||
.sops.yaml.tmp
|
.sops.yaml.tmp
|
||||||
|
|
@ -37,7 +31,3 @@ sops-age.key
|
||||||
# Ignore local Python bytecode from validation helpers
|
# Ignore local Python bytecode from validation helpers
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
||||||
# Tofu state backups
|
|
||||||
*.tfstate.*.backup
|
|
||||||
*.tfstate.bak*
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
# Copy this file to .sops.yaml after replacing the age recipient with the
|
|
||||||
# public key generated on the Debian homelab server.
|
|
||||||
creation_rules:
|
|
||||||
- path_regex: '(^|/).*\.(secret|enc)\.(ya?ml|json)$'
|
|
||||||
encrypted_regex: '.*'
|
|
||||||
age: age1ywayyaqwksdzedhf3w8xlsf89lnzw89c5yxtwjp42pnc5cds4dds5j6yxm
|
|
||||||
|
|
@ -52,14 +52,6 @@ spec:
|
||||||
annotations:
|
annotations:
|
||||||
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} is over 85% full"
|
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} is over 85% full"
|
||||||
description: "PVC capacity is near exhaustion. Review app storage before it becomes an outage."
|
description: "PVC capacity is near exhaustion. Review app storage before it becomes an outage."
|
||||||
- alert: HomelabHostFilesystemNearlyFull
|
|
||||||
expr: 100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker"}) > 85
|
|
||||||
for: 15m
|
|
||||||
labels:
|
|
||||||
severity: warning
|
|
||||||
annotations:
|
|
||||||
summary: "Host filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} is over 85% full"
|
|
||||||
description: "The {{ $labels.mountpoint }} mount on {{ $labels.instance }} is near exhaustion. Review disk usage before it becomes an outage."
|
|
||||||
- name: homelab.edge
|
- name: homelab.edge
|
||||||
rules:
|
rules:
|
||||||
- alert: HomelabTraefik5xxSpike
|
- alert: HomelabTraefik5xxSpike
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,12 @@ locals {
|
||||||
backend_host = var.backend_host
|
backend_host = var.backend_host
|
||||||
backend_port = tostring(var.backend_port)
|
backend_port = tostring(var.backend_port)
|
||||||
})
|
})
|
||||||
default_conf_matrix = var.matrix_enabled ? templatefile("${path.module}/templates/matrix-vhost.conf.tftpl", {
|
|
||||||
matrix_server_name = var.matrix_server_name
|
|
||||||
matrix_backend_host = var.matrix_backend_host
|
|
||||||
matrix_backend_port = tostring(var.matrix_backend_port)
|
|
||||||
matrix_cert_dir = var.matrix_cert_dir
|
|
||||||
element_server_name = var.element_server_name
|
|
||||||
element_backend_host = var.element_backend_host
|
|
||||||
element_backend_port = tostring(var.element_backend_port)
|
|
||||||
}) : ""
|
|
||||||
config_hash = sha256(join("\n---\n", [
|
config_hash = sha256(join("\n---\n", [
|
||||||
local.compose_file,
|
local.compose_file,
|
||||||
local.default_conf,
|
local.default_conf,
|
||||||
local.default_vcl,
|
local.default_vcl,
|
||||||
local.haproxy_cfg,
|
local.haproxy_cfg,
|
||||||
local.squid_conf,
|
local.squid_conf,
|
||||||
local.default_conf_matrix,
|
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,11 +109,6 @@ resource "null_resource" "edge_services" {
|
||||||
destination = "/tmp/homelab-edge/config_files/squid.conf"
|
destination = "/tmp/homelab-edge/config_files/squid.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioner "file" {
|
|
||||||
content = local.default_conf_matrix
|
|
||||||
destination = "/tmp/homelab-edge/config_files/matrix.conf"
|
|
||||||
}
|
|
||||||
|
|
||||||
provisioner "remote-exec" {
|
provisioner "remote-exec" {
|
||||||
inline = [
|
inline = [
|
||||||
<<EOT
|
<<EOT
|
||||||
|
|
@ -199,7 +184,6 @@ sudo cp /tmp/homelab-edge/config_files/default.conf "$install_dir/config_files/d
|
||||||
sudo cp /tmp/homelab-edge/config_files/default.vcl "$install_dir/config_files/default.vcl"
|
sudo cp /tmp/homelab-edge/config_files/default.vcl "$install_dir/config_files/default.vcl"
|
||||||
sudo cp /tmp/homelab-edge/config_files/haproxy.cfg "$install_dir/config_files/haproxy.cfg"
|
sudo cp /tmp/homelab-edge/config_files/haproxy.cfg "$install_dir/config_files/haproxy.cfg"
|
||||||
sudo cp /tmp/homelab-edge/config_files/squid.conf "$install_dir/config_files/squid.conf"
|
sudo cp /tmp/homelab-edge/config_files/squid.conf "$install_dir/config_files/squid.conf"
|
||||||
sudo cp /tmp/homelab-edge/config_files/matrix.conf "$install_dir/config_files/matrix.conf"
|
|
||||||
|
|
||||||
if [ ! -s "$install_dir/certs/current.crt" ] || [ ! -s "$install_dir/certs/current.key" ]; then
|
if [ ! -s "$install_dir/certs/current.crt" ] || [ ! -s "$install_dir/certs/current.key" ]; then
|
||||||
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ services:
|
||||||
- "443:443"
|
- "443:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./config_files/default.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./config_files/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
- ./config_files/matrix.conf:/etc/nginx/conf.d/matrix.conf:ro
|
|
||||||
- ./certs:/etc/nginx/certs:ro
|
- ./certs:/etc/nginx/certs:ro
|
||||||
- ./certbot/www:/var/www/certbot:ro
|
- ./certbot/www:/var/www/certbot:ro
|
||||||
- nginx_cache:/var/cache/nginx
|
- nginx_cache:/var/cache/nginx
|
||||||
|
|
@ -38,21 +37,6 @@ services:
|
||||||
- squid_cache:/var/spool/squid
|
- squid_cache:/var/spool/squid
|
||||||
- squid_logs:/var/log/squid
|
- squid_logs:/var/log/squid
|
||||||
|
|
||||||
node-exporter:
|
|
||||||
image: prom/node-exporter:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
pid: host
|
|
||||||
ports:
|
|
||||||
- "0.0.0.0:9100:9100/tcp"
|
|
||||||
command:
|
|
||||||
- --path.procfs=/host/proc
|
|
||||||
- --path.sysfs=/host/sys
|
|
||||||
- --path.rootfs=/rootfs
|
|
||||||
volumes:
|
|
||||||
- /proc:/host/proc:ro
|
|
||||||
- /sys:/host/sys:ro
|
|
||||||
- /:/rootfs:ro
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nginx_cache:
|
nginx_cache:
|
||||||
nginx_dynamic_cache:
|
nginx_dynamic_cache:
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
#----------------------------------------------------------------------
|
|
||||||
# MATRIX + ELEMENT (isolated certs, does NOT touch shared current.crt)
|
|
||||||
# Rendered by bootstrap/edge terraform. Keep this file idempotent:
|
|
||||||
# it regenerates identically on every apply.
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
|
|
||||||
# --- ACME + HTTP redirect for matrix / element ---
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name ${matrix_server_name} ${element_server_name};
|
|
||||||
|
|
||||||
location ^~ /.well-known/acme-challenge/ {
|
|
||||||
root /var/www/certbot;
|
|
||||||
default_type "text/plain";
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- matrix server_name — Synapse client-server API ---
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
http2 on;
|
|
||||||
server_name ${matrix_server_name};
|
|
||||||
|
|
||||||
ssl_certificate ${matrix_cert_dir}/matrix.crt;
|
|
||||||
ssl_certificate_key ${matrix_cert_dir}/matrix.key;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_session_cache shared:matrix_ssl:10m;
|
|
||||||
ssl_session_timeout 1d;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://${matrix_backend_host}:${matrix_backend_port};
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
proxy_read_timeout 90s;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /.well-known/matrix {
|
|
||||||
proxy_pass http://${matrix_backend_host}:${matrix_backend_port}/.well-known/matrix;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- element server_name — Element web UI ---
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
http2 on;
|
|
||||||
server_name ${element_server_name};
|
|
||||||
|
|
||||||
ssl_certificate ${matrix_cert_dir}/matrix.crt;
|
|
||||||
ssl_certificate_key ${matrix_cert_dir}/matrix.key;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_session_cache shared:matrix_ssl:10m;
|
|
||||||
ssl_session_timeout 1d;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://${element_backend_host}:${element_backend_port};
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -103,49 +103,6 @@ variable "heimdall_backend_port" {
|
||||||
default = 8082
|
default = 8082
|
||||||
}
|
}
|
||||||
|
|
||||||
# Matrix (isolated vhost + its own cert; does NOT touch shared current.crt)
|
|
||||||
variable "matrix_enabled" {
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "matrix_server_name" {
|
|
||||||
type = string
|
|
||||||
default = "matrix.lab2025.duckdns.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Synapse client-server port on the LAN host. Override to use a Tailscale IP.
|
|
||||||
variable "matrix_backend_host" {
|
|
||||||
type = string
|
|
||||||
default = "192.168.100.73"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "matrix_backend_port" {
|
|
||||||
type = number
|
|
||||||
default = 8008
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "matrix_cert_dir" {
|
|
||||||
type = string
|
|
||||||
default = "/etc/nginx/certs"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Element (shares the matrix isolated cert)
|
|
||||||
variable "element_server_name" {
|
|
||||||
type = string
|
|
||||||
default = "element.lab2025.duckdns.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "element_backend_host" {
|
|
||||||
type = string
|
|
||||||
default = "192.168.100.73"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "element_backend_port" {
|
|
||||||
type = number
|
|
||||||
default = 8081
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "haproxy_stats_user" {
|
variable "haproxy_stats_user" {
|
||||||
type = string
|
type = string
|
||||||
default = "admin"
|
default = "admin"
|
||||||
|
|
|
||||||
|
|
@ -1865,46 +1865,6 @@ resource "helm_release" "prometheus_stack" {
|
||||||
prometheusSpec = {
|
prometheusSpec = {
|
||||||
nodeSelector = local.prometheus_stack_node_selector
|
nodeSelector = local.prometheus_stack_node_selector
|
||||||
retention = var.observability.prometheus.retention
|
retention = var.observability.prometheus.retention
|
||||||
additionalScrapeConfigs = yamlencode([
|
|
||||||
{
|
|
||||||
job_name = "rpi-node"
|
|
||||||
static_configs = [
|
|
||||||
{
|
|
||||||
targets = ["192.168.100.89:9100"]
|
|
||||||
labels = {
|
|
||||||
host_group = "edge-dns-worker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
metrics_path = "/metrics"
|
|
||||||
scheme = "http"
|
|
||||||
relabel_configs = [
|
|
||||||
{
|
|
||||||
source_labels = ["__address__"]
|
|
||||||
target_label = "instance"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
job_name = "oci-edge-node"
|
|
||||||
static_configs = [
|
|
||||||
{
|
|
||||||
targets = ["132.145.170.74:9100"]
|
|
||||||
labels = {
|
|
||||||
host_group = "public-edge"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
metrics_path = "/metrics"
|
|
||||||
scheme = "http"
|
|
||||||
relabel_configs = [
|
|
||||||
{
|
|
||||||
source_labels = ["__address__"]
|
|
||||||
target_label = "instance"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
resources = {
|
resources = {
|
||||||
requests = {
|
requests = {
|
||||||
cpu = "100m"
|
cpu = "100m"
|
||||||
|
|
|
||||||
28
homelab.yml
28
homelab.yml
|
|
@ -12,8 +12,6 @@ domain:
|
||||||
subdomains:
|
subdomains:
|
||||||
- demos.lab2025.duckdns.org
|
- demos.lab2025.duckdns.org
|
||||||
- heimdall.lab2025.duckdns.org
|
- heimdall.lab2025.duckdns.org
|
||||||
- matrix.lab2025.duckdns.org
|
|
||||||
- element.lab2025.duckdns.org
|
|
||||||
- grafana.lab2025.duckdns.org
|
- grafana.lab2025.duckdns.org
|
||||||
- prometheus.lab2025.duckdns.org
|
- prometheus.lab2025.duckdns.org
|
||||||
- alertmanager.lab2025.duckdns.org
|
- alertmanager.lab2025.duckdns.org
|
||||||
|
|
@ -118,32 +116,6 @@ services:
|
||||||
suwayomi_port: 4567
|
suwayomi_port: 4567
|
||||||
maintainerr_port: 6246
|
maintainerr_port: 6246
|
||||||
flaresolverr_port: 8191
|
flaresolverr_port: 8191
|
||||||
matrix:
|
|
||||||
host: debian
|
|
||||||
install_dir: /home/jv/matrix-server
|
|
||||||
synapse_port: 8008
|
|
||||||
element_port: 8081
|
|
||||||
server_name: matrix.lab2025.duckdns.org
|
|
||||||
synapse_image: matrixdotorg/synapse:latest
|
|
||||||
element_image: vectorim/element-web:latest
|
|
||||||
postgres_image: postgres:16-alpine
|
|
||||||
postgres_db: synapse
|
|
||||||
postgres_user: synapse
|
|
||||||
bot_user: hermes
|
|
||||||
allowed_users:
|
|
||||||
- "@jv:matrix.lab2025.duckdns.org"
|
|
||||||
omniroute:
|
|
||||||
host: debian
|
|
||||||
install_dir: /home/jv/homelab-omniroute
|
|
||||||
port: 20128
|
|
||||||
image: diegosouzapw/omniroute:latest
|
|
||||||
container_name: omniroute
|
|
||||||
hermes:
|
|
||||||
host: debian
|
|
||||||
home: /home/jv/.hermes
|
|
||||||
install_dir: /home/jv/.hermes/hermes-agent
|
|
||||||
uv_bin: /home/jv/.hermes/bin/uv
|
|
||||||
bot_user: hermes
|
|
||||||
traefik:
|
traefik:
|
||||||
host: kubernetes
|
host: kubernetes
|
||||||
load_balancer_ip: 192.168.100.240
|
load_balancer_ip: 192.168.100.240
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ FLARESOLVERR_PORT=8191
|
||||||
KAPOWARR_PORT=5656
|
KAPOWARR_PORT=5656
|
||||||
SUWAYOMI_PORT=4567
|
SUWAYOMI_PORT=4567
|
||||||
MAINTAINERR_PORT=6246
|
MAINTAINERR_PORT=6246
|
||||||
JELLYFIN_PORT=8096
|
|
||||||
JELLYSEERR_PORT=5055
|
|
||||||
|
|
||||||
# Optional bootstrap values. Leave blank until the apps have created API keys.
|
# Optional bootstrap values. Leave blank until the apps have created API keys.
|
||||||
PROWLARR_API_KEY=
|
PROWLARR_API_KEY=
|
||||||
|
|
|
||||||
|
|
@ -141,26 +141,6 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- arr
|
- arr
|
||||||
|
|
||||||
jellyfin:
|
|
||||||
<<: *linuxserver-common
|
|
||||||
image: ${JELLYFIN_IMAGE:-lscr.io/linuxserver/jellyfin:latest}
|
|
||||||
container_name: ${JELLYFIN_CONTAINER_NAME:-arr-jellyfin}
|
|
||||||
dns:
|
|
||||||
- ${ARR_DNS_PRIMARY:-1.1.1.1}
|
|
||||||
- ${ARR_DNS_SECONDARY:-8.8.8.8}
|
|
||||||
group_add:
|
|
||||||
- "44" # video group (for access to DRM render node on the host)
|
|
||||||
devices:
|
|
||||||
- "/dev/dri/renderD128:/dev/dri/renderD128"
|
|
||||||
- "/dev/dri/card0:/dev/dri/card0"
|
|
||||||
ports:
|
|
||||||
- "${JELLYFIN_PORT:-8096}:8096"
|
|
||||||
# /config keeps its state; /data/media carries the media root so libraries
|
|
||||||
# can point at movies/tv/comics/manga/youtube. See README/media layout.
|
|
||||||
volumes:
|
|
||||||
- ${ARR_ROOT:-/data/arr}/config/jellyfin:/config
|
|
||||||
- ${ARR_ROOT:-/data/arr}/media:/data/media
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
arr:
|
arr:
|
||||||
name: homelab-arr
|
name: homelab-arr
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Hermes agent secrets - SOPS-encrypted. Do not commit plaintext.
|
|
||||||
# Decrypted at deploy time by jeannie deploy_hermes.
|
|
||||||
MATRIX_BOT_PASSWORD: ENC[AES256_GCM,data:+21aq+VzaYZLizC2p8oOEf1614M=,iv:qKpVrpE50ByIOXW32RnYy7zwPIbtt3UybAn7Pb8bY5Y=,tag:utUx46AyxK+jk3c7Wdnygw==,type:str]
|
|
||||||
HERMES_OMNIROUTE_API_KEY: ""
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBySEFyREdGOS9HOGtkeVRF
|
|
||||||
M0w4aUcxK2F5NTdSR29CSDY2SHZVbWlXZ0hzCmZOV3VXa3J5eEJLY2pTa2RHTGhV
|
|
||||||
eWpXdXVmVWFiLzZpMmlyUmJIQ3hUdjAKLS0tIG8ybWVNTXNZTHM4dnJ4Y2Z5VXAr
|
|
||||||
TXVIVXVWenlBc0lEZ0VSWmdFQmpPSGsK2FtwcyBFqVzCphxGVV8xg6QZea3SC6wt
|
|
||||||
gX1rp3icFegLisy/m4cUJkfku9zTHoHjsqPjbl+xzs3iaReHSVQRlA==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1ywayyaqwksdzedhf3w8xlsf89lnzw89c5yxtwjp42pnc5cds4dds5j6yxm
|
|
||||||
encrypted_regex: .*
|
|
||||||
lastmodified: "2026-09-16T16:32:24Z"
|
|
||||||
mac: ENC[AES256_GCM,data:0wKCLSIIDh4+/IHY/UN2KeYEdHBn0ZhCVRcIa3TV4P8CtRbcItQbhj5e95q5FPWJRBASXIxj/WwOkyd5JFUmxH5XhaeKCHvxbgNMClPb8Ic5KSnNMRgSZtrCgKlACySFEDL5aD9mxqkd3f5bVd+JY04HGHkSJUWRfdGnNdwGfnw=,iv:BPyQLTty1qjSldlxeUEC/Uo80H1R8Vlfey9ie26JZ30=,tag:oYZ2TVwKUgf+oPl8nvqeLA==,type:str]
|
|
||||||
version: 3.13.3
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Matrix stack — non-secret defaults.
|
|
||||||
# Copy to a sibling .env (gitignored) or export these as env vars when running
|
|
||||||
# deploy_matrix. NEVER put real secrets in this committed example.
|
|
||||||
MATRIX_SERVER_NAME=matrix.lab2025.duckdns.org
|
|
||||||
MATRIX_SYNAPSE_PORT=8008
|
|
||||||
MATRIX_ELEMENT_PORT=8081
|
|
||||||
MATRIX_SYNAPSE_CONTAINER_NAME=matrix-synapse
|
|
||||||
MATRIX_POSTGRES_CONTAINER_NAME=matrix-postgres
|
|
||||||
MATRIX_ELEMENT_CONTAINER_NAME=matrix-element
|
|
||||||
MATRIX_POSTGRES_DB=synapse
|
|
||||||
MATRIX_POSTGRES_USER=synapse
|
|
||||||
|
|
||||||
# SECRETS (must be supplied at deploy time, not committed):
|
|
||||||
# MATRIX_POSTGRES_PASSWORD postgres/synapse db password
|
|
||||||
# MATRIX_REGISTRATION_SHARED_SECRET shared registration secret for register_new_matrix_user
|
|
||||||
# MATRIX_MACAROON_SECRET_KEY macaroon secret
|
|
||||||
# MATRIX_FORM_SECRET form secret
|
|
||||||
# MATRIX_BOT_PASSWORD hermes bot account password
|
|
||||||
# (The synapse signing key is generated by Synapse on first boot — not a secret you set.)
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Deploy/update the self-hosted Matrix stack (Synapse + Postgres + Element).
|
|
||||||
# Idempotent: renders homeserver.yaml from the committed template, ensures
|
|
||||||
# correct data-dir ownership, and starts containers.
|
|
||||||
#
|
|
||||||
# Secrets are read from an install-dir .env (written by jeannie deploy_matrix),
|
|
||||||
# NOT from the repo. The signing key is created by Synapse on first boot.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
ENV_FILE="${MATRIX_ENV_FILE:-${SCRIPT_DIR}/.env}"
|
|
||||||
|
|
||||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
|
||||||
echo "Missing secret env file ${ENV_FILE}. Run jeannie deploy-matrix first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -a
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "${ENV_FILE}"
|
|
||||||
set +a
|
|
||||||
|
|
||||||
MATRIX_SERVER_NAME="${MATRIX_SERVER_NAME:-matrix.lab2025.duckdns.org}"
|
|
||||||
MATRIX_POSTGRES_DB="${MATRIX_POSTGRES_DB:-synapse}"
|
|
||||||
MATRIX_POSTGRES_USER="${MATRIX_POSTGRES_USER:-synapse}"
|
|
||||||
SYNAPSE_UID="${MATRIX_SYNAPSE_UID:-991}"
|
|
||||||
POSTGRES_UID="${MATRIX_POSTGRES_UID:-70}"
|
|
||||||
|
|
||||||
render_homeserver() {
|
|
||||||
local tpl="${SCRIPT_DIR}/homeserver.yaml.tpl"
|
|
||||||
local out="${SCRIPT_DIR}/synapse-data/homeserver.yaml"
|
|
||||||
for var in MATRIX_SERVER_NAME MATRIX_POSTGRES_DB MATRIX_POSTGRES_USER \
|
|
||||||
MATRIX_POSTGRES_PASSWORD MATRIX_REGISTRATION_SHARED_SECRET \
|
|
||||||
MATRIX_MACAROON_SECRET_KEY MATRIX_FORM_SECRET; do
|
|
||||||
if [[ -z "${!var:-}" ]]; then
|
|
||||||
echo "Missing required secret/env ${var}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
# shellcheck disable=SC2016
|
|
||||||
envsubst \
|
|
||||||
'${MATRIX_SERVER_NAME} ${MATRIX_POSTGRES_DB} ${MATRIX_POSTGRES_USER} ${MATRIX_POSTGRES_PASSWORD} ${MATRIX_REGISTRATION_SHARED_SECRET} ${MATRIX_MACAROON_SECRET_KEY} ${MATRIX_FORM_SECRET}' \
|
|
||||||
<"${tpl}" >"${out}"
|
|
||||||
cp "${SCRIPT_DIR}/log_config.yaml" "${SCRIPT_DIR}/synapse-data/${MATRIX_SERVER_NAME}.log.config"
|
|
||||||
}
|
|
||||||
|
|
||||||
sudo mkdir -p "${SCRIPT_DIR}/synapse-data" "${SCRIPT_DIR}/postgres-data"
|
|
||||||
|
|
||||||
# First boot: generate signing key + log config (the files a hand-written
|
|
||||||
# homeserver.yaml would not provide). Without this Synapse fails to boot on a
|
|
||||||
# missing signing key. Only touches the data dir (not postgres).
|
|
||||||
if [[ ! -f "${SCRIPT_DIR}/synapse-data/${MATRIX_SERVER_NAME}.signing.key" ]]; then
|
|
||||||
echo "First boot: generating Synapse signing key + log config..."
|
|
||||||
docker compose --env-file "${ENV_FILE}" -f "${SCRIPT_DIR}/docker-compose.yml" \
|
|
||||||
run --rm --no-deps synapse generate --config-path /data/homeserver.yaml
|
|
||||||
fi
|
|
||||||
|
|
||||||
render_homeserver
|
|
||||||
|
|
||||||
# Ownership foot-gun: wrong uid prevents boot (synapse 991, postgres 70).
|
|
||||||
sudo chown -R "${SYNAPSE_UID}:${SYNAPSE_UID}" "${SCRIPT_DIR}/synapse-data"
|
|
||||||
sudo chown -R "${POSTGRES_UID}:${POSTGRES_UID}" "${SCRIPT_DIR}/postgres-data"
|
|
||||||
|
|
||||||
docker compose --env-file "${ENV_FILE}" -f "${SCRIPT_DIR}/docker-compose.yml" up -d
|
|
||||||
|
|
||||||
echo "Matrix stack up:"
|
|
||||||
echo " Synapse http://127.0.0.1:${MATRIX_SYNAPSE_PORT:-8008}"
|
|
||||||
echo " Element http://127.0.0.1:${MATRIX_ELEMENT_PORT:-8081}"
|
|
||||||
echo " server_name ${MATRIX_SERVER_NAME}"
|
|
||||||
|
|
||||||
cat <<'EOF'
|
|
||||||
Next: register users (hermes bot, and any humans) via the shared-secret CLI:
|
|
||||||
docker exec -u 991 matrix-synapse register_new_matrix_user -c /data/homeserver.yaml -u NAME -p PASS [--no-admin]
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
services:
|
|
||||||
synapse:
|
|
||||||
image: matrixdotorg/synapse:latest
|
|
||||||
container_name: ${MATRIX_SYNAPSE_CONTAINER_NAME:-matrix-synapse}
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
environment:
|
|
||||||
SYNAPSE_SERVER_NAME: ${MATRIX_SERVER_NAME:-matrix.lab2025.duckdns.org}
|
|
||||||
SYNAPSE_REPORT_STATS: "no"
|
|
||||||
# federation disabled (user choice): client-only listener on 8008
|
|
||||||
ports:
|
|
||||||
- "${MATRIX_SYNAPSE_PORT:-8008}:8008"
|
|
||||||
volumes:
|
|
||||||
- ./synapse-data:/data
|
|
||||||
networks:
|
|
||||||
- matrix
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
container_name: ${MATRIX_POSTGRES_CONTAINER_NAME:-matrix-postgres}
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${MATRIX_POSTGRES_DB:-synapse}
|
|
||||||
POSTGRES_USER: ${MATRIX_POSTGRES_USER:-synapse}
|
|
||||||
POSTGRES_PASSWORD: ${MATRIX_POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
|
|
||||||
volumes:
|
|
||||||
- ./postgres-data:/var/lib/postgresql/data
|
|
||||||
networks:
|
|
||||||
- matrix
|
|
||||||
|
|
||||||
element:
|
|
||||||
image: vectorim/element-web:latest
|
|
||||||
container_name: ${MATRIX_ELEMENT_CONTAINER_NAME:-matrix-element}
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- synapse
|
|
||||||
ports:
|
|
||||||
- "${MATRIX_ELEMENT_PORT:-8081}:80"
|
|
||||||
volumes:
|
|
||||||
# Must mount the FILE, not a directory (element-web reads /app/config.json)
|
|
||||||
- ./element-config/config.json:/app/config.json
|
|
||||||
networks:
|
|
||||||
- matrix
|
|
||||||
|
|
||||||
networks:
|
|
||||||
matrix:
|
|
||||||
driver: bridge
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"default_server_name": "matrix.lab2025.duckdns.org",
|
|
||||||
"default_server_config": {
|
|
||||||
"m.homeserver": {
|
|
||||||
"base_url": "https://matrix.lab2025.duckdns.org",
|
|
||||||
"server_name": "matrix.lab2025.duckdns.org"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"disable_custom_urls": false,
|
|
||||||
"disable_guests": true,
|
|
||||||
"disable_login_language_selector": false,
|
|
||||||
"disable_3pid_login": false,
|
|
||||||
"force_verification": false,
|
|
||||||
"brand": "Matrix",
|
|
||||||
"default_federate": false,
|
|
||||||
"show_labs_settings": true,
|
|
||||||
"default_theme": "dark",
|
|
||||||
"room_directory": {
|
|
||||||
"servers": ["matrix.lab2025.duckdns.org"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Configuration file for Synapse.
|
|
||||||
# This is generated by Jeannie from infra/matrix-server/homeserver.yaml.tpl.
|
|
||||||
# Secrets (registration_shared_secret, macaroon_secret_key, form_secret, postgres
|
|
||||||
# password, signing key) are injected by the deploy_matrix() bootstrap — never store
|
|
||||||
# them in this template.
|
|
||||||
|
|
||||||
server_name: "${MATRIX_SERVER_NAME}"
|
|
||||||
pid_file: /data/homeserver.pid
|
|
||||||
listeners:
|
|
||||||
- port: 8008
|
|
||||||
resources:
|
|
||||||
- compress: false
|
|
||||||
names:
|
|
||||||
- client
|
|
||||||
- federation
|
|
||||||
tls: false
|
|
||||||
type: http
|
|
||||||
x_forwarded: true
|
|
||||||
database:
|
|
||||||
name: psycopg2
|
|
||||||
args:
|
|
||||||
user: "${MATRIX_POSTGRES_USER}"
|
|
||||||
password: "${MATRIX_POSTGRES_PASSWORD}"
|
|
||||||
database: "${MATRIX_POSTGRES_DB}"
|
|
||||||
host: postgres
|
|
||||||
port: 5432
|
|
||||||
cp_min: 5
|
|
||||||
cp_max: 10
|
|
||||||
log_config: "/data/${MATRIX_SERVER_NAME}.log.config"
|
|
||||||
media_store_path: /data/media_store
|
|
||||||
registration_shared_secret: "${MATRIX_REGISTRATION_SHARED_SECRET}"
|
|
||||||
report_stats: false
|
|
||||||
macaroon_secret_key: "${MATRIX_MACAROON_SECRET_KEY}"
|
|
||||||
form_secret: "${MATRIX_FORM_SECRET}"
|
|
||||||
signing_key_path: "/data/${MATRIX_SERVER_NAME}.signing.key"
|
|
||||||
trusted_key_servers:
|
|
||||||
- server_name: "matrix.org"
|
|
||||||
|
|
||||||
# vim:ft=yaml
|
|
||||||
user_directory:
|
|
||||||
enabled: true
|
|
||||||
search_all_users: true
|
|
||||||
prefer_local_users: true
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
version: 1
|
|
||||||
|
|
||||||
formatters:
|
|
||||||
precise:
|
|
||||||
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
console:
|
|
||||||
class: logging.StreamHandler
|
|
||||||
formatter: precise
|
|
||||||
|
|
||||||
loggers:
|
|
||||||
_placeholder:
|
|
||||||
level: "INFO"
|
|
||||||
|
|
||||||
synapse.storage.SQL:
|
|
||||||
# beware: increasing this to DEBUG will make synapse log sensitive
|
|
||||||
# information such as access tokens.
|
|
||||||
level: INFO
|
|
||||||
|
|
||||||
root:
|
|
||||||
level: INFO
|
|
||||||
handlers: [console]
|
|
||||||
|
|
||||||
disable_existing_loggers: false
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Matrix homeserver secrets - SOPS-encrypted. Do not commit plaintext.
|
|
||||||
# Decrypted at deploy time by jeannie deploy_matrix.
|
|
||||||
MATRIX_POSTGRES_PASSWORD: ENC[AES256_GCM,data:HYti676SrsKpr5UuYX7wrg==,iv:pggYfnjqZAGulrCNVBJ/vcICcsn9hic150hmRb1D5uE=,tag:rs4jSLwMZeoaUgjqCYIkTA==,type:str]
|
|
||||||
MATRIX_REGISTRATION_SHARED_SECRET: ENC[AES256_GCM,data:+OChmnoaVrWhLCQHPe3sSbfuEs2H6nJuPhFPelRM+hOPCADXYZPiUPxH3RJfLY9niCg=,iv:b2wVg4Xg/qTTT7ghyNJJNOfnafESE7EZAZoVUKFJxJc=,tag:ewdDXpFoeb2/+HlLzYJySw==,type:str]
|
|
||||||
MATRIX_MACAROON_SECRET_KEY: ENC[AES256_GCM,data:0IY3J7ijJhYiqd10A2D/xo0Utok0llFFjUOOqY57QjnCA8tgeJOH4lNuiPTScKPMnnI=,iv:PkqyW3RaQpA8+TQtKI6ibo11CJVUiod/OO2QLBa4Lrs=,tag:Z8j5xqlcysgkrmbmiMVNEA==,type:str]
|
|
||||||
MATRIX_FORM_SECRET: ENC[AES256_GCM,data:WfaToWF7NO5VgpeJcuONlhHK+GopNjPnVAjFLorrronMNUzTKIICr2xjrVBq3wvPuf4=,iv:Qgf7fdO9boSV136LL7/IxNcYz33gNP7YbIQohJFEAAI=,tag:FXtUqNj/JtsrWLx/ntypag==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1THJOdkhOb21UeFk2NHdx
|
|
||||||
bUNSMTZUVDlOeUh1T1N4WnA5akpVVWVEVFRNCk02RG9kNENCVHJUSVVXNkE0ZUFh
|
|
||||||
ek8vU2NzRUwvMm1EMDNwdnc1YjhzZ2cKLS0tIFpmZU5Pb0FJRTl1ZktVVXA1RTAw
|
|
||||||
MjBHcmZQTFJqZUdHWVgxdURhQk5wRW8Ka0TXyBnr7Puq+dYVizMK+HIAEncAD2bl
|
|
||||||
5JGP5TU1Dp0I9njHJYmDAicx5iaMhfwQe4iL9/73FOy1vnfYPlv9gQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1ywayyaqwksdzedhf3w8xlsf89lnzw89c5yxtwjp42pnc5cds4dds5j6yxm
|
|
||||||
encrypted_regex: .*
|
|
||||||
lastmodified: "2026-09-16T16:32:24Z"
|
|
||||||
mac: ENC[AES256_GCM,data:lyhzE48mseEWnf2+UoYy4lkBPFtvD3PuB4BR/LbTEkz4xrC0KYHqlVma5dEhLoOTs+k5rhFy3ko3lZoZ396kkrOMftd9zyYTK7Nk/ZxPRrg3gPqr/Dd4bTCGDstznNMgbsCgzklhB35IYWgP/PL5ccZiP/OnqWXoTMPRYy/EPC8=,iv:FVhAK3zGSROkI0iyhRo1+PMlXLbZQ+74A4pnlSR6Y80=,tag:x3/mNU6l8PqPBb0cRbPAuQ==,type:str]
|
|
||||||
version: 3.13.3
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# OmniRoute — non-secret defaults.
|
|
||||||
# Copy to a sibling .env or export when running deploy_omniroute.
|
|
||||||
# Secrets (JWT_SECRET, API_KEY_SECRET, STORAGE_ENCRYPTION_*) are auto-generated
|
|
||||||
# by OmniRoute into its data volume (server.env) on first boot — not stored here.
|
|
||||||
OMNIROUTE_IMAGE=diegosouzapw/omniroute:latest
|
|
||||||
OMNIROUTE_CONTAINER_NAME=omniroute
|
|
||||||
OMNIROUTE_PORT=20128
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Deploy/update the OmniRoute LLM-routing gateway that Hermes uses as its model
|
|
||||||
# provider (http://localhost:20128/v1). Idempotent. Secrets live in the data
|
|
||||||
# volume (server.env) generated by OmniRoute on first boot.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
ENV_FILE="${OMNIROUTE_ENV_FILE:-${SCRIPT_DIR}/.env}"
|
|
||||||
|
|
||||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
|
||||||
echo "Missing env file ${ENV_FILE}. Run jeannie deploy-omniroute first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -a
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "${ENV_FILE}"
|
|
||||||
set +a
|
|
||||||
|
|
||||||
sudo mkdir -p "${SCRIPT_DIR}"
|
|
||||||
docker compose --env-file "${ENV_FILE}" -f "${SCRIPT_DIR}/docker-compose.yml" up -d
|
|
||||||
|
|
||||||
echo "OmniRoute up at http://127.0.0.1:${OMNIROUTE_PORT:-20128}"
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
services:
|
|
||||||
omniroute:
|
|
||||||
image: ${OMNIROUTE_IMAGE:-diegosouzapw/omniroute:latest}
|
|
||||||
container_name: ${OMNIROUTE_CONTAINER_NAME:-omniroute}
|
|
||||||
restart: unless-stopped
|
|
||||||
stop_timeout: 40
|
|
||||||
ports:
|
|
||||||
- "${OMNIROUTE_PORT:-20128}:20128"
|
|
||||||
volumes:
|
|
||||||
- omniroute-data:/app/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
omniroute-data:
|
|
||||||
|
|
@ -55,24 +55,6 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- dns
|
- dns
|
||||||
|
|
||||||
node-exporter:
|
|
||||||
image: prom/node-exporter:latest
|
|
||||||
container_name: ${NODE_EXPORTER_CONTAINER_NAME:-homelab-node-exporter}
|
|
||||||
restart: unless-stopped
|
|
||||||
pid: host
|
|
||||||
ports:
|
|
||||||
- "${NODE_EXPORTER_BIND_IP:-192.168.100.89}:9100:9100/tcp"
|
|
||||||
command:
|
|
||||||
- --path.procfs=/host/proc
|
|
||||||
- --path.sysfs=/host/sys
|
|
||||||
- --path.rootfs=/rootfs
|
|
||||||
volumes:
|
|
||||||
- /proc:/host/proc:ro
|
|
||||||
- /sys:/host/sys:ro
|
|
||||||
- /:/rootfs:ro
|
|
||||||
networks:
|
|
||||||
- dns
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dns:
|
dns:
|
||||||
name: ${RPI_SERVICES_NETWORK:-homelab-rpi-services}
|
name: ${RPI_SERVICES_NETWORK:-homelab-rpi-services}
|
||||||
|
|
|
||||||
336
jeannie
336
jeannie
|
|
@ -4117,233 +4117,6 @@ ENV_EOT
|
||||||
echo "Heimdall is reachable at http://127.0.0.1:${http_port}/ and ${public_url%/}/."
|
echo "Heimdall is reachable at http://127.0.0.1:${http_port}/ and ${public_url%/}/."
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy_matrix() {
|
|
||||||
local mode="${LAB_MATRIX_DEPLOY:-true}"
|
|
||||||
local install_dir="${LAB_MATRIX_INSTALL_DIR:-/home/jv/matrix-server}"
|
|
||||||
local image=""
|
|
||||||
local container_name=""
|
|
||||||
local http_port=""
|
|
||||||
local ssh_port=""
|
|
||||||
local domain=""
|
|
||||||
local install_docker="false"
|
|
||||||
local source_dir="${REPO_ROOT}/infra/matrix-server"
|
|
||||||
local value_name
|
|
||||||
local value
|
|
||||||
|
|
||||||
require_debian_server "deploy-matrix"
|
|
||||||
|
|
||||||
if disabled_value "${mode}"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
for value_name in install_dir; do
|
|
||||||
value="${!value_name}"
|
|
||||||
if [[ "${value}" == *$'\n'* ]]; then
|
|
||||||
echo "${value_name} cannot contain a newline." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ! -s "${source_dir}/docker-compose.yml" || ! -s "${source_dir}/homeserver.yaml.tpl" || ! -s "${source_dir}/bootstrap.sh" ]]; then
|
|
||||||
echo "Missing Matrix source files in ${source_dir}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Deploying Matrix stack on the Debian host at ${install_dir}..."
|
|
||||||
|
|
||||||
ensure_local_docker_compose "MATRIX" "${install_docker}"
|
|
||||||
repair_local_docker_iptables
|
|
||||||
|
|
||||||
sudo mkdir -p "${install_dir}"
|
|
||||||
sudo cp "${source_dir}/docker-compose.yml" "${install_dir}/docker-compose.yml"
|
|
||||||
sudo cp "${source_dir}/homeserver.yaml.tpl" "${install_dir}/homeserver.yaml.tpl"
|
|
||||||
sudo cp "${source_dir}/log_config.yaml" "${install_dir}/log_config.yaml"
|
|
||||||
sudo cp "${source_dir}/element-config/config.json" "${install_dir}/element-config/config.json" 2>/dev/null || {
|
|
||||||
sudo mkdir -p "${install_dir}/element-config"
|
|
||||||
sudo cp "${source_dir}/element-config/config.json" "${install_dir}/element-config/config.json"
|
|
||||||
}
|
|
||||||
sudo cp "${source_dir}/bootstrap.sh" "${install_dir}/bootstrap.sh"
|
|
||||||
sudo chmod +x "${install_dir}/bootstrap.sh"
|
|
||||||
|
|
||||||
# Secrets come from the SOPS-encrypted repo file, decrypted at deploy time.
|
|
||||||
# (Env vars override the file, for manual/ops overrides.)
|
|
||||||
if ! sops_load_secrets "${REPO_ROOT}/infra/matrix-server/matrix.secret.yaml"; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${MATRIX_POSTGRES_PASSWORD:-}" || -z "${MATRIX_REGISTRATION_SHARED_SECRET:-}" ]]; then
|
|
||||||
echo "MATRIX_POSTGRES_PASSWORD and MATRIX_REGISTRATION_SHARED_SECRET missing after decrypt." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local matrix_server_name="${LAB_MATRIX_SERVER_NAME:-matrix.lab2025.duckdns.org}"
|
|
||||||
local matrix_synapse_port="${LAB_MATRIX_SYNAPSE_PORT:-8008}"
|
|
||||||
local matrix_element_port="${LAB_MATRIX_ELEMENT_PORT:-8081}"
|
|
||||||
local postgres_db="${LAB_MATRIX_POSTGRES_DB:-synapse}"
|
|
||||||
local postgres_user="${LAB_MATRIX_POSTGRES_USER:-synapse}"
|
|
||||||
|
|
||||||
sudo tee "${install_dir}/.env" >/dev/null <<ENV_EOT
|
|
||||||
MATRIX_SERVER_NAME=${matrix_server_name}
|
|
||||||
MATRIX_SYNAPSE_PORT=${matrix_synapse_port}
|
|
||||||
MATRIX_ELEMENT_PORT=${matrix_element_port}
|
|
||||||
MATRIX_SYNAPSE_CONTAINER_NAME=${LAB_MATRIX_SYNAPSE_CONTAINER_NAME:-matrix-synapse}
|
|
||||||
MATRIX_POSTGRES_CONTAINER_NAME=${LAB_MATRIX_POSTGRES_CONTAINER_NAME:-matrix-postgres}
|
|
||||||
MATRIX_ELEMENT_CONTAINER_NAME=${LAB_MATRIX_ELEMENT_CONTAINER_NAME:-matrix-element}
|
|
||||||
MATRIX_POSTGRES_DB=${postgres_db}
|
|
||||||
MATRIX_POSTGRES_USER=${postgres_user}
|
|
||||||
MATRIX_POSTGRES_PASSWORD=${MATRIX_POSTGRES_PASSWORD}
|
|
||||||
MATRIX_REGISTRATION_SHARED_SECRET=${MATRIX_REGISTRATION_SHARED_SECRET}
|
|
||||||
MATRIX_MACAROON_SECRET_KEY=${MATRIX_MACAROON_SECRET_KEY:-${MATRIX_REGISTRATION_SHARED_SECRET}}
|
|
||||||
MATRIX_FORM_SECRET=${MATRIX_FORM_SECRET:-${MATRIX_REGISTRATION_SHARED_SECRET}}
|
|
||||||
ENV_EOT
|
|
||||||
|
|
||||||
# Pull + render + chown + up. bootstrap.sh is the single source of truth.
|
|
||||||
# sudo the docker compose commands; data dirs are chown'd inside bootstrap.
|
|
||||||
(
|
|
||||||
cd "${install_dir}"
|
|
||||||
sudo docker compose --env-file "${install_dir}/.env" -f "${install_dir}/docker-compose.yml" pull
|
|
||||||
sudo MATRIX_ENV_FILE="${install_dir}/.env" bash "${install_dir}/bootstrap.sh"
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "Matrix stack is reachable at http://127.0.0.1:${matrix_synapse_port}/ and Element at http://127.0.0.1:${matrix_element_port}/."
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_omniroute() {
|
|
||||||
local mode="${LAB_OMNIROUTE_DEPLOY:-true}"
|
|
||||||
local install_dir="${LAB_OMNIROUTE_INSTALL_DIR:-/home/jv/homelab-omniroute}"
|
|
||||||
local install_docker="false"
|
|
||||||
local source_dir="${REPO_ROOT}/infra/omniroute"
|
|
||||||
|
|
||||||
require_debian_server "deploy-omniroute"
|
|
||||||
|
|
||||||
if disabled_value "${mode}"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -s "${source_dir}/docker-compose.yml" || ! -s "${source_dir}/bootstrap.sh" ]]; then
|
|
||||||
echo "Missing OmniRoute source files in ${source_dir}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Deploying OmniRoute LLM gateway on the Debian host at ${install_dir}..."
|
|
||||||
|
|
||||||
ensure_local_docker_compose "OMNIROUTE" "${install_docker}"
|
|
||||||
repair_local_docker_iptables
|
|
||||||
|
|
||||||
sudo mkdir -p "${install_dir}"
|
|
||||||
sudo cp "${source_dir}/docker-compose.yml" "${install_dir}/docker-compose.yml"
|
|
||||||
sudo cp "${source_dir}/bootstrap.sh" "${install_dir}/bootstrap.sh"
|
|
||||||
sudo chmod +x "${install_dir}/bootstrap.sh"
|
|
||||||
|
|
||||||
sudo tee "${install_dir}/.env" >/dev/null <<ENV_EOT
|
|
||||||
OMNIROUTE_IMAGE=${LAB_OMNIROUTE_IMAGE:-diegosouzapw/omniroute:latest}
|
|
||||||
OMNIROUTE_CONTAINER_NAME=${LAB_OMNIROUTE_CONTAINER_NAME:-omniroute}
|
|
||||||
OMNIROUTE_PORT=${LAB_OMNIROUTE_PORT:-20128}
|
|
||||||
ENV_EOT
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "${install_dir}"
|
|
||||||
sudo docker compose pull
|
|
||||||
sudo bash "${install_dir}/bootstrap.sh"
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "OmniRoute is reachable at http://127.0.0.1:${LAB_OMNIROUTE_PORT:-20128}."
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_hermes() {
|
|
||||||
local mode="${LAB_HERMES_DEPLOY:-true}"
|
|
||||||
local hermes_home="${LAB_HERMES_HOME:-/home/jv/.hermes}"
|
|
||||||
local install_dir="${LAB_HERMES_INSTALL_DIR:-/home/jv/.hermes/hermes-agent}"
|
|
||||||
local uv_bin="${LAB_HERMES_UV_BIN:-/home/jv/.hermes/bin/uv}"
|
|
||||||
local install_docker="${LAB_HERMES_INSTALL_DOCKER:-false}"
|
|
||||||
local value_name
|
|
||||||
local value
|
|
||||||
|
|
||||||
require_debian_server "deploy-hermes"
|
|
||||||
|
|
||||||
if disabled_value "${mode}"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
for value_name in hermes_home install_dir uv_bin; do
|
|
||||||
value="${!value_name}"
|
|
||||||
if [[ "${value}" == *$'\n'* ]]; then
|
|
||||||
echo "${value_name} cannot contain a newline." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Deploying Hermes agent on the Debian host at ${install_dir}..."
|
|
||||||
|
|
||||||
ensure_local_docker_compose "HERMES" "${install_docker}"
|
|
||||||
|
|
||||||
# Install uv if missing.
|
|
||||||
if [[ ! -x "${uv_bin}" ]]; then
|
|
||||||
mkdir -p "$(dirname "${uv_bin}")"
|
|
||||||
echo "Installing uv..."
|
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | bash
|
|
||||||
# uv installs to ~/.local/bin; copy/alias into HERMES bin if that path differs.
|
|
||||||
if command -v uv >/dev/null 2>&1 && [[ ! -x "${uv_bin}" ]]; then
|
|
||||||
cp "$(command -v uv)" "${uv_bin}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install Hermes via its official installer (git method, managed venv layout).
|
|
||||||
if [[ ! -x "${hermes_home}/hermes-agent/venv/bin/python" ]]; then
|
|
||||||
echo "Installing Hermes agent..."
|
|
||||||
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure the Matrix platform dependencies (E2EE) are present in the venv.
|
|
||||||
if [[ -x "${uv_bin}" && -f "${install_dir}/pyproject.toml" ]]; then
|
|
||||||
cd "${install_dir}"
|
|
||||||
"${uv_bin}" pip install 'mautrix[encryption]' -p venv/bin/python
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Secret env for the gateway (.env under HERMES_HOME, mode 600, never committed).
|
|
||||||
# Pull the hermes secrets from the SOPS-encrypted repo file; env vars override.
|
|
||||||
if ! sops_load_secrets "${REPO_ROOT}/infra/hermes.secret.yaml"; then
|
|
||||||
echo "WARNING: could not load hermes secrets; MATRIX_PASSWORD may be blank." >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
local secrets_file="${hermes_home}/.env"
|
|
||||||
sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "${hermes_home}"
|
|
||||||
if [[ -f "${secrets_file}" ]]; then
|
|
||||||
sudo cp "${secrets_file}" "${secrets_file}.bak-pre-jeannie"
|
|
||||||
fi
|
|
||||||
sudo tee "${secrets_file}" >/dev/null <<ENV_EOT
|
|
||||||
OPENAI_BASE_URL=${HERMES_OPENAI_BASE_URL:-http://localhost:${LAB_OMNIROUTE_PORT:-20128}/v1}
|
|
||||||
HERMES_CUSTOM_LOCALHOST_${LAB_OMNIROUTE_PORT:-20128}_API_KEY=${HERMES_OMNIROUTE_API_KEY:-}
|
|
||||||
MATRIX_HOMESERVER=${LAB_MATRIX_SERVER_NAME:-matrix.lab2025.duckdns.org}
|
|
||||||
MATRIX_USER_ID=${HERMES_MATRIX_USER_ID:-@hermes:matrix.lab2025.duckdns.org}
|
|
||||||
MATRIX_PASSWORD=${MATRIX_BOT_PASSWORD:-}
|
|
||||||
MATRIX_ALLOWED_USERS=${HERMES_MATRIX_ALLOWED_USERS:-@jv:matrix.lab2025.duckdns.org}
|
|
||||||
MATRIX_E2EE_MODE=required
|
|
||||||
ENV_EOT
|
|
||||||
sudo chmod 600 "${secrets_file}"
|
|
||||||
|
|
||||||
# Create the Matrix bot account (idempotent) if the homeserver is up.
|
|
||||||
if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^matrix-synapse$'; then
|
|
||||||
if [[ -n "${MATRIX_BOT_PASSWORD:-}" ]]; then
|
|
||||||
if ! docker exec matrix-synapse register_new_matrix_user -c /data/homeserver.yaml \
|
|
||||||
-u hermes -p "${MATRIX_BOT_PASSWORD}" --no-admin >/dev/null 2>&1; then
|
|
||||||
echo "Hermes bot account already exists (expected on re-run) or auto-registration skipped." >&2
|
|
||||||
else
|
|
||||||
echo "Hermes Matrix bot account ready."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "MATRIX_BOT_PASSWORD not set; skipping Hermes bot account registration (needed only on fresh install)." >&2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install/start the gateway systemd user service (Hermes manages the unit).
|
|
||||||
"${hermes_home}/hermes-agent/venv/bin/python" "${hermes_home}/hermes-agent/hermes" gateway install
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable --now hermes-gateway 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "Hermes agent deployed. Run 'hermes' to configure the model, or check gateway logs at ${hermes_home}/logs/gateway.log."
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_rpi_services() {
|
deploy_rpi_services() {
|
||||||
local mode="${LAB_RPI_SERVICES_DEPLOY:-true}"
|
local mode="${LAB_RPI_SERVICES_DEPLOY:-true}"
|
||||||
local rpi_host="${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}"
|
local rpi_host="${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}"
|
||||||
|
|
@ -5836,22 +5609,6 @@ spec = {
|
||||||
"title": "Heimdall deploy",
|
"title": "Heimdall deploy",
|
||||||
"command": [jeannie, "__up-task", "heimdall-deploy"],
|
"command": [jeannie, "__up-task", "heimdall-deploy"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "matrix-deploy",
|
|
||||||
"title": "Matrix deploy",
|
|
||||||
"command": [jeannie, "__up-task", "matrix-deploy"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "omniroute-deploy",
|
|
||||||
"title": "OmniRoute deploy",
|
|
||||||
"command": [jeannie, "__up-task", "omniroute-deploy"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hermes-deploy",
|
|
||||||
"title": "Hermes deploy",
|
|
||||||
"needs": ["omniroute-deploy", "matrix-deploy"],
|
|
||||||
"command": [jeannie, "__up-task", "hermes-deploy"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "rpi-services",
|
"id": "rpi-services",
|
||||||
"title": "RPi services",
|
"title": "RPi services",
|
||||||
|
|
@ -5899,15 +5656,6 @@ up_task() {
|
||||||
heimdall-deploy)
|
heimdall-deploy)
|
||||||
deploy_heimdall
|
deploy_heimdall
|
||||||
;;
|
;;
|
||||||
matrix-deploy)
|
|
||||||
deploy_matrix
|
|
||||||
;;
|
|
||||||
omniroute-deploy)
|
|
||||||
deploy_omniroute
|
|
||||||
;;
|
|
||||||
hermes-deploy)
|
|
||||||
deploy_hermes
|
|
||||||
;;
|
|
||||||
rpi-services)
|
rpi-services)
|
||||||
deploy_rpi_services
|
deploy_rpi_services
|
||||||
;;
|
;;
|
||||||
|
|
@ -7831,6 +7579,9 @@ ensure_sops_age_tools() {
|
||||||
if ! command -v age-keygen >/dev/null 2>&1; then
|
if ! command -v age-keygen >/dev/null 2>&1; then
|
||||||
missing_packages+=(age)
|
missing_packages+=(age)
|
||||||
fi
|
fi
|
||||||
|
if ! command -v sops >/dev/null 2>&1; then
|
||||||
|
missing_packages+=(sops)
|
||||||
|
fi
|
||||||
|
|
||||||
if ((${#missing_packages[@]} > 0)); then
|
if ((${#missing_packages[@]} > 0)); then
|
||||||
echo "Installing missing secret-management tools: ${missing_packages[*]}"
|
echo "Installing missing secret-management tools: ${missing_packages[*]}"
|
||||||
|
|
@ -7838,26 +7589,10 @@ ensure_sops_age_tools() {
|
||||||
sudo apt-get install -y --no-install-recommends "${missing_packages[@]}"
|
sudo apt-get install -y --no-install-recommends "${missing_packages[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# age must be present; it's small and apt-provided on Debian.
|
|
||||||
if ! command -v age-keygen >/dev/null 2>&1; then
|
if ! command -v age-keygen >/dev/null 2>&1; then
|
||||||
echo "age-keygen is still unavailable after package installation." >&2
|
echo "age-keygen is still unavailable after package installation." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# sops: not packaged on Debian trixie — fall back to the GitHub release binary.
|
|
||||||
if ! command -v sops >/dev/null 2>&1; then
|
|
||||||
echo "sops not found in the apt repos; installing from GitHub release..."
|
|
||||||
local sops_ver sops_url
|
|
||||||
sops_ver="$(curl -fsSL https://api.github.com/repos/getsops/sops/releases/latest | grep -oE '"tag_name":\s*"v[^"]+"' | head -1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+')"
|
|
||||||
if [[ -z "${sops_ver}" ]]; then
|
|
||||||
echo "Could not determine the latest SOPS release version." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sops_url="https://github.com/getsops/sops/releases/download/${sops_ver}/sops-v${sops_ver#v}.linux.amd64"
|
|
||||||
curl -fsSL -o "/tmp/sops-${sops_ver}" "${sops_url}" && \
|
|
||||||
sudo install -m 0755 "/tmp/sops-${sops_ver}" /usr/local/bin/sops && \
|
|
||||||
rm -f "/tmp/sops-${sops_ver}"
|
|
||||||
fi
|
|
||||||
if ! command -v sops >/dev/null 2>&1; then
|
if ! command -v sops >/dev/null 2>&1; then
|
||||||
echo "sops is still unavailable after package installation." >&2
|
echo "sops is still unavailable after package installation." >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -7874,56 +7609,6 @@ sops_age_recipient() {
|
||||||
awk -F': ' '/^# public key:/ { print $2; exit }' "${key_file}"
|
awk -F': ' '/^# public key:/ { print $2; exit }' "${key_file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Decrypt a SOPS-encrypted YAML of `KEY: value` pairs into the current env.
|
|
||||||
# Usage: sops_load_secrets <repo/path/to/file.secret.yaml>
|
|
||||||
# Requires the age key (sops_age_key_file) to decrypt; fails loudly if missing.
|
|
||||||
sops_load_secrets() {
|
|
||||||
local file="${1:?sops_load_secrets: missing file}"
|
|
||||||
local key_file
|
|
||||||
local out
|
|
||||||
|
|
||||||
if [[ ! -f "${file}" ]]; then
|
|
||||||
echo "Missing encrypted secret file: ${file} (run ./jeannie secrets-init then sops -e)." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if ! sops_available; then
|
|
||||||
echo "sops not installed. Run ./jeannie secrets-init first." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
key_file="$(sops_age_key_file)"
|
|
||||||
if [[ ! -s "${key_file}" ]]; then
|
|
||||||
echo "Missing SOPS age key at ${key_file}. Run ./jeannie secrets-init, or copy the age key to this machine." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
out="$(SOPS_AGE_KEY_FILE="${key_file}" sops --decrypt "${file}")" || {
|
|
||||||
echo "SOPS decryption failed for ${file}." >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Export every bare `KEY: value` line (ignore comments/blank).
|
|
||||||
local line key val syn="0"
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*: ]] || continue
|
|
||||||
key="${line%%:*}"
|
|
||||||
val="${line#*: }"
|
|
||||||
# Strip surrounding double quotes, if sops kept them.
|
|
||||||
val="${val%\"}"; val="${val#\"}"
|
|
||||||
export "${key}=${val}"
|
|
||||||
syn="$((syn + 1))"
|
|
||||||
done <<<"${out}"
|
|
||||||
|
|
||||||
if ((syn == 0)); then
|
|
||||||
echo "No KEY: value pairs found in decrypted ${file}." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sops_available() {
|
|
||||||
command -v sops >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets_init() {
|
secrets_init() {
|
||||||
local key_file
|
local key_file
|
||||||
local key_dir
|
local key_dir
|
||||||
|
|
@ -8423,9 +8108,6 @@ Build And Bootstrap
|
||||||
up Deploy the full homelab pipeline.
|
up Deploy the full homelab pipeline.
|
||||||
deploy-gitea Deploy the Debian-hosted Gitea container.
|
deploy-gitea Deploy the Debian-hosted Gitea container.
|
||||||
deploy-heimdall Deploy the Debian-hosted Heimdall dashboard.
|
deploy-heimdall Deploy the Debian-hosted Heimdall dashboard.
|
||||||
deploy-matrix Deploy the Matrix homeserver (Synapse + Element + Postgres).
|
|
||||||
deploy-omniroute Deploy the OmniRoute LLM routing gateway.
|
|
||||||
deploy-hermes Deploy the Hermes agent (git-install + venv + gateway service).
|
|
||||||
bootstrap-gitea-repo Ensure the Gitea repo and SSH key wiring exist.
|
bootstrap-gitea-repo Ensure the Gitea repo and SSH key wiring exist.
|
||||||
rpi-services Deploy Pi-hole, Unbound, and Uptime Kuma on RPi4.
|
rpi-services Deploy Pi-hole, Unbound, and Uptime Kuma on RPi4.
|
||||||
ollama-setup Install/configure Ollama on the Debian host.
|
ollama-setup Install/configure Ollama on the Debian host.
|
||||||
|
|
@ -8713,18 +8395,6 @@ case "${1:-}" in
|
||||||
record_change_journal "deploy-heimdall" "$@"
|
record_change_journal "deploy-heimdall" "$@"
|
||||||
deploy_heimdall
|
deploy_heimdall
|
||||||
;;
|
;;
|
||||||
deploy-matrix)
|
|
||||||
record_change_journal "deploy-matrix" "$@"
|
|
||||||
deploy_matrix
|
|
||||||
;;
|
|
||||||
deploy-omniroute)
|
|
||||||
record_change_journal "deploy-omniroute" "$@"
|
|
||||||
deploy_omniroute
|
|
||||||
;;
|
|
||||||
deploy-hermes)
|
|
||||||
record_change_journal "deploy-hermes" "$@"
|
|
||||||
deploy_hermes
|
|
||||||
;;
|
|
||||||
rpi-services)
|
rpi-services)
|
||||||
record_change_journal "rpi-services" "$@"
|
record_change_journal "rpi-services" "$@"
|
||||||
deploy_rpi_services
|
deploy_rpi_services
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue