feat: Remove deprecated setup scripts for first installation and provisioning
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
export SESSION_COOKIE_SECURE="1"
|
||||
export INSTANCE_TLS_MODE="production"
|
||||
export INSTANCE_PARENT_DOMAIN="${INSTANCE_PARENT_DOMAIN:-meine-domain}"
|
||||
export INSTANCE_WILDCARD_CERT_FILE="${INSTANCE_WILDCARD_CERT_FILE:-/etc/nginx/certs/wildcard.meine-domain.crt}"
|
||||
export INSTANCE_WILDCARD_KEY_FILE="${INSTANCE_WILDCARD_KEY_FILE:-/etc/nginx/certs/wildcard.meine-domain.key}"
|
||||
|
||||
if [[ ! -f "$INSTANCE_WILDCARD_CERT_FILE" || ! -f "$INSTANCE_WILDCARD_KEY_FILE" ]]; then
|
||||
echo "Error: Wildcard certificate missing for production mode"
|
||||
echo " CERT: $INSTANCE_WILDCARD_CERT_FILE"
|
||||
echo " KEY: $INSTANCE_WILDCARD_KEY_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Launching PRODUCTION stack"
|
||||
echo " SESSION_COOKIE_SECURE=$SESSION_COOKIE_SECURE"
|
||||
echo " INSTANCE_TLS_MODE=$INSTANCE_TLS_MODE"
|
||||
echo " INSTANCE_PARENT_DOMAIN=$INSTANCE_PARENT_DOMAIN"
|
||||
echo " INSTANCE_WILDCARD_CERT_FILE=$INSTANCE_WILDCARD_CERT_FILE"
|
||||
echo " INSTANCE_WILDCARD_KEY_FILE=$INSTANCE_WILDCARD_KEY_FILE"
|
||||
|
||||
docker compose build website
|
||||
docker compose up -d
|
||||
|
||||
NGINX_SCRIPT="$SCRIPT_DIR/../nginx.sh"
|
||||
if [[ -x "$NGINX_SCRIPT" ]]; then
|
||||
echo "Aktualisiere Nginx Reverse Proxy..."
|
||||
sudo "$NGINX_SCRIPT" apply || echo "Warnung: Nginx konnte nicht automatisch aktualisiert werden."
|
||||
fi
|
||||
|
||||
# Automatically sync hosts after container start
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ -x "$SCRIPT_DIR/sync_dev_hosts.sh" ]]; then
|
||||
echo "Warte auf MongoDB-Verfügbarkeit..."
|
||||
sleep 5
|
||||
"$SCRIPT_DIR/sync_dev_hosts.sh" || true
|
||||
echo "Production Hosts werden synchronisiert (stündlich)"
|
||||
|
||||
# Starte Background-Daemon für stündliche Sync
|
||||
if [[ "${PROD_HOSTS_REFRESH_INTERVAL}" != "0" ]]; then
|
||||
nohup bash -lc "while true; do '$SCRIPT_DIR/sync_dev_hosts.sh' >/dev/null 2>&1 || true; sleep ${PROD_HOSTS_REFRESH_INTERVAL:-3600}; done" \
|
||||
>"$SCRIPT_DIR/prod-hosts-sync.log" 2>&1 &
|
||||
echo " Prod-Hosts-Sync läuft im Hintergrund (Log: $SCRIPT_DIR/prod-hosts-sync.log)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Production stack is running on http://localhost:4999"
|
||||
echo "Provisioning expects wildcard certificate for all subdomains."
|
||||
@@ -1,765 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ACTION="create"
|
||||
SCHOOL_NAME=""
|
||||
SUBDOMAIN=""
|
||||
REPO_URL=""
|
||||
BASE_DIR=""
|
||||
PARENT_DOMAIN=""
|
||||
HTTPS_PORT=""
|
||||
HTTP_PORT=""
|
||||
TLS_MODE="development"
|
||||
APP_IMAGE_TAG="latest"
|
||||
LIBRARY_ENABLED="0"
|
||||
WILDCARD_CERT_FILE="/etc/nginx/certs/wildcard.meine-domain.crt"
|
||||
WILDCARD_KEY_FILE="/etc/nginx/certs/wildcard.meine-domain.key"
|
||||
NGINX_SITES_AVAILABLE="/etc/nginx/sites-available"
|
||||
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled"
|
||||
|
||||
RELEASE_BUNDLE_ASSET="inventarsystem-docker-bundle.tar.gz"
|
||||
RELEASE_IMAGE_ASSET_PREFIX="inventarsystem-image-"
|
||||
|
||||
print_kv() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
printf '%s=%s\n' "$key" "$value"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local message="$1"
|
||||
print_kv "MESSAGE" "$message"
|
||||
print_kv "ERROR" "$message"
|
||||
exit 1
|
||||
}
|
||||
|
||||
slugify() {
|
||||
local input="$1"
|
||||
input="${input,,}"
|
||||
input="$(echo "$input" | sed -E 's/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/ß/ss/g')"
|
||||
input="$(echo "$input" | sed -E 's/[^a-z0-9-]+/-/g; s/-+/-/g; s/^-+//; s/-+$//')"
|
||||
printf '%s' "${input:0:63}"
|
||||
}
|
||||
|
||||
is_valid_subdomain() {
|
||||
local value="$1"
|
||||
[[ "$value" =~ ^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])$ ]]
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
local cmd="$1"
|
||||
command -v "$cmd" >/dev/null 2>&1 || fail "Fehlender Befehl: $cmd"
|
||||
}
|
||||
|
||||
is_port_in_use() {
|
||||
local port="$1"
|
||||
|
||||
# Running inside container namespaces cannot reliably see host listeners,
|
||||
# but docker CLI can inspect host-published ports via the socket.
|
||||
if docker ps --format '{{.Ports}}' | grep -Eq "(^|[,[:space:]])((0\.0\.0\.0|\[::\]):)?${port}->"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -ltn "( sport = :$port )" 2>/dev/null | awk 'NR>1 {print $4}' | grep -q . && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
next_free_port() {
|
||||
local start_port="$1"
|
||||
local current="$start_port"
|
||||
while is_port_in_use "$current"; do
|
||||
current=$((current + 1))
|
||||
done
|
||||
printf '%s' "$current"
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
local target_dir="$1"
|
||||
local http_port="$2"
|
||||
local https_port="$3"
|
||||
local app_image_tag="$4"
|
||||
|
||||
cat > "$target_dir/.docker-build.env" <<EOF
|
||||
NUITKA_BUILD=0
|
||||
INVENTAR_HTTP_PORT=$http_port
|
||||
INVENTAR_HTTPS_PORT=$https_port
|
||||
INVENTAR_APP_IMAGE=ghcr.io/aiirondev/legendary-octo-garbanzo:$app_image_tag
|
||||
INVENTAR_MULTITENANT_ENABLED=true
|
||||
INVENTAR_SESSION_BACKEND=redis
|
||||
INVENTAR_REDIS_HOST=redis
|
||||
INVENTAR_REDIS_PORT=6379
|
||||
INVENTAR_QUERY_CACHE_ENABLED=true
|
||||
EOF
|
||||
}
|
||||
|
||||
preferred_compose_file() {
|
||||
if [ -f "docker-compose-multitenant.yml" ]; then
|
||||
printf '%s' "docker-compose-multitenant.yml"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s' "docker-compose.yml"
|
||||
}
|
||||
|
||||
repo_slug_from_url() {
|
||||
local repo_url="$1"
|
||||
local normalized=""
|
||||
|
||||
normalized="$repo_url"
|
||||
normalized="${normalized#https://github.com/}"
|
||||
normalized="${normalized#http://github.com/}"
|
||||
normalized="${normalized#git@github.com:}"
|
||||
normalized="${normalized%.git}"
|
||||
|
||||
if [[ "$normalized" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]; then
|
||||
printf '%s' "$normalized"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_release_metadata() {
|
||||
local repo_slug="$1"
|
||||
local requested_tag="$2"
|
||||
local out_file="$3"
|
||||
local api_url=""
|
||||
|
||||
if [ "$requested_tag" = "latest" ]; then
|
||||
api_url="https://api.github.com/repos/$repo_slug/releases/latest"
|
||||
else
|
||||
api_url="https://api.github.com/repos/$repo_slug/releases/tags/$requested_tag"
|
||||
fi
|
||||
|
||||
if ! curl -fsSL "$api_url" -o "$out_file"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
extract_release_info() {
|
||||
local meta_file="$1"
|
||||
|
||||
python3 - <<'PY' "$meta_file" "$RELEASE_BUNDLE_ASSET" "$RELEASE_IMAGE_ASSET_PREFIX"
|
||||
import json
|
||||
import sys
|
||||
|
||||
meta_file, bundle_asset, image_prefix = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
with open(meta_file, "r", encoding="utf-8") as fh:
|
||||
data = json.load(fh)
|
||||
|
||||
tag = (data.get("tag_name") or "").strip()
|
||||
bundle_url = ""
|
||||
image_url = ""
|
||||
|
||||
for asset in data.get("assets", []):
|
||||
name = (asset.get("name") or "").strip()
|
||||
url = (asset.get("browser_download_url") or "").strip()
|
||||
if not url:
|
||||
continue
|
||||
if name == bundle_asset:
|
||||
bundle_url = url
|
||||
if tag and name == f"{image_prefix}{tag}.tar.gz":
|
||||
image_url = url
|
||||
|
||||
print(tag)
|
||||
print(bundle_url)
|
||||
print(image_url)
|
||||
PY
|
||||
}
|
||||
|
||||
pin_compose_app_image() {
|
||||
local instance_dir="$1"
|
||||
local resolved_tag="$2"
|
||||
local compose_file=""
|
||||
|
||||
for compose_file in "$instance_dir/docker-compose.yml" "$instance_dir/docker-compose-multitenant.yml"; do
|
||||
[ -f "$compose_file" ] || continue
|
||||
|
||||
python3 - <<'PY' "$compose_file" "$resolved_tag"
|
||||
import re
|
||||
import sys
|
||||
|
||||
compose_file, tag = sys.argv[1], sys.argv[2]
|
||||
target_image = f"ghcr.io/aiirondev/legendary-octo-garbanzo:{tag}"
|
||||
|
||||
with open(compose_file, "r", encoding="utf-8") as fh:
|
||||
lines = fh.readlines()
|
||||
|
||||
out = []
|
||||
in_app = False
|
||||
in_build = False
|
||||
image_set = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.lstrip(" ")
|
||||
indent = len(line) - len(stripped)
|
||||
|
||||
if not in_app and re.match(r"^\s{2}app:\s*$", line):
|
||||
in_app = True
|
||||
image_set = True
|
||||
out.append(line)
|
||||
out.append(f" image: {target_image}\n")
|
||||
continue
|
||||
|
||||
if in_app:
|
||||
if indent == 2 and re.match(r"^[A-Za-z0-9_-]+:\s*$", stripped):
|
||||
in_app = False
|
||||
in_build = False
|
||||
|
||||
if in_app:
|
||||
if in_build:
|
||||
if indent > 4:
|
||||
continue
|
||||
in_build = False
|
||||
|
||||
if re.match(r"^\s{4}build:\s*$", line):
|
||||
in_build = True
|
||||
continue
|
||||
|
||||
if re.match(r"^\s{4}image:\s*", line):
|
||||
if image_set:
|
||||
continue
|
||||
out.append(f" image: {target_image}\n")
|
||||
image_set = True
|
||||
continue
|
||||
|
||||
out.append(line)
|
||||
|
||||
with open(compose_file, "w", encoding="utf-8") as fh:
|
||||
fh.writelines(out)
|
||||
PY
|
||||
done
|
||||
}
|
||||
|
||||
install_from_release() {
|
||||
local target_dir="$1"
|
||||
local repo_url="$2"
|
||||
local requested_tag="$3"
|
||||
local tmp_dir=""
|
||||
local repo_slug=""
|
||||
local meta_file=""
|
||||
local bundle_path=""
|
||||
local image_path=""
|
||||
local tag=""
|
||||
local bundle_url=""
|
||||
local image_url=""
|
||||
|
||||
repo_slug="$(repo_slug_from_url "$repo_url")" || fail "Repository-URL nicht unterstützbar für Release-Install: $repo_url"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
|
||||
meta_file="$tmp_dir/release.json"
|
||||
|
||||
if ! fetch_release_metadata "$repo_slug" "$requested_tag" "$meta_file"; then
|
||||
fail "Release-Metadaten konnten nicht geladen werden (Repo: $repo_slug, Tag: $requested_tag)."
|
||||
fi
|
||||
|
||||
mapfile -t release_info < <(extract_release_info "$meta_file")
|
||||
tag="${release_info[0]:-}"
|
||||
bundle_url="${release_info[1]:-}"
|
||||
image_url="${release_info[2]:-}"
|
||||
|
||||
[ -n "$tag" ] || fail "Release-Metadaten enthalten keinen gültigen Tag."
|
||||
[ -n "$bundle_url" ] || fail "Release-Asset fehlt: $RELEASE_BUNDLE_ASSET"
|
||||
[ -n "$image_url" ] || fail "Release-Image-Asset fehlt: ${RELEASE_IMAGE_ASSET_PREFIX}${tag}.tar.gz"
|
||||
|
||||
bundle_path="$tmp_dir/$RELEASE_BUNDLE_ASSET"
|
||||
image_path="$tmp_dir/${RELEASE_IMAGE_ASSET_PREFIX}${tag}.tar.gz"
|
||||
|
||||
curl -fsSL "$bundle_url" -o "$bundle_path" || fail "Release-Bundle konnte nicht geladen werden."
|
||||
curl -fsSL "$image_url" -o "$image_path" || fail "Release-Image konnte nicht geladen werden."
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
tar -xzf "$bundle_path" -C "$target_dir" || fail "Release-Bundle konnte nicht entpackt werden."
|
||||
docker load -i "$image_path" >/dev/null 2>&1 || fail "Docker-Image konnte nicht geladen werden."
|
||||
docker tag "ghcr.io/aiirondev/legendary-octo-garbanzo:$tag" "ghcr.io/aiirondev/legendary-octo-garbanzo:latest" >/dev/null 2>&1 || true
|
||||
|
||||
pin_compose_app_image "$target_dir" "$tag"
|
||||
rm -rf "$tmp_dir" >/dev/null 2>&1 || true
|
||||
print_kv "APP_IMAGE_TAG" "$tag"
|
||||
}
|
||||
|
||||
ensure_requested_app_image() {
|
||||
local app_image_tag="$1"
|
||||
local app_image="ghcr.io/aiirondev/legendary-octo-garbanzo:$app_image_tag"
|
||||
|
||||
if docker image inspect "$app_image" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! docker pull "$app_image" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
set_library_enabled() {
|
||||
local instance_dir="$1"
|
||||
local enabled="$2"
|
||||
local cfg="$instance_dir/config.json"
|
||||
[ -f "$cfg" ] || return 0
|
||||
|
||||
python3 - <<'PY' "$cfg" "$enabled"
|
||||
import json
|
||||
import sys
|
||||
|
||||
cfg_path = sys.argv[1]
|
||||
enabled = sys.argv[2] == "1"
|
||||
|
||||
with open(cfg_path, "r", encoding="utf-8") as fh:
|
||||
data = json.load(fh)
|
||||
|
||||
modules = data.get("modules")
|
||||
if not isinstance(modules, dict):
|
||||
modules = {}
|
||||
data["modules"] = modules
|
||||
|
||||
library = modules.get("library")
|
||||
if not isinstance(library, dict):
|
||||
library = {}
|
||||
modules["library"] = library
|
||||
|
||||
library["enabled"] = enabled
|
||||
|
||||
with open(cfg_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(data, fh, indent=2, ensure_ascii=False)
|
||||
fh.write("\n")
|
||||
PY
|
||||
}
|
||||
|
||||
normalize_instance_compose() {
|
||||
local compose_file=""
|
||||
|
||||
for compose_file in "docker-compose.yml" "docker-compose-multitenant.yml"; do
|
||||
[ -f "$compose_file" ] || continue
|
||||
|
||||
python3 - <<'PY' "$compose_file"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
content = path.read_text(encoding="utf-8")
|
||||
lines = content.splitlines()
|
||||
|
||||
out = []
|
||||
in_nginx = False
|
||||
in_ports = False
|
||||
ports_indent = ""
|
||||
in_app = False
|
||||
seen_app_image = False
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
indent = len(line) - len(line.lstrip(" "))
|
||||
|
||||
if stripped.startswith("nginx:") and indent == 2:
|
||||
in_nginx = True
|
||||
in_ports = False
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if indent == 2 and stripped.endswith(":"):
|
||||
in_app = stripped.startswith("app:")
|
||||
seen_app_image = False
|
||||
|
||||
if in_nginx and indent == 2 and stripped.endswith(":") and not stripped.startswith("nginx:"):
|
||||
in_nginx = False
|
||||
in_ports = False
|
||||
|
||||
if stripped.startswith("container_name:") and indent == 4:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if in_app and indent == 4 and stripped.startswith("image:"):
|
||||
if seen_app_image:
|
||||
i += 1
|
||||
continue
|
||||
seen_app_image = True
|
||||
|
||||
if in_nginx and stripped.startswith("ports:") and indent == 4:
|
||||
out.append(line)
|
||||
out.append(" - \"${INVENTAR_HTTP_PORT:-80}:80\"")
|
||||
out.append(" - \"${INVENTAR_HTTPS_PORT:-443}:443\"")
|
||||
i += 1
|
||||
in_ports = True
|
||||
ports_indent = " "
|
||||
while i < len(lines):
|
||||
nxt = lines[i]
|
||||
nxt_stripped = nxt.strip()
|
||||
nxt_indent = len(nxt) - len(nxt.lstrip(" "))
|
||||
if nxt_stripped.startswith("-") and nxt_indent >= len(ports_indent):
|
||||
i += 1
|
||||
continue
|
||||
break
|
||||
continue
|
||||
|
||||
out.append(line)
|
||||
i += 1
|
||||
|
||||
normalized = "\n".join(out)
|
||||
if content.endswith("\n"):
|
||||
normalized += "\n"
|
||||
|
||||
if normalized != content:
|
||||
path.write_text(normalized, encoding="utf-8")
|
||||
PY
|
||||
done
|
||||
}
|
||||
|
||||
run_instance_start() {
|
||||
local start_output
|
||||
local retry_output
|
||||
local update_output
|
||||
local compose_file
|
||||
local has_redis=0
|
||||
|
||||
compose_file="$(preferred_compose_file)"
|
||||
if [ -f "$compose_file" ] && grep -Eq '^\s{2}redis:\s*$' "$compose_file"; then
|
||||
has_redis=1
|
||||
fi
|
||||
|
||||
stack_is_running() {
|
||||
local running_services
|
||||
running_services="$(docker compose -f "$compose_file" --env-file .docker-build.env ps --status running --services 2>/dev/null || true)"
|
||||
printf '%s\n' "$running_services" | grep -Fxq app || return 1
|
||||
printf '%s\n' "$running_services" | grep -Fxq nginx || return 1
|
||||
printf '%s\n' "$running_services" | grep -Fxq mongodb || return 1
|
||||
if [ "$has_redis" = "1" ]; then
|
||||
printf '%s\n' "$running_services" | grep -Fxq redis || return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "$compose_file" = "docker-compose-multitenant.yml" ]; then
|
||||
if start_output="$(docker compose -f "$compose_file" --env-file .docker-build.env up -d --remove-orphans 2>&1)"; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (Inventarsystem Multiinstancing aktiv)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if stack_is_running; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (Multiinstancing läuft, Healthcheck im Startskript war nicht erreichbar)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if printf '%s' "$start_output" | grep -qi "local app image not found"; then
|
||||
if [ ! -x ./update.sh ]; then
|
||||
fail "Multiinstancing-Start fehlgeschlagen und update.sh fehlt. Letzte Meldung: $(printf '%s' "$start_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
if ! update_output="$(bash ./update.sh 2>&1)"; then
|
||||
if stack_is_running; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (update.sh meldete Healthcheck-Fehler, Multiinstancing-Dienste laufen)."
|
||||
return 0
|
||||
fi
|
||||
fail "update.sh fehlgeschlagen: $(printf '%s' "$update_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
if retry_output="$(docker compose -f "$compose_file" --env-file .docker-build.env up -d --remove-orphans 2>&1)"; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (Multiinstancing-Image automatisch per update.sh geladen)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
fail "Multiinstancing-Start nach update.sh fehlgeschlagen: $(printf '%s' "$retry_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
fail "Multiinstancing-Start fehlgeschlagen: $(printf '%s' "$start_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
if start_output="$(INVENTAR_SETUP_CRON=0 INVENTAR_HTTP_PORT="$HTTP_PORT" INVENTAR_HTTPS_PORT="$HTTPS_PORT" bash ./start.sh --no-cron 2>&1)"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if stack_is_running; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (Healthcheck im Startskript war nicht erreichbar, Dienste laufen)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if printf '%s' "$start_output" | grep -qi "local app image not found"; then
|
||||
if [ ! -x ./update.sh ]; then
|
||||
fail "start.sh fehlgeschlagen und update.sh fehlt. Letzte Meldung: $(printf '%s' "$start_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
if ! update_output="$(bash ./update.sh 2>&1)"; then
|
||||
if stack_is_running; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (update.sh meldete Healthcheck-Fehler, Dienste laufen)."
|
||||
return 0
|
||||
fi
|
||||
fail "update.sh fehlgeschlagen: $(printf '%s' "$update_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
if retry_output="$(INVENTAR_SETUP_CRON=0 INVENTAR_HTTP_PORT="$HTTP_PORT" INVENTAR_HTTPS_PORT="$HTTPS_PORT" bash ./start.sh --no-cron 2>&1)"; then
|
||||
print_kv "MESSAGE" "Instanz gestartet (Image automatisch per update.sh geladen)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
fail "Start nach update.sh fehlgeschlagen: $(printf '%s' "$retry_output" | tail -n1)"
|
||||
fi
|
||||
|
||||
fail "start.sh fehlgeschlagen: $(printf '%s' "$start_output" | tail -n1)"
|
||||
}
|
||||
|
||||
setup_or_update_repo() {
|
||||
local target_dir="$1"
|
||||
local repo_url="$2"
|
||||
local app_image_tag="$3"
|
||||
|
||||
if [ -f "$target_dir/start.sh" ]; then
|
||||
# Existing installation: keep update flow inside Inventarsystem tooling.
|
||||
if [ "$app_image_tag" = "latest" ] && [ -x "$target_dir/update.sh" ]; then
|
||||
(cd "$target_dir" && bash ./update.sh >/dev/null 2>&1 || true)
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -d "$target_dir" ] && [ "$(find "$target_dir" -mindepth 1 -maxdepth 1 | wc -l)" -gt 0 ]; then
|
||||
fail "Zielverzeichnis ist nicht leer: $target_dir"
|
||||
fi
|
||||
|
||||
install_from_release "$target_dir" "$repo_url" "$app_image_tag"
|
||||
}
|
||||
|
||||
write_nginx_site() {
|
||||
local subdomain="$1"
|
||||
local full_domain="$2"
|
||||
local https_port="$3"
|
||||
local cert_file=""
|
||||
local key_file=""
|
||||
local cert_dir="/etc/nginx/certs"
|
||||
local site_name="inventarsystem-${subdomain}.conf"
|
||||
local avail_file="$NGINX_SITES_AVAILABLE/$site_name"
|
||||
local enabled_file="$NGINX_SITES_ENABLED/$site_name"
|
||||
|
||||
if [ ! -d "$NGINX_SITES_AVAILABLE" ] || [ ! -d "$NGINX_SITES_ENABLED" ]; then
|
||||
print_kv "NGINX_STATUS" "skipped"
|
||||
print_kv "MESSAGE" "Instanz gestartet. Nginx-Verzeichnisse nicht gefunden, Reverse-Proxy manuell anlegen."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -w "$NGINX_SITES_AVAILABLE" ] || [ ! -w "$NGINX_SITES_ENABLED" ]; then
|
||||
print_kv "NGINX_STATUS" "manual_required"
|
||||
print_kv "MESSAGE" "Instanz gestartet. Keine Schreibrechte auf Nginx-Konfiguration, bitte als root/sudo ausführen."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$TLS_MODE" = "production" ]; then
|
||||
cert_file="$WILDCARD_CERT_FILE"
|
||||
key_file="$WILDCARD_KEY_FILE"
|
||||
|
||||
if [ ! -f "$cert_file" ] || [ ! -f "$key_file" ]; then
|
||||
print_kv "NGINX_STATUS" "error"
|
||||
print_kv "MESSAGE" "Produktivmodus aktiv, aber Wildcard-Zertifikat fehlt: $cert_file / $key_file"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
cert_file="$cert_dir/$full_domain.crt"
|
||||
key_file="$cert_dir/$full_domain.key"
|
||||
|
||||
if [ ! -f "$cert_file" ] || [ ! -f "$key_file" ]; then
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
print_kv "NGINX_STATUS" "manual_required"
|
||||
print_kv "MESSAGE" "Instanz gestartet. openssl fehlt für Development-Zertifikat, Zertifikat bitte manuell anlegen."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -d "$cert_dir" ] || [ ! -w "$cert_dir" ]; then
|
||||
print_kv "NGINX_STATUS" "manual_required"
|
||||
print_kv "MESSAGE" "Instanz gestartet. Keine Schreibrechte auf $cert_dir für Development-Zertifikat."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! openssl req -x509 -nodes -newkey rsa:2048 -sha256 \
|
||||
-days 825 \
|
||||
-keyout "$key_file" \
|
||||
-out "$cert_file" \
|
||||
-subj "/CN=$full_domain" \
|
||||
-addext "subjectAltName=DNS:$full_domain" >/dev/null 2>&1; then
|
||||
openssl req -x509 -nodes -newkey rsa:2048 -sha256 \
|
||||
-days 825 \
|
||||
-keyout "$key_file" \
|
||||
-out "$cert_file" \
|
||||
-subj "/CN=$full_domain" >/dev/null 2>&1 || {
|
||||
print_kv "NGINX_STATUS" "manual_required"
|
||||
print_kv "MESSAGE" "Instanz gestartet. Development-Zertifikat konnte nicht erstellt werden."
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
|
||||
chmod 600 "$key_file" 2>/dev/null || true
|
||||
chmod 644 "$cert_file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
cat > "$avail_file" <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name $full_domain;
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name $full_domain;
|
||||
|
||||
ssl_certificate $cert_file;
|
||||
ssl_certificate_key $key_file;
|
||||
|
||||
location / {
|
||||
proxy_pass https://127.0.0.1:$https_port;
|
||||
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 \$scheme;
|
||||
proxy_ssl_verify off;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
ln -sfn "$avail_file" "$enabled_file"
|
||||
|
||||
if ! command -v nginx >/dev/null 2>&1; then
|
||||
print_kv "NGINX_STATUS" "manual_required"
|
||||
print_kv "MESSAGE" "Instanz gestartet. Nginx-Site wurde geschrieben, Reload bitte auf dem Host ausführen."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if nginx -t >/dev/null 2>&1; then
|
||||
nginx -s reload >/dev/null 2>&1 || true
|
||||
print_kv "NGINX_STATUS" "ok"
|
||||
else
|
||||
print_kv "NGINX_STATUS" "error"
|
||||
print_kv "MESSAGE" "Instanz gestartet, aber nginx -t ist fehlgeschlagen."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--action)
|
||||
ACTION="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--school-name)
|
||||
SCHOOL_NAME="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--subdomain)
|
||||
SUBDOMAIN="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--repo)
|
||||
REPO_URL="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--base-dir)
|
||||
BASE_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--domain)
|
||||
PARENT_DOMAIN="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--https-port)
|
||||
HTTPS_PORT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--http-port)
|
||||
HTTP_PORT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--tls-mode)
|
||||
TLS_MODE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--wildcard-cert-file)
|
||||
WILDCARD_CERT_FILE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--wildcard-key-file)
|
||||
WILDCARD_KEY_FILE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--app-image-tag)
|
||||
APP_IMAGE_TAG="${2:-latest}"
|
||||
shift 2
|
||||
;;
|
||||
--library-enabled)
|
||||
LIBRARY_ENABLED="${2:-0}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
fail "Unbekannter Parameter: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$REPO_URL" ] || fail "Repository-URL fehlt"
|
||||
[ -n "$BASE_DIR" ] || fail "Basisverzeichnis fehlt"
|
||||
[ -n "$PARENT_DOMAIN" ] || fail "Parent-Domain fehlt"
|
||||
|
||||
if [ -z "$SUBDOMAIN" ]; then
|
||||
SUBDOMAIN="$(slugify "$SCHOOL_NAME")"
|
||||
else
|
||||
SUBDOMAIN="$(slugify "$SUBDOMAIN")"
|
||||
fi
|
||||
|
||||
is_valid_subdomain "$SUBDOMAIN" || fail "Ungueltige Subdomain: $SUBDOMAIN"
|
||||
|
||||
INSTANCE_DIR="$BASE_DIR/$SUBDOMAIN"
|
||||
FULL_DOMAIN="$SUBDOMAIN.$PARENT_DOMAIN"
|
||||
|
||||
require_cmd docker
|
||||
require_cmd bash
|
||||
require_cmd curl
|
||||
require_cmd tar
|
||||
require_cmd python3
|
||||
|
||||
mkdir -p "$BASE_DIR"
|
||||
|
||||
if [ -z "$HTTPS_PORT" ]; then
|
||||
HTTPS_PORT="$(next_free_port 15443)"
|
||||
fi
|
||||
|
||||
if [ -z "$HTTP_PORT" ]; then
|
||||
HTTP_PORT="$(next_free_port 15080)"
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "create" ] || [ "$ACTION" = "start" ]; then
|
||||
setup_or_update_repo "$INSTANCE_DIR" "$REPO_URL" "$APP_IMAGE_TAG"
|
||||
|
||||
[ -f "$INSTANCE_DIR/start.sh" ] || fail "start.sh im Zielrepository nicht gefunden: $INSTANCE_DIR"
|
||||
|
||||
if ! ensure_requested_app_image "$APP_IMAGE_TAG"; then
|
||||
fail "App-Image konnte nicht geladen werden: ghcr.io/aiirondev/legendary-octo-garbanzo:$APP_IMAGE_TAG"
|
||||
fi
|
||||
|
||||
write_env_file "$INSTANCE_DIR" "$HTTP_PORT" "$HTTPS_PORT" "$APP_IMAGE_TAG"
|
||||
set_library_enabled "$INSTANCE_DIR" "$LIBRARY_ENABLED"
|
||||
|
||||
cd "$INSTANCE_DIR"
|
||||
normalize_instance_compose
|
||||
run_instance_start
|
||||
|
||||
write_nginx_site "$SUBDOMAIN" "$FULL_DOMAIN" "$HTTPS_PORT"
|
||||
|
||||
print_kv "SUBDOMAIN" "$SUBDOMAIN"
|
||||
print_kv "DOMAIN" "$FULL_DOMAIN"
|
||||
print_kv "HTTPS_PORT" "$HTTPS_PORT"
|
||||
print_kv "HTTP_PORT" "$HTTP_PORT"
|
||||
print_kv "APP_IMAGE_TAG" "$APP_IMAGE_TAG"
|
||||
print_kv "LIBRARY_ENABLED" "$LIBRARY_ENABLED"
|
||||
print_kv "INSTANCE_DIR" "$INSTANCE_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fail "Nicht unterstuetzte Action: $ACTION"
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
RUN_START=1
|
||||
RUN_NGINX=1
|
||||
RUN_HOSTS_SYNC=1
|
||||
RUN_AUTOSTART=1
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: sudo ./setup-first-install.sh [options]
|
||||
|
||||
Options:
|
||||
--skip-start Skip stack start via gitea.sh
|
||||
--skip-nginx Skip nginx apply via nginx.sh
|
||||
--skip-hosts-sync Skip installation of invario-hosts-sync.service
|
||||
--skip-autostart Skip installation of invario-stack-autostart.service
|
||||
-h, --help Show this help
|
||||
|
||||
Default behavior:
|
||||
1) Start stack
|
||||
2) Apply nginx
|
||||
3) Install hosts-sync service
|
||||
4) Install stack-autostart service
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-start)
|
||||
RUN_START=0
|
||||
shift
|
||||
;;
|
||||
--skip-nginx)
|
||||
RUN_NGINX=0
|
||||
shift
|
||||
;;
|
||||
--skip-hosts-sync)
|
||||
RUN_HOSTS_SYNC=0
|
||||
shift
|
||||
;;
|
||||
--skip-autostart)
|
||||
RUN_AUTOSTART=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "This script must be run as root. Use: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_script() {
|
||||
local file="$1"
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Missing required file: $file"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -x "$file" ]]; then
|
||||
chmod +x "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
require_script "$SCRIPT_DIR/gitea.sh"
|
||||
require_script "$SCRIPT_DIR/nginx.sh"
|
||||
require_script "$SCRIPT_DIR/setup-hosts-sync.sh"
|
||||
require_script "$SCRIPT_DIR/setup-stack-autostart.sh"
|
||||
|
||||
echo "Running first installation setup in: $SCRIPT_DIR"
|
||||
|
||||
action_or_skip() {
|
||||
local flag="$1"
|
||||
local title="$2"
|
||||
shift 2
|
||||
|
||||
if [[ "$flag" -eq 1 ]]; then
|
||||
echo "[RUN ] $title"
|
||||
"$@"
|
||||
else
|
||||
echo "[SKIP] $title"
|
||||
fi
|
||||
}
|
||||
|
||||
action_or_skip "$RUN_START" "Start stack (gitea.sh start)" "$SCRIPT_DIR/gitea.sh" start
|
||||
action_or_skip "$RUN_NGINX" "Apply nginx (nginx.sh apply)" "$SCRIPT_DIR/nginx.sh" apply
|
||||
action_or_skip "$RUN_HOSTS_SYNC" "Install hosts-sync service" "$SCRIPT_DIR/setup-hosts-sync.sh"
|
||||
action_or_skip "$RUN_AUTOSTART" "Install stack-autostart service" "$SCRIPT_DIR/setup-stack-autostart.sh"
|
||||
|
||||
echo
|
||||
echo "First installation setup completed."
|
||||
echo "Service status checks:"
|
||||
echo " systemctl status invario-hosts-sync"
|
||||
echo " systemctl status invario-stack-autostart"
|
||||
echo "Stack status:"
|
||||
echo " $SCRIPT_DIR/gitea.sh status"
|
||||
Reference in New Issue
Block a user