removed customer folders and script
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
# Customer: Demo / Test Server (N100 Mini PC)
|
||||
# Hardware: Intel N100, 16GB RAM, 128GB NVMe + 1TB HDD
|
||||
# Network: Local + Cloudflare Tunnel for demo access
|
||||
|
||||
customer_id: demo-felhom
|
||||
domain: demo-felhom.eu
|
||||
hdd_path: /mnt/hdd_1
|
||||
gitea_repo: customers/demo-felhom-stacks
|
||||
hardware: n100
|
||||
notes: "Internal demo/test server for validating deployments"
|
||||
|
||||
# Apps to deploy on this node
|
||||
apps:
|
||||
- actualbudget
|
||||
- docmost
|
||||
- filebrowser
|
||||
- homebox
|
||||
- immich
|
||||
- mealie
|
||||
- paperless-ngx
|
||||
- romm
|
||||
- stirling-pdf
|
||||
- vaultwarden
|
||||
|
||||
# Per-customer overrides (optional)
|
||||
# Uncomment to pin versions or disable auto-updates
|
||||
overrides: {}
|
||||
# immich_version: "v2.5.5" # Pin Immich to specific version
|
||||
# auto_update: false # Skip ALL version updates from catalog
|
||||
|
||||
# Portainer env vars to set (reference only — actual secrets go in Portainer!)
|
||||
# These are documented here so you remember what each stack needs.
|
||||
env_vars_reference:
|
||||
docmost:
|
||||
APP_SECRET: "generate with: openssl rand -hex 32"
|
||||
DB_PASSWORD: "generate secure password"
|
||||
immich:
|
||||
DB_PASSWORD: "generate secure password"
|
||||
paperless-ngx:
|
||||
PAPERLESS_SECRET_KEY: "generate with: openssl rand -hex 32"
|
||||
DB_PASSWORD: "generate secure password"
|
||||
PAPERLESS_ADMIN_USER: "admin"
|
||||
PAPERLESS_ADMIN_PASSWORD: "set initial password"
|
||||
romm:
|
||||
DB_PASSWORD: "generate secure password"
|
||||
MYSQL_ROOT_PASSWORD: "generate secure password"
|
||||
ROMM_AUTH_SECRET_KEY: "generate with: openssl rand -hex 32"
|
||||
vaultwarden:
|
||||
ADMIN_TOKEN: "generate with: openssl rand -hex 32"
|
||||
SIGNUPS_ALLOWED: "true (set to false after account creation)"
|
||||
|
||||
# Storage layout reference
|
||||
# This shows where user data lives after render (HDD host paths):
|
||||
#
|
||||
# /mnt/hdd_1/ ← HDD root (filebrowser serves this)
|
||||
# /mnt/hdd_1/storage/immich/ ← photos & videos
|
||||
# /mnt/hdd_1/storage/paperless/consume/ ← drop documents here for OCR
|
||||
# /mnt/hdd_1/storage/paperless/media/ ← processed documents
|
||||
# /mnt/hdd_1/storage/paperless/export/ ← document exports / backup
|
||||
# /mnt/hdd_1/storage/romm/library/ ← ROM files
|
||||
# /mnt/hdd_1/storage/romm/resources/ ← cover art, metadata
|
||||
#
|
||||
# Named volumes (on NVMe, managed by Docker):
|
||||
# actualbudget_data, docmost_*, homebox_data, mealie_data,
|
||||
# immich_postgres_data, paperless_data, vaultwarden_data, etc.
|
||||
|
||||
# Backup considerations
|
||||
backup_notes:
|
||||
databases:
|
||||
- "docmost: PostgreSQL (docmost-postgres)"
|
||||
- "immich: PostgreSQL (immich-postgres)"
|
||||
- "paperless-ngx: PostgreSQL (paperless-postgres)"
|
||||
- "romm: MariaDB (romm-db)"
|
||||
hdd_paths:
|
||||
- "/mnt/hdd_1/storage/immich (photos — large, Backrest read-only mount)"
|
||||
- "/mnt/hdd_1/storage/paperless/media (documents — Backrest read-only mount)"
|
||||
- "/mnt/hdd_1/storage/romm/library (ROMs — Backrest read-only mount)"
|
||||
named_volumes:
|
||||
- "actualbudget_data"
|
||||
- "docmost_storage"
|
||||
- "homebox_data"
|
||||
- "mealie_data"
|
||||
- "stirling_data"
|
||||
- "vaultwarden_data"
|
||||
@@ -1,44 +0,0 @@
|
||||
# Customer: Pi Test Customer #1 (Raspberry Pi)
|
||||
# Hardware: Raspberry Pi 4/5, 4-8GB RAM, SD/USB + External HDD
|
||||
# Network: Local only (.local domain with self-signed cert)
|
||||
|
||||
customer_id: pi-customer-1
|
||||
domain: pi-customer-1.local
|
||||
hdd_path: /mnt/hdd_1
|
||||
gitea_repo: customers/pi-customer-1-stacks
|
||||
hardware: rpi
|
||||
notes: "Test customer on Raspberry Pi — lightweight apps only"
|
||||
|
||||
# Apps to deploy on this node (Pi-compatible only)
|
||||
apps:
|
||||
- actualbudget
|
||||
- filebrowser
|
||||
- mealie
|
||||
- stirling-pdf
|
||||
- vaultwarden
|
||||
|
||||
# Per-customer overrides
|
||||
overrides: {}
|
||||
|
||||
# Portainer env vars to set (reference only)
|
||||
env_vars_reference:
|
||||
vaultwarden:
|
||||
ADMIN_TOKEN: "generate with: openssl rand -hex 32"
|
||||
SIGNUPS_ALLOWED: "true (set to false after account creation)"
|
||||
|
||||
# Storage layout reference:
|
||||
# /mnt/hdd_1/ ← HDD root (filebrowser serves this)
|
||||
#
|
||||
# Named volumes (on SD/USB boot, managed by Docker):
|
||||
# actualbudget_data, mealie_data, stirling_data, vaultwarden_data
|
||||
|
||||
# Backup considerations
|
||||
backup_notes:
|
||||
databases: [] # No database containers — all apps use SQLite/file storage
|
||||
hdd_paths:
|
||||
- "/mnt/hdd_1 (filebrowser root — user files)"
|
||||
named_volumes:
|
||||
- "actualbudget_data"
|
||||
- "mealie_data"
|
||||
- "stirling_data"
|
||||
- "vaultwarden_data"
|
||||
@@ -1,585 +0,0 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Felhom App Catalog - Customer Repo Renderer v2.0
|
||||
#
|
||||
# Reads customer YAML configs and generates Docker Compose stacks
|
||||
# by substituting {{DOMAIN}} and {{HDD_PATH}} placeholders in templates.
|
||||
#
|
||||
# Output structure (monorepo — all customers in one Gitea repo):
|
||||
# output/
|
||||
# ├── README.md
|
||||
# ├── demo-felhom/
|
||||
# │ ├── actualbudget/docker-compose.yml
|
||||
# │ ├── immich/docker-compose.yml
|
||||
# │ └── ...
|
||||
# └── pi-customer-1/
|
||||
# ├── actualbudget/docker-compose.yml
|
||||
# └── ...
|
||||
#
|
||||
# Portainer GitOps compose path example:
|
||||
# demo-felhom/actualbudget/docker-compose.yml
|
||||
#
|
||||
# Usage:
|
||||
# ./render.sh # Render all customers
|
||||
# ./render.sh --customer demo-felhom # Render specific customer
|
||||
# ./render.sh --dry-run # Show what would be done
|
||||
# ./render.sh --push # Render + commit + push to Gitea
|
||||
# ./render.sh --output-dir /tmp/out # Custom output directory
|
||||
# ./render.sh --debug # Verbose output
|
||||
#
|
||||
# Prerequisites:
|
||||
# - git (for push mode)
|
||||
# - Access to Gitea repo (for push mode)
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Configuration
|
||||
#-------------------------------------------------------------------------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CATALOG_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
TEMPLATES_DIR="${CATALOG_DIR}/templates"
|
||||
CUSTOMERS_DIR="${CATALOG_DIR}/customers"
|
||||
DEFAULT_OUTPUT_DIR="${CATALOG_DIR}/output"
|
||||
|
||||
# Gitea monorepo settings (for --push mode)
|
||||
GITEA_REPO_URL="${GITEA_REPO_URL:-https://gitea.dooplex.hu/admin/customers-felhom.eu.git}"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Colors & Logging
|
||||
#-------------------------------------------------------------------------------
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
log_debug() { [[ "${DEBUG:-false}" == "true" ]] && echo -e "[DEBUG] $*" || true; }
|
||||
log_step() { echo -e "\n${BOLD}[STEP]${NC} $*"; }
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Simple YAML parser (no external dependencies)
|
||||
#-------------------------------------------------------------------------------
|
||||
yaml_get_value() {
|
||||
local file="$1" key="$2"
|
||||
grep -E "^${key}:" "$file" 2>/dev/null | sed "s/^${key}:[[:space:]]*//" | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" || echo ""
|
||||
}
|
||||
|
||||
yaml_get_list() {
|
||||
local file="$1" key="$2"
|
||||
local in_section=false
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^${key}: ]]; then
|
||||
in_section=true
|
||||
continue
|
||||
fi
|
||||
if $in_section; then
|
||||
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]+(.*) ]]; then
|
||||
echo "${BASH_REMATCH[1]}" | sed 's/^"//' | sed 's/"$//'
|
||||
elif [[ "$line" =~ ^[a-zA-Z_] ]]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
yaml_get_override() {
|
||||
local file="$1" key="$2"
|
||||
local in_overrides=false
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^overrides: ]]; then
|
||||
in_overrides=true
|
||||
continue
|
||||
fi
|
||||
if $in_overrides; then
|
||||
if [[ "$line" =~ ^[[:space:]]+${key}:[[:space:]]*(.*) ]]; then
|
||||
local val="${BASH_REMATCH[1]}"
|
||||
echo "$val" | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//"
|
||||
return
|
||||
fi
|
||||
if [[ "$line" =~ ^[a-zA-Z_] ]]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
echo ""
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Template rendering (one customer)
|
||||
#-------------------------------------------------------------------------------
|
||||
render_customer() {
|
||||
local customer_file="$1"
|
||||
local output_base="$2"
|
||||
|
||||
local customer_id domain hdd_path
|
||||
customer_id=$(yaml_get_value "$customer_file" "customer_id")
|
||||
domain=$(yaml_get_value "$customer_file" "domain")
|
||||
hdd_path=$(yaml_get_value "$customer_file" "hdd_path")
|
||||
|
||||
if [[ -z "$customer_id" || -z "$domain" ]]; then
|
||||
log_error "Missing customer_id or domain in: $customer_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_step "Rendering customer: ${BOLD}${customer_id}${NC} (${domain})"
|
||||
|
||||
# Check auto_update override
|
||||
local auto_update
|
||||
auto_update=$(yaml_get_override "$customer_file" "auto_update")
|
||||
if [[ "$auto_update" == "false" ]]; then
|
||||
log_warn "auto_update=false — skipping this customer"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get app list
|
||||
local apps=()
|
||||
while IFS= read -r app; do
|
||||
[[ -n "$app" ]] && apps+=("$app")
|
||||
done < <(yaml_get_list "$customer_file" "apps")
|
||||
|
||||
if [[ ${#apps[@]} -eq 0 ]]; then
|
||||
log_warn "No apps configured for $customer_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Apps: ${apps[*]}"
|
||||
log_info "Domain: $domain"
|
||||
if [[ -n "$hdd_path" ]]; then
|
||||
log_info "HDD path: $hdd_path"
|
||||
fi
|
||||
|
||||
# Output directory: output/<customer_id>/ (no -stacks suffix)
|
||||
local customer_output="${output_base}/${customer_id}"
|
||||
|
||||
# Strip .git from repo URL for display
|
||||
local repo_display="${GITEA_REPO_URL%.git}"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} Would create: ${customer_output}/"
|
||||
for app in "${apps[@]}"; do
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} ${app}/docker-compose.yml ({{DOMAIN}} → ${domain})"
|
||||
# Check if template uses HDD_PATH
|
||||
local template="${TEMPLATES_DIR}/${app}/docker-compose.yml"
|
||||
if [[ -f "$template" ]] && grep -q '{{HDD_PATH}}' "$template"; then
|
||||
if [[ -n "$hdd_path" ]]; then
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} ↳ HDD path: {{HDD_PATH}} → ${hdd_path}"
|
||||
else
|
||||
echo -e " ${YELLOW}[DRY-RUN]${NC} ↳ ⚠ Template uses {{HDD_PATH}} but hdd_path not set!"
|
||||
fi
|
||||
fi
|
||||
local version_override
|
||||
version_override=$(yaml_get_override "$customer_file" "${app}_version")
|
||||
if [[ -n "$version_override" ]]; then
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} ↳ version pinned to: ${version_override}"
|
||||
fi
|
||||
done
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} Portainer compose paths:"
|
||||
for app in "${apps[@]}"; do
|
||||
echo -e " ${CYAN}[DRY-RUN]${NC} ${customer_id}/${app}/docker-compose.yml"
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$customer_output"
|
||||
|
||||
# Generate a README for this customer's section
|
||||
cat > "${customer_output}/README.md" << EOF
|
||||
# ${customer_id}
|
||||
|
||||
**Domain:** \`${domain}\`
|
||||
**HDD Path:** \`${hdd_path:-N/A}\`
|
||||
**Generated:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
||||
|
||||
## Deployed Apps
|
||||
|
||||
| App | Portainer Compose Path |
|
||||
|-----|------------------------|
|
||||
$(for app in "${apps[@]}"; do echo "| ${app} | \`${customer_id}/${app}/docker-compose.yml\` |"; done)
|
||||
|
||||
## Portainer Stack Setup
|
||||
|
||||
For each app, create a stack in Portainer:
|
||||
1. **Stacks → Add Stack → Repository**
|
||||
2. Repository URL: \`${repo_display}\`
|
||||
3. Compose path: \`${customer_id}/<appname>/docker-compose.yml\`
|
||||
4. Add required environment variables (see comments in compose files)
|
||||
5. Enable GitOps auto-update (optional, polling interval: 5 min)
|
||||
6. Deploy
|
||||
|
||||
---
|
||||
*Auto-generated by felhom-app-catalog render.sh. Do not edit manually.*
|
||||
EOF
|
||||
|
||||
# Process each app
|
||||
local rendered_count=0
|
||||
for app in "${apps[@]}"; do
|
||||
local template="${TEMPLATES_DIR}/${app}/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$template" ]]; then
|
||||
log_error "Template not found: ${template}"
|
||||
continue
|
||||
fi
|
||||
|
||||
mkdir -p "${customer_output}/${app}"
|
||||
|
||||
# Substitute {{DOMAIN}} with customer domain
|
||||
sed "s/{{DOMAIN}}/${domain}/g" "$template" > "${customer_output}/${app}/docker-compose.yml"
|
||||
|
||||
# Substitute {{HDD_PATH}} if the template uses it
|
||||
if grep -q '{{HDD_PATH}}' "${customer_output}/${app}/docker-compose.yml"; then
|
||||
if [[ -z "$hdd_path" ]]; then
|
||||
log_error " ${app}: template uses {{HDD_PATH}} but hdd_path not set in customer config!"
|
||||
log_error " Add 'hdd_path: /mnt/hdd_1' (or similar) to ${customer_file}"
|
||||
rm "${customer_output}/${app}/docker-compose.yml"
|
||||
rmdir "${customer_output}/${app}" 2>/dev/null || true
|
||||
continue
|
||||
fi
|
||||
local clean_hdd_path="${hdd_path%/}"
|
||||
sed -i "s|{{HDD_PATH}}|${clean_hdd_path}|g" "${customer_output}/${app}/docker-compose.yml"
|
||||
fi
|
||||
|
||||
# Apply version override if configured
|
||||
local version_override
|
||||
version_override=$(yaml_get_override "$customer_file" "${app}_version")
|
||||
if [[ -n "$version_override" ]]; then
|
||||
log_info " ${app}: applying version override → ${version_override}"
|
||||
case "$app" in
|
||||
immich)
|
||||
sed -i "s|ghcr.io/immich-app/immich-server:[^ ]*|ghcr.io/immich-app/immich-server:${version_override}|g" \
|
||||
"${customer_output}/${app}/docker-compose.yml"
|
||||
sed -i "s|ghcr.io/immich-app/immich-machine-learning:[^ ]*|ghcr.io/immich-app/immich-machine-learning:${version_override}|g" \
|
||||
"${customer_output}/${app}/docker-compose.yml"
|
||||
;;
|
||||
*)
|
||||
local first_image
|
||||
first_image=$(grep -m1 "image:" "${customer_output}/${app}/docker-compose.yml" | sed 's/.*image:[[:space:]]*//')
|
||||
if [[ -n "$first_image" ]]; then
|
||||
local image_name
|
||||
image_name=$(echo "$first_image" | cut -d: -f1)
|
||||
sed -i "s|${first_image}|${image_name}:${version_override}|" \
|
||||
"${customer_output}/${app}/docker-compose.yml"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
rendered_count=$((rendered_count + 1))
|
||||
log_debug " Rendered: ${app}/docker-compose.yml"
|
||||
done
|
||||
|
||||
log_success "Rendered ${rendered_count}/${#apps[@]} apps → ${customer_output}/"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Git push — single monorepo push (all customers together)
|
||||
#-------------------------------------------------------------------------------
|
||||
push_to_gitea() {
|
||||
local output_dir="$1"
|
||||
|
||||
log_step "Pushing to Gitea: ${GITEA_REPO_URL}"
|
||||
|
||||
cd "$output_dir"
|
||||
|
||||
# Initialize git if needed, or update remote
|
||||
if [[ ! -d ".git" ]]; then
|
||||
git init -b main
|
||||
git remote add origin "$GITEA_REPO_URL"
|
||||
log_info "Initialized new git repo"
|
||||
else
|
||||
# Ensure remote URL is current
|
||||
git remote set-url origin "$GITEA_REPO_URL" 2>/dev/null || \
|
||||
git remote add origin "$GITEA_REPO_URL" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Stage all changes
|
||||
git add -A
|
||||
|
||||
# Check if there are changes to commit
|
||||
if git diff --cached --quiet 2>/dev/null; then
|
||||
log_info "No changes to push — output matches Gitea"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Show what changed
|
||||
local changed_files
|
||||
changed_files=$(git diff --cached --name-only | wc -l)
|
||||
log_info "Changed files: ${changed_files}"
|
||||
if [[ "$DEBUG" == "true" ]]; then
|
||||
git diff --cached --name-only | head -20
|
||||
fi
|
||||
|
||||
# Commit
|
||||
local commit_msg="render: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
# Add summary of what customers were rendered
|
||||
local customers_rendered
|
||||
customers_rendered=$(find "$output_dir" -maxdepth 1 -mindepth 1 -type d ! -name '.git' -printf '%f ' 2>/dev/null || true)
|
||||
if [[ -n "$customers_rendered" ]]; then
|
||||
commit_msg="${commit_msg} [${customers_rendered}]"
|
||||
fi
|
||||
|
||||
git commit -m "$commit_msg"
|
||||
|
||||
# Push
|
||||
if git push origin main 2>/dev/null; then
|
||||
log_success "Pushed to ${GITEA_REPO_URL}"
|
||||
else
|
||||
log_warn "Normal push failed — trying pull+rebase first..."
|
||||
if git pull --rebase origin main 2>/dev/null; then
|
||||
git push origin main
|
||||
log_success "Rebased and pushed to ${GITEA_REPO_URL}"
|
||||
else
|
||||
log_warn "Rebase failed — force pushing..."
|
||||
git push -u origin main --force
|
||||
log_success "Force-pushed to ${GITEA_REPO_URL}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Generate root README for the monorepo
|
||||
#-------------------------------------------------------------------------------
|
||||
generate_root_readme() {
|
||||
local output_dir="$1"
|
||||
shift
|
||||
local customer_files=("$@")
|
||||
|
||||
local repo_display="${GITEA_REPO_URL%.git}"
|
||||
|
||||
cat > "${output_dir}/README.md" << 'HEADER'
|
||||
# Felhom Customer Stacks
|
||||
|
||||
Auto-generated Docker Compose stacks for all Felhom customers.
|
||||
**Do not edit manually** — regenerate with `render.sh` from `felhom-app-catalog`.
|
||||
|
||||
HEADER
|
||||
|
||||
echo "## Customers" >> "${output_dir}/README.md"
|
||||
echo "" >> "${output_dir}/README.md"
|
||||
echo "| Customer | Domain | Hardware | Apps |" >> "${output_dir}/README.md"
|
||||
echo "|----------|--------|----------|------|" >> "${output_dir}/README.md"
|
||||
|
||||
for cf in "${customer_files[@]}"; do
|
||||
local cid cdomain chw capps
|
||||
cid=$(yaml_get_value "$cf" "customer_id")
|
||||
cdomain=$(yaml_get_value "$cf" "domain")
|
||||
chw=$(yaml_get_value "$cf" "hardware")
|
||||
# Count apps
|
||||
capps=$(yaml_get_list "$cf" "apps" | wc -l)
|
||||
echo "| [${cid}](./${cid}/) | \`${cdomain}\` | ${chw} | ${capps} apps |" >> "${output_dir}/README.md"
|
||||
done
|
||||
|
||||
cat >> "${output_dir}/README.md" << EOF
|
||||
|
||||
## Portainer GitOps Setup
|
||||
|
||||
For each app stack on a customer node:
|
||||
1. **Stacks → Add Stack → Repository**
|
||||
2. Repository URL: \`${repo_display}\`
|
||||
3. Compose path: \`<customer-id>/<app>/docker-compose.yml\`
|
||||
4. Set environment variables (secrets — see comments in compose files)
|
||||
5. Optional: Enable auto-update polling (5 min)
|
||||
|
||||
## Regenerating
|
||||
|
||||
From the \`felhom-app-catalog\` repo:
|
||||
\`\`\`bash
|
||||
./scripts/render.sh # Render locally
|
||||
./scripts/render.sh --push # Render + push to Gitea
|
||||
./scripts/render.sh --dry-run # Preview changes
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
*Last generated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*
|
||||
EOF
|
||||
|
||||
log_debug "Generated root README.md"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Argument parsing
|
||||
#-------------------------------------------------------------------------------
|
||||
DRY_RUN=false
|
||||
PUSH=false
|
||||
DEBUG=false
|
||||
TARGET_CUSTOMER=""
|
||||
OUTPUT_DIR=""
|
||||
|
||||
print_help() {
|
||||
cat << 'EOF'
|
||||
Felhom App Catalog - Customer Repo Renderer v2.0
|
||||
|
||||
Renders Docker Compose stacks from templates into a single Gitea monorepo.
|
||||
All customers share one repo, each in their own subdirectory.
|
||||
|
||||
USAGE:
|
||||
./render.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--customer ID Render only this customer (others left untouched)
|
||||
--output-dir DIR Output directory (default: ./output/)
|
||||
--push Render + commit + push to Gitea
|
||||
--dry-run Show what would be done
|
||||
--debug Verbose output
|
||||
-h, --help Show this help
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
GITEA_REPO_URL Full Gitea repo URL
|
||||
(default: https://gitea.dooplex.hu/admin/customers-felhom.eu.git)
|
||||
|
||||
EXAMPLES:
|
||||
./render.sh # Render all customers
|
||||
./render.sh --customer demo-felhom # Render one customer
|
||||
./render.sh --dry-run # Preview changes
|
||||
./render.sh --push # Render + push to Gitea
|
||||
GITEA_REPO_URL=https://gitea.example.com/org/repo.git ./render.sh --push
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--customer) TARGET_CUSTOMER="$2"; shift 2 ;;
|
||||
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
--push) PUSH=true; shift ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--debug) DEBUG=true; shift ;;
|
||||
-h|--help) print_help; exit 0 ;;
|
||||
*) log_error "Unknown option: $1"; print_help; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
OUTPUT_DIR="${OUTPUT_DIR:-${DEFAULT_OUTPUT_DIR}}"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Main
|
||||
#-------------------------------------------------------------------------------
|
||||
main() {
|
||||
echo ""
|
||||
echo -e "${BOLD}Felhom App Catalog — Renderer v2.0${NC}"
|
||||
echo -e "Templates: ${TEMPLATES_DIR}"
|
||||
echo -e "Customers: ${CUSTOMERS_DIR}"
|
||||
echo -e "Output: ${OUTPUT_DIR}"
|
||||
echo -e "Gitea repo: ${GITEA_REPO_URL}"
|
||||
echo ""
|
||||
|
||||
# Validate directories
|
||||
if [[ ! -d "$TEMPLATES_DIR" ]]; then
|
||||
log_error "Templates directory not found: $TEMPLATES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -d "$CUSTOMERS_DIR" ]]; then
|
||||
log_error "Customers directory not found: $CUSTOMERS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find customer configs to process
|
||||
local customer_files=()
|
||||
if [[ -n "$TARGET_CUSTOMER" ]]; then
|
||||
local target_file="${CUSTOMERS_DIR}/${TARGET_CUSTOMER}.yaml"
|
||||
if [[ ! -f "$target_file" ]]; then
|
||||
log_error "Customer config not found: $target_file"
|
||||
exit 1
|
||||
fi
|
||||
customer_files=("$target_file")
|
||||
else
|
||||
while IFS= read -r -d '' f; do
|
||||
customer_files+=("$f")
|
||||
done < <(find "$CUSTOMERS_DIR" -name "*.yaml" -print0 | sort -z)
|
||||
fi
|
||||
|
||||
if [[ ${#customer_files[@]} -eq 0 ]]; then
|
||||
log_error "No customer configs found in $CUSTOMERS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Found ${#customer_files[@]} customer(s) to render"
|
||||
|
||||
# Create output directory
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
fi
|
||||
|
||||
# If --push and output dir already has a .git from previous push, pull first
|
||||
if [[ "$PUSH" == "true" && "$DRY_RUN" != "true" && -d "${OUTPUT_DIR}/.git" ]]; then
|
||||
log_info "Pulling latest from Gitea before rendering..."
|
||||
cd "$OUTPUT_DIR"
|
||||
git pull --rebase origin main 2>/dev/null || true
|
||||
cd - > /dev/null
|
||||
fi
|
||||
|
||||
# Clean output directory before rendering (preserve .git only)
|
||||
# This prevents stale files from old runs accumulating
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
local stale_count=0
|
||||
for item in "${OUTPUT_DIR}"/*; do
|
||||
[[ -e "$item" ]] || continue
|
||||
rm -rf "$item"
|
||||
stale_count=$((stale_count + 1))
|
||||
done
|
||||
# Also remove hidden files except .git
|
||||
for item in "${OUTPUT_DIR}"/.[!.]*; do
|
||||
[[ -e "$item" ]] || continue
|
||||
[[ "$(basename "$item")" == ".git" ]] && continue
|
||||
rm -rf "$item"
|
||||
done
|
||||
if [[ $stale_count -gt 0 ]]; then
|
||||
log_info "Cleaned ${stale_count} item(s) from output directory"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Render each customer
|
||||
local success_count=0
|
||||
for customer_file in "${customer_files[@]}"; do
|
||||
if render_customer "$customer_file" "$OUTPUT_DIR"; then
|
||||
success_count=$((success_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate root README (only when rendering all customers)
|
||||
if [[ -z "$TARGET_CUSTOMER" && "$DRY_RUN" != "true" ]]; then
|
||||
# Get ALL customer files for the root README (even if we only rendered some)
|
||||
local all_customer_files=()
|
||||
while IFS= read -r -d '' f; do
|
||||
all_customer_files+=("$f")
|
||||
done < <(find "$CUSTOMERS_DIR" -name "*.yaml" -print0 | sort -z)
|
||||
generate_root_readme "$OUTPUT_DIR" "${all_customer_files[@]}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BOLD}${GREEN} Rendering complete: ${success_count}/${#customer_files[@]} customers${NC}"
|
||||
echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
echo ""
|
||||
echo -e "${BOLD}Output structure:${NC}"
|
||||
# Show tree-like output
|
||||
for d in "${OUTPUT_DIR}"/*/; do
|
||||
[[ -d "$d" && "$(basename "$d")" != ".git" ]] || continue
|
||||
local cid
|
||||
cid=$(basename "$d")
|
||||
local app_count
|
||||
app_count=$(find "$d" -name "docker-compose.yml" | wc -l)
|
||||
echo -e " 📁 ${BOLD}${cid}/${NC} (${app_count} apps)"
|
||||
find "$d" -name "docker-compose.yml" -printf " %P\n" | sort
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Push all at once
|
||||
if [[ "$PUSH" == "true" && "$DRY_RUN" != "true" ]]; then
|
||||
push_to_gitea "$OUTPUT_DIR"
|
||||
elif [[ "$PUSH" != "true" && "$DRY_RUN" != "true" ]]; then
|
||||
echo -e "${YELLOW}Tip:${NC} Use --push to commit and push to Gitea"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
Reference in New Issue
Block a user