Initial PlayBull release with Trilogy Hub integration.
Includes homelab deploy tooling, image position/zoom manifest persistence, and full trading/casino stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
21
deploy/caddy-trilogyhub.snippet
Normal file
21
deploy/caddy-trilogyhub.snippet
Normal file
@@ -0,0 +1,21 @@
|
||||
# Trilogy Hub — Caddy-Block (Homelab: progress-caddy)
|
||||
# Datei auf dem Server: /home/gizzler/pgs2/finalprogressharing/Caddyfile
|
||||
# Nach Aenderung: docker exec progress-caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
|
||||
trilogyhub.de, www.trilogyhub.de {
|
||||
encode gzip zstd
|
||||
@ws {
|
||||
header Connection *Upgrade*
|
||||
header Upgrade websocket
|
||||
}
|
||||
reverse_proxy 192.168.178.49:3080
|
||||
}
|
||||
|
||||
halmc.duckdns.org {
|
||||
encode gzip zstd
|
||||
@ws {
|
||||
header Connection *Upgrade*
|
||||
header Upgrade websocket
|
||||
}
|
||||
reverse_proxy 192.168.178.49:3080
|
||||
}
|
||||
74
deploy/cloudflare-ddns-update.sh
Normal file
74
deploy/cloudflare-ddns-update.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cloudflare Dynamic DNS — aktualisiert A/AAAA wenn sich die Fritz!Box-WAN-IP aendert.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="${SCRIPT_DIR}/env.homelab"
|
||||
STATE_FILE="${SCRIPT_DIR}/.cf-ddns-last-ip"
|
||||
|
||||
[[ -f "${ENV_FILE}" ]] || { echo "cloudflare-ddns: ${ENV_FILE} fehlt" >&2; exit 1; }
|
||||
# shellcheck source=/dev/null
|
||||
source <(sed 's/\r$//' "${ENV_FILE}")
|
||||
|
||||
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN fehlt}"
|
||||
: "${CLOUDFLARE_ZONE_ID:?CLOUDFLARE_ZONE_ID fehlt}"
|
||||
|
||||
APP_DOMAIN="${APP_DOMAIN:-trilogyhub.de}"
|
||||
DDNS_RECORD="${DDNS_RECORD_NAME:-home.${APP_DOMAIN}}"
|
||||
DDNS_PROXIED="${DDNS_PROXIED:-true}"
|
||||
|
||||
IPV4="$(curl -4 -fsS --max-time 15 https://api.ipify.org 2>/dev/null || true)"
|
||||
IPV6="$(curl -6 -fsS --max-time 15 https://api64.ipify.org 2>/dev/null || true)"
|
||||
[[ -n "${IPV4}" || -n "${IPV6}" ]] || { echo "cloudflare-ddns: keine IP" >&2; exit 1; }
|
||||
|
||||
CURRENT="${IPV4}|${IPV6}"
|
||||
LAST="$(cat "${STATE_FILE}" 2>/dev/null || true)"
|
||||
if [[ "${CURRENT}" == "${LAST}" ]]; then
|
||||
echo "$(date -Is) cloudflare-ddns ${DDNS_RECORD}: unveraendert v4=${IPV4:-none}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export CF_API="https://api.cloudflare.com/client/v4"
|
||||
export CF_ZONE="${CLOUDFLARE_ZONE_ID}"
|
||||
export CF_TOKEN="${CLOUDFLARE_API_TOKEN}"
|
||||
export CF_PROXIED="${DDNS_PROXIED}"
|
||||
|
||||
upsert() {
|
||||
local rtype="$1" ip="$2"
|
||||
[[ -n "${ip}" ]] || return 0
|
||||
python3 - "${DDNS_RECORD}" "${rtype}" "${ip}" <<'PY'
|
||||
import json, os, sys, urllib.request
|
||||
name, rtype, ip = sys.argv[1:4]
|
||||
api = os.environ["CF_API"]
|
||||
zone = os.environ["CF_ZONE"]
|
||||
token = os.environ["CF_TOKEN"]
|
||||
proxied = os.environ.get("CF_PROXIED", "true").lower() == "true"
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
list_url = f"{api}/zones/{zone}/dns_records?type={rtype}&name={name}"
|
||||
with urllib.request.urlopen(urllib.request.Request(list_url, headers=headers), timeout=20) as r:
|
||||
data = json.load(r)
|
||||
if not data.get("success"):
|
||||
raise SystemExit(f"list failed: {data}")
|
||||
body = json.dumps({"type": rtype, "name": name, "content": ip, "ttl": 120, "proxied": proxied}).encode()
|
||||
recs = data.get("result", [])
|
||||
if recs:
|
||||
url = f"{api}/zones/{zone}/dns_records/{recs[0]['id']}"
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method="PUT")
|
||||
act = "aktualisiert"
|
||||
else:
|
||||
url = f"{api}/zones/{zone}/dns_records"
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
|
||||
act = "angelegt"
|
||||
with urllib.request.urlopen(req, timeout=20) as r:
|
||||
out = json.load(r)
|
||||
if not out.get("success"):
|
||||
raise SystemExit(f"write failed: {out}")
|
||||
print(f"{rtype} {name} -> {ip} ({act})")
|
||||
PY
|
||||
}
|
||||
|
||||
upsert A "${IPV4}"
|
||||
upsert AAAA "${IPV6}"
|
||||
echo "${CURRENT}" > "${STATE_FILE}"
|
||||
chmod 600 "${STATE_FILE}" 2>/dev/null || true
|
||||
echo "$(date -Is) cloudflare-ddns fertig"
|
||||
26
deploy/cloudflared/config.yml
Normal file
26
deploy/cloudflared/config.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Cloudflare Tunnel — HTTPS am Edge, kein Port-Forwarding noetig.
|
||||
# Token wird per systemd EnvironmentFile gesetzt (deploy/env.homelab).
|
||||
#
|
||||
# Zero Trust Dashboard:
|
||||
# Networks → Tunnels → Create → Cloudflared
|
||||
# Public Hostname: trilogyhub.de → http://127.0.0.1:3080
|
||||
# Tunnel-Token in CLOUDFLARE_TUNNEL_TOKEN eintragen
|
||||
#
|
||||
# Hinweis: Hostnames im Zero-Trust-Dashboard eintragen (Token-Install).
|
||||
# Cloudflare-DDNS meldet WAN-IP fuer home.trilogyhub.de (optional).
|
||||
|
||||
tunnel: playbull
|
||||
credentials-file: /etc/cloudflared/playbull.json
|
||||
|
||||
ingress:
|
||||
- hostname: trilogyhub.de
|
||||
service: http://127.0.0.1:3080
|
||||
originRequest:
|
||||
noTLSVerify: true
|
||||
httpHostHeader: trilogyhub.de
|
||||
- hostname: www.trilogyhub.de
|
||||
service: http://127.0.0.1:3080
|
||||
originRequest:
|
||||
noTLSVerify: true
|
||||
httpHostHeader: www.trilogyhub.de
|
||||
- service: http_status:404
|
||||
114
deploy/connect-trilogyhub.sh
Normal file
114
deploy/connect-trilogyhub.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# trilogyhub.de mit Homelab-Website verbinden (Nginx + optional Certbot + Tunnel-Hinweis)
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="${1:-trilogyhub.de}"
|
||||
PORT="${APP_PORT:-3080}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
ENV_FILE="${APP_DIR}/deploy/env.homelab"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "sudo bash deploy/connect-trilogyhub.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ -f "${ENV_FILE}" ]] || { echo "${ENV_FILE} fehlt" >&2; exit 1; }
|
||||
sed -i 's/\r$//' "${ENV_FILE}" 2>/dev/null || true
|
||||
# shellcheck source=/dev/null
|
||||
source "${ENV_FILE}"
|
||||
|
||||
PORT="${APP_PORT:-3080}"
|
||||
DOMAIN="${APP_DOMAIN:-${DOMAIN}}"
|
||||
|
||||
echo "==> Verbinde ${DOMAIN} → 127.0.0.1:${PORT}"
|
||||
|
||||
# env aktualisieren
|
||||
grep -q '^APP_DOMAIN=' "${ENV_FILE}" && sed -i "s|^APP_DOMAIN=.*|APP_DOMAIN=${DOMAIN}|" "${ENV_FILE}" || echo "APP_DOMAIN=${DOMAIN}" >> "${ENV_FILE}"
|
||||
|
||||
# LAN (Fritz-Netz)
|
||||
sed -i 's/\r$//' "${APP_DIR}/deploy/nginx/bestewebsite-lan.conf" 2>/dev/null || true
|
||||
cp "${APP_DIR}/deploy/nginx/trilogyhub.conf" /etc/nginx/sites-available/trilogyhub 2>/dev/null || \
|
||||
cp "${APP_DIR}/deploy/nginx/bestewebsite.conf" /etc/nginx/sites-available/trilogyhub
|
||||
|
||||
# HTTP-only zuerst (fuer Certbot)
|
||||
cat > /etc/nginx/sites-available/trilogyhub <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${DOMAIN} www.${DOMAIN};
|
||||
|
||||
client_max_body_size 30m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:${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 \$scheme;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
if [[ -f "${APP_DIR}/deploy/nginx/bestewebsite-lan.conf" ]]; then
|
||||
cp "${APP_DIR}/deploy/nginx/bestewebsite-lan.conf" /etc/nginx/sites-available/bestewebsite-lan
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite-lan /etc/nginx/sites-enabled/00-bestewebsite-lan
|
||||
fi
|
||||
|
||||
ln -sf /etc/nginx/sites-available/trilogyhub /etc/nginx/sites-enabled/trilogyhub
|
||||
# alte DuckDNS-Sites nicht loeschen — nur Redirect optional
|
||||
if [[ -f /etc/nginx/sites-available/bestewebsite ]]; then
|
||||
cat > /etc/nginx/sites-available/bestewebsite-legacy <<'LEG'
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name halmc.duckdns.org pimmelschwanz.duckdns.org;
|
||||
return 301 https://trilogyhub.de$request_uri;
|
||||
}
|
||||
LEG
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite-legacy /etc/nginx/sites-enabled/bestewebsite-legacy 2>/dev/null || true
|
||||
fi
|
||||
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
# App muss laufen
|
||||
if ! curl -sf "http://127.0.0.1:${PORT}/" >/dev/null; then
|
||||
echo "WARNUNG: nichts auf Port ${PORT} — playbull.service starten" >&2
|
||||
systemctl restart playbull 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Certbot wenn DNS auf diesen Server zeigt
|
||||
if command -v certbot >/dev/null 2>&1; then
|
||||
echo "==> Versuche Let's Encrypt fuer ${DOMAIN}..."
|
||||
if certbot --nginx -d "${DOMAIN}" -d "www.${DOMAIN}" \
|
||||
--non-interactive --agree-tos --redirect \
|
||||
-m "admin@${DOMAIN}" 2>/dev/null; then
|
||||
echo " SSL OK"
|
||||
else
|
||||
echo " SSL noch nicht moeglich (DNS/Cloudflare noch nicht aktiv) — HTTP auf Port 80 ist vorbereitet"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cloudflare Tunnel
|
||||
if [[ -n "${CLOUDFLARE_TUNNEL_TOKEN:-}" ]]; then
|
||||
echo "==> Cloudflare Tunnel"
|
||||
cloudflared service uninstall 2>/dev/null || true
|
||||
cloudflared service install "${CLOUDFLARE_TUNNEL_TOKEN}"
|
||||
systemctl enable cloudflared
|
||||
systemctl restart cloudflared
|
||||
echo " Im Cloudflare-Dashboard Public Hostname setzen:"
|
||||
echo " ${DOMAIN} + www.${DOMAIN} → http://127.0.0.1:${PORT}"
|
||||
fi
|
||||
|
||||
nginx -t && systemctl reload nginx
|
||||
|
||||
echo ""
|
||||
echo "=== Fertig ==="
|
||||
echo "App: http://127.0.0.1:${PORT}/"
|
||||
echo "LAN: http://192.168.178.49/"
|
||||
echo "Domain: http://${DOMAIN}/ (sobald DNS auf Homelab/Cloudflare zeigt)"
|
||||
echo "HTTPS: https://${DOMAIN}/ (nach Cloudflare Active + Tunnel ODER Certbot)"
|
||||
15
deploy/duckdns-update.bat
Normal file
15
deploy/duckdns-update.bat
Normal file
@@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
REM DuckDNS IP-Updater fuer Windows (optional, falls kein Linux-Homelab-Timer)
|
||||
setlocal
|
||||
cd /d "%~dp0.."
|
||||
if not exist "deploy\env.homelab" (
|
||||
echo deploy\env.homelab fehlt
|
||||
exit /b 1
|
||||
)
|
||||
for /f "usebackq tokens=1,2 delims==" %%a in ("deploy\env.homelab") do (
|
||||
if "%%a"=="DUCKDNS_TOKEN" set DUCKDNS_TOKEN=%%b
|
||||
if "%%a"=="DUCKDNS_DOMAIN" set DUCKDNS_DOMAIN=%%b
|
||||
)
|
||||
for /f "delims=" %%i in ('powershell -NoProfile -Command "(Invoke-WebRequest -Uri 'https://api.ipify.org' -UseBasicParsing).Content"') do set IPV4=%%i
|
||||
for /f "tokens=1 delims=." %%s in ("%DUCKDNS_DOMAIN%") do set SUB=%%s
|
||||
powershell -NoProfile -Command "$r=(Invoke-WebRequest -Uri 'https://www.duckdns.org/update?domains=%SUB%&token=%DUCKDNS_TOKEN%&verbose=true&ip=%IPV4%' -UseBasicParsing).Content; Write-Host (Get-Date -Format o) 'duckdns' '%SUB%:' $r 'ip=' '%IPV4%'"
|
||||
32
deploy/duckdns-update.sh
Normal file
32
deploy/duckdns-update.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# DuckDNS IP-Updater — holt oeffentliche IPv4/IPv6 und meldet sie an DuckDNS.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${SCRIPT_DIR}/env.homelab"
|
||||
|
||||
: "${DUCKDNS_TOKEN:?DUCKDNS_TOKEN fehlt in deploy/env.homelab}"
|
||||
: "${DUCKDNS_DOMAIN:?DUCKDNS_DOMAIN fehlt in deploy/env.homelab}"
|
||||
|
||||
SUBDOMAIN="${DUCKDNS_DOMAIN%%.*}"
|
||||
|
||||
IPV4="$(curl -4 -fsS --max-time 15 https://api.ipify.org 2>/dev/null || true)"
|
||||
IPV6="$(curl -6 -fsS --max-time 15 https://api64.ipify.org 2>/dev/null || true)"
|
||||
|
||||
if [[ -z "${IPV4}" && -z "${IPV6}" ]]; then
|
||||
echo "duckdns-update: keine oeffentliche IP ermittelbar" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://www.duckdns.org/update?domains=${SUBDOMAIN}&token=${DUCKDNS_TOKEN}&verbose=true"
|
||||
[[ -n "${IPV4}" ]] && URL+="&ip=${IPV4}"
|
||||
[[ -n "${IPV6}" ]] && URL+="&ipv6=${IPV6}"
|
||||
|
||||
RESP="$(curl -fsS --max-time 20 "${URL}")"
|
||||
echo "$(date -Is) duckdns ${SUBDOMAIN}: ${RESP} (v4=${IPV4:-none} v6=${IPV6:-none})"
|
||||
|
||||
case "${RESP}" in
|
||||
OK*) exit 0 ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
33
deploy/env.homelab.example
Normal file
33
deploy/env.homelab.example
Normal file
@@ -0,0 +1,33 @@
|
||||
# Homelab-Konfiguration (deploy/env.homelab auf dem Server — NICHT committen)
|
||||
HOMELAB_SSH=gizzler@192.168.178.49
|
||||
|
||||
# === Hauptdomain (Strato, DNS bei Cloudflare) ===
|
||||
APP_DOMAIN=trilogyhub.de
|
||||
|
||||
# Cloudflare Tunnel (Zero Trust → Networks → Tunnels → dein Tunnel → Install command / Token)
|
||||
# Public Hostnames im Dashboard eintragen:
|
||||
# trilogyhub.de → http://127.0.0.1:3080
|
||||
# www.trilogyhub.de → http://127.0.0.1:3080
|
||||
CLOUDFLARE_TUNNEL_TOKEN=
|
||||
|
||||
# Cloudflare Dynamic DNS — meldet Fritz!Box-WAN-IP an Cloudflare (alle 5 Min)
|
||||
# API Token: My Profile → API Tokens → Create → Zone DNS Edit (nur trilogyhub.de)
|
||||
# Zone ID: Cloudflare Dashboard → trilogyhub.de → Overview → rechts „Zone ID“
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ZONE_ID=
|
||||
# Welcher DNS-Name bekommt die aktuelle IP? (home.* empfohlen wenn Hauptdomain per Tunnel laeuft)
|
||||
DDNS_RECORD_NAME=home.trilogyhub.de
|
||||
DDNS_PROXIED=true
|
||||
|
||||
# Legacy DuckDNS (optional, kann leer bleiben)
|
||||
DUCKDNS_TOKEN=
|
||||
DUCKDNS_DOMAIN=
|
||||
|
||||
# PlayBull
|
||||
APP_PORT=3080
|
||||
APP_DIR=/home/gizzler/bestewebsite/bestewebsite
|
||||
APP_USER=gizzler
|
||||
|
||||
# App-Secrets
|
||||
JWT_SECRET=bitte-aendern
|
||||
FINNHUB_API_KEY=
|
||||
16
deploy/finish-on-server.sh
Normal file
16
deploy/finish-on-server.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Auf dem Homelab ausfuehren (nach push-homelab.ps1), wenn der Installer per SSH abbrach:
|
||||
# cd ~/bestewebsite/bestewebsite && bash deploy/finish-on-server.sh
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$DIR"
|
||||
chmod +x deploy/install-homelab.sh deploy/duckdns-update.sh 2>/dev/null || true
|
||||
|
||||
echo "==> PlayBull Installer (braucht sudo-Passwort auf dem Server)"
|
||||
echo " Ziel: ${APP_DIR} (playbull.service)"
|
||||
sudo bash deploy/install-homelab.sh
|
||||
|
||||
echo ""
|
||||
echo "Pruefen:"
|
||||
systemctl status playbull --no-pager || true
|
||||
135
deploy/fix-domain.sh
Normal file
135
deploy/fix-domain.sh
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# Domain-Migration auf dem Homelab (DuckDNS + Nginx + Certbot)
|
||||
# Usage: sudo bash deploy/fix-domain.sh [neue-domain]
|
||||
set -euo pipefail
|
||||
|
||||
NEW_DOMAIN="${1:-trilogyhub.de}"
|
||||
OLD_DOMAIN="${2:-halmc.duckdns.org}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Bitte als root: sudo bash deploy/fix-domain.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENV_FILE="${APP_DIR}/deploy/env.homelab"
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
echo "deploy/env.homelab fehlt" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Domain: ${OLD_DOMAIN} -> ${NEW_DOMAIN}"
|
||||
|
||||
# env.homelab aktualisieren (Token behalten, CRLF entfernen)
|
||||
sed -i 's/\r$//' "${ENV_FILE}"
|
||||
if grep -q '^APP_DOMAIN=' "${ENV_FILE}"; then
|
||||
sed -i "s|^APP_DOMAIN=.*|APP_DOMAIN=${NEW_DOMAIN}|" "${ENV_FILE}"
|
||||
else
|
||||
echo "APP_DOMAIN=${NEW_DOMAIN}" >> "${ENV_FILE}"
|
||||
fi
|
||||
if grep -q '^DUCKDNS_DOMAIN=' "${ENV_FILE}"; then
|
||||
sed -i "s|^DUCKDNS_DOMAIN=.*|DUCKDNS_DOMAIN=${NEW_DOMAIN}|" "${ENV_FILE}"
|
||||
else
|
||||
echo "DUCKDNS_DOMAIN=${NEW_DOMAIN}" >> "${ENV_FILE}"
|
||||
fi
|
||||
chmod 600 "${ENV_FILE}"
|
||||
|
||||
# DuckDNS IP aktualisieren
|
||||
if grep -q '^DUCKDNS_TOKEN=.\+' "${ENV_FILE}" 2>/dev/null; then
|
||||
echo "==> DuckDNS Update"
|
||||
bash "${APP_DIR}/deploy/duckdns-update.sh" || echo " DuckDNS-Update fehlgeschlagen (manuell pruefen)"
|
||||
else
|
||||
echo " DuckDNS-Token leer — Update uebersprungen"
|
||||
fi
|
||||
|
||||
# Nginx LAN
|
||||
echo "==> Nginx LAN"
|
||||
sed -i 's/\r$//' "${APP_DIR}/deploy/nginx/bestewebsite-lan.conf" "${APP_DIR}/deploy/nginx/bestewebsite.conf" 2>/dev/null || true
|
||||
cp "${APP_DIR}/deploy/nginx/bestewebsite-lan.conf" /etc/nginx/sites-available/bestewebsite-lan
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite-lan /etc/nginx/sites-enabled/00-bestewebsite-lan
|
||||
|
||||
# Nginx öffentlich — zuerst HTTP-only für Certbot
|
||||
echo "==> Nginx HTTP (Certbot-Vorbereitung)"
|
||||
cat > /etc/nginx/sites-available/bestewebsite <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${NEW_DOMAIN};
|
||||
|
||||
client_max_body_size 30m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3080;
|
||||
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 \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite /etc/nginx/sites-enabled/bestewebsite
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
# Let's Encrypt Zertifikat
|
||||
echo "==> Certbot fuer ${NEW_DOMAIN}"
|
||||
CERTBOT_EMAIL="admin@${NEW_DOMAIN}"
|
||||
if certbot certificates 2>/dev/null | grep -q "${NEW_DOMAIN}"; then
|
||||
certbot renew --nginx --quiet || true
|
||||
else
|
||||
certbot --nginx -d "${NEW_DOMAIN}" --non-interactive --agree-tos --redirect -m "${CERTBOT_EMAIL}" || {
|
||||
echo "Certbot fehlgeschlagen — pruefe ob ${NEW_DOMAIN} auf diese Maschine zeigt (Port 80)." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Finales Nginx-Template — Certbot-Zertifikat einbinden (kein erneutes certbot install)
|
||||
echo "==> Nginx HTTPS finalisieren"
|
||||
cp "${APP_DIR}/deploy/nginx/bestewebsite.conf" /etc/nginx/sites-available/bestewebsite
|
||||
if [[ ! -f "/etc/letsencrypt/live/${NEW_DOMAIN}/fullchain.pem" ]]; then
|
||||
echo "Zertifikat fehlt fuer ${NEW_DOMAIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite /etc/nginx/sites-enabled/bestewebsite
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
# Alte Domain optional auf neue weiterleiten (falls noch DNS zeigt)
|
||||
if [[ "${OLD_DOMAIN}" != "${NEW_DOMAIN}" ]]; then
|
||||
cat > /etc/nginx/sites-available/bestewebsite-legacy <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${OLD_DOMAIN};
|
||||
return 301 https://${NEW_DOMAIN}\$request_uri;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name ${OLD_DOMAIN};
|
||||
ssl_certificate /etc/letsencrypt/live/${OLD_DOMAIN}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${OLD_DOMAIN}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
return 301 https://${NEW_DOMAIN}\$request_uri;
|
||||
}
|
||||
EOF
|
||||
if [[ -f "/etc/letsencrypt/live/${OLD_DOMAIN}/fullchain.pem" ]]; then
|
||||
ln -sf /etc/nginx/sites-available/bestewebsite-legacy /etc/nginx/sites-enabled/bestewebsite-legacy
|
||||
nginx -t && systemctl reload nginx
|
||||
echo " Redirect ${OLD_DOMAIN} -> ${NEW_DOMAIN} aktiv"
|
||||
else
|
||||
rm -f /etc/nginx/sites-enabled/bestewebsite-legacy
|
||||
fi
|
||||
fi
|
||||
|
||||
systemctl restart playbull || true
|
||||
|
||||
echo ""
|
||||
echo "=== Fertig ==="
|
||||
echo "HTTPS: https://${NEW_DOMAIN}"
|
||||
echo "LAN: http://192.168.178.49"
|
||||
echo "Test: curl -sfI https://${NEW_DOMAIN}/ | head -3"
|
||||
33
deploy/homelab-exec.ps1
Normal file
33
deploy/homelab-exec.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fuehrt einen Befehl auf dem Homelab aus (ohne interaktives SSH-Passwort).
|
||||
|
||||
.USAGE
|
||||
.\deploy\homelab-exec.ps1 "systemctl status playbull --no-pager"
|
||||
.\deploy\homelab-exec.ps1 -Sudo "bash deploy/install-homelab.sh"
|
||||
#>
|
||||
param(
|
||||
[Parameter(Position = 0, Mandatory = $true)]
|
||||
[string]$Command,
|
||||
|
||||
[switch]$Sudo,
|
||||
[string]$WorkDir = "",
|
||||
[int]$TimeoutSeconds = 120
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
. "$PSScriptRoot/homelab-lib.ps1"
|
||||
|
||||
$config = Get-HomelabConfig
|
||||
$result = Invoke-HomelabRemote -Config $config -Command $Command -Sudo:$Sudo -WorkDir $WorkDir -TimeoutSeconds $TimeoutSeconds
|
||||
|
||||
if ($result.Output) {
|
||||
$result.Output | ForEach-Object { Write-Output $_ }
|
||||
}
|
||||
if ($result.Error) {
|
||||
$result.Error | ForEach-Object { [Console]::Error.WriteLine($_) }
|
||||
}
|
||||
if ($result.ExitStatus -ne 0) {
|
||||
exit $result.ExitStatus
|
||||
}
|
||||
13
deploy/homelab-git-check.ps1
Normal file
13
deploy/homelab-git-check.ps1
Normal file
@@ -0,0 +1,13 @@
|
||||
#Requires -Version 5.1
|
||||
$ErrorActionPreference = "Stop"
|
||||
. "$PSScriptRoot/homelab-lib.ps1"
|
||||
$config = Get-HomelabConfig
|
||||
$cmds = @(
|
||||
"git status -sb",
|
||||
"git diff --stat | head -30",
|
||||
"curl -s http://127.0.0.1:3080/api/images | head -c 200"
|
||||
)
|
||||
foreach ($c in $cmds) {
|
||||
Write-Host "`n>> $c" -ForegroundColor Cyan
|
||||
& "$PSScriptRoot/homelab-exec.ps1" $c
|
||||
}
|
||||
130
deploy/homelab-lib.ps1
Normal file
130
deploy/homelab-lib.ps1
Normal file
@@ -0,0 +1,130 @@
|
||||
# Gemeinsame Homelab-Hilfen (credentials + Posh-SSH)
|
||||
function Read-HomelabKeyValueFile {
|
||||
param([string]$Path)
|
||||
$map = @{}
|
||||
if (-not (Test-Path $Path)) { return $map }
|
||||
Get-Content $Path | ForEach-Object {
|
||||
$line = $_.Trim()
|
||||
if ($line -eq "" -or $line.StartsWith("#")) { return }
|
||||
if ($line -match '^([^=]+)=(.*)$') {
|
||||
$map[$Matches[1].Trim()] = $Matches[2].Trim()
|
||||
}
|
||||
}
|
||||
return $map
|
||||
}
|
||||
|
||||
function Ensure-PoshSshModule {
|
||||
if (-not (Get-Module -ListAvailable -Name Posh-SSH)) {
|
||||
Install-Module -Name Posh-SSH -Scope CurrentUser -Force -AllowClobber
|
||||
}
|
||||
Import-Module Posh-SSH -ErrorAction Stop
|
||||
}
|
||||
|
||||
function Get-HomelabConfig {
|
||||
$deployDir = $PSScriptRoot
|
||||
$credFile = Join-Path $deployDir "homelab.credentials"
|
||||
if (-not (Test-Path $credFile)) {
|
||||
throw "deploy/homelab.credentials fehlt."
|
||||
}
|
||||
|
||||
$cred = Read-HomelabKeyValueFile $credFile
|
||||
$envFile = Read-HomelabKeyValueFile (Join-Path $deployDir "env.homelab")
|
||||
|
||||
$hostName = $cred["HOMELAB_HOST"]
|
||||
$user = $cred["HOMELAB_USER"]
|
||||
$password = $cred["HOMELAB_PASSWORD"]
|
||||
$sudoPassword = if ($cred["HOMELAB_SUDO_PASSWORD"]) { $cred["HOMELAB_SUDO_PASSWORD"] } else { $password }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($hostName)) { $hostName = "192.168.178.49" }
|
||||
if ([string]::IsNullOrWhiteSpace($user)) { $user = "gizzler" }
|
||||
if ([string]::IsNullOrWhiteSpace($password)) {
|
||||
throw "HOMELAB_PASSWORD in deploy/homelab.credentials ist leer."
|
||||
}
|
||||
|
||||
$appDir = if ($cred["HOMELAB_APP_DIR"]) { $cred["HOMELAB_APP_DIR"] }
|
||||
elseif ($envFile["APP_DIR"]) { $envFile["APP_DIR"] }
|
||||
else { "/home/gizzler/bestewebsite/bestewebsite" }
|
||||
|
||||
$sec = ConvertTo-SecureString $password -AsPlainText -Force
|
||||
$psCred = New-Object System.Management.Automation.PSCredential ($user, $sec)
|
||||
|
||||
return [PSCustomObject]@{
|
||||
HostName = $hostName
|
||||
User = $user
|
||||
Password = $password
|
||||
SudoPassword = $sudoPassword
|
||||
AppDir = $appDir
|
||||
Credential = $psCred
|
||||
SshTarget = "${user}@${hostName}"
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-HomelabIPv4 {
|
||||
param([string]$HostName)
|
||||
if ($HostName -match '^\d+\.\d+\.\d+\.\d+$') { return $HostName }
|
||||
try {
|
||||
$v4 = [System.Net.Dns]::GetHostAddresses($HostName) |
|
||||
Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } |
|
||||
Select-Object -First 1
|
||||
if ($v4) { return $v4.ToString() }
|
||||
} catch { }
|
||||
return $HostName
|
||||
}
|
||||
|
||||
function New-HomelabSession {
|
||||
param($Config)
|
||||
Ensure-PoshSshModule
|
||||
$target = Resolve-HomelabIPv4 $Config.HostName
|
||||
$session = New-SSHSession -ComputerName $target -Credential $Config.Credential -AcceptKey -ConnectionTimeout 30
|
||||
if (-not $session) { throw "SSH-Verbindung zu $($Config.User)@${target} fehlgeschlagen." }
|
||||
return $session
|
||||
}
|
||||
|
||||
function Invoke-HomelabRemote {
|
||||
param(
|
||||
$Config,
|
||||
[string]$Command,
|
||||
[switch]$Sudo,
|
||||
[string]$WorkDir = "",
|
||||
[int]$TimeoutSeconds = 120
|
||||
)
|
||||
if ([string]::IsNullOrWhiteSpace($WorkDir)) { $WorkDir = $Config.AppDir }
|
||||
|
||||
$remoteCmd = $Command
|
||||
if ($Sudo) {
|
||||
$inner = if ($WorkDir) { "cd '$WorkDir' && $Command" } else { $Command }
|
||||
$escaped = $inner.Replace("'", "'\''")
|
||||
$remoteCmd = "echo '$($Config.SudoPassword)' | sudo -S bash -lc '$escaped'"
|
||||
} elseif ($WorkDir) {
|
||||
$remoteCmd = "cd '$WorkDir' && $Command"
|
||||
}
|
||||
|
||||
$session = New-HomelabSession $Config
|
||||
try {
|
||||
$result = Invoke-SSHCommand -SessionId $session.SessionId -Command $remoteCmd -TimeOut $TimeoutSeconds
|
||||
return $result
|
||||
}
|
||||
finally {
|
||||
Remove-SSHSession -SessionId $session.SessionId | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Send-HomelabPath {
|
||||
param(
|
||||
$Config,
|
||||
[string]$LocalPath,
|
||||
[string]$RemoteDir
|
||||
)
|
||||
Ensure-PoshSshModule
|
||||
$target = Resolve-HomelabIPv4 $Config.HostName
|
||||
$remoteDir = ($RemoteDir.TrimEnd('/') + '/')
|
||||
|
||||
$sftp = New-SFTPSession -ComputerName $target -Credential $Config.Credential -AcceptKey
|
||||
if (-not $sftp) { throw "SFTP-Verbindung zu ${target} fehlgeschlagen." }
|
||||
try {
|
||||
Set-SFTPItem -SessionId $sftp.SessionId -Path $LocalPath -Destination $remoteDir -Force
|
||||
}
|
||||
finally {
|
||||
Remove-SFTPSession -SessionId $sftp.SessionId | Out-Null
|
||||
}
|
||||
}
|
||||
11
deploy/homelab-safe-pull.sh
Normal file
11
deploy/homelab-safe-pull.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Auf dem Homelab: Trilogy pullen, PlayBull-Overlay sichern.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
git stash push -u -m "playbull-vor-trilogy-pull-$(date +%Y%m%d-%H%M)" -- \
|
||||
. ':(exclude)node_modules' ':(exclude)node_modules/**' \
|
||||
':(exclude)data' ':(exclude)data/**' \
|
||||
':(exclude)playbull-deploy.tgz' ':(exclude)deploy/env.homelab' \
|
||||
':(exclude)deploy/homelab.credentials' ':(exclude).env' || true
|
||||
git pull origin main
|
||||
git status -sb
|
||||
13
deploy/homelab.credentials.example
Normal file
13
deploy/homelab.credentials.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Lokale Homelab-Zugangsdaten (NICHT committen)
|
||||
# Kopieren nach: deploy/homelab.credentials
|
||||
# Ziel auf dem Server: ~/bestewebsite/bestewebsite
|
||||
|
||||
HOMELAB_HOST=mikeshomelab
|
||||
HOMELAB_USER=gizzler
|
||||
HOMELAB_PASSWORD=dein-ssh-passwort
|
||||
|
||||
# Optional — falls sudo ein anderes Passwort braucht (sonst = HOMELAB_PASSWORD)
|
||||
# HOMELAB_SUDO_PASSWORD=
|
||||
|
||||
# Optional — Arbeitsverzeichnis auf dem Server (Standard aus env.homelab)
|
||||
# HOMELAB_APP_DIR=/home/gizzler/bestewebsite/bestewebsite
|
||||
212
deploy/install-homelab.sh
Normal file
212
deploy/install-homelab.sh
Normal file
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env bash
|
||||
# PlayBull Homelab-Installer — Ziel: ~/bestewebsite/bestewebsite
|
||||
# DuckDNS + playbull.service + Cloudflare Tunnel (cloudflared service install)
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Bitte als root ausfuehren: sudo bash deploy/install-homelab.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
if [[ ! -f "${SCRIPT_DIR}/env.homelab" ]]; then
|
||||
cp "${SCRIPT_DIR}/env.homelab.example" "${SCRIPT_DIR}/env.homelab"
|
||||
echo "deploy/env.homelab erstellt — Token eintragen und erneut starten." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source <(sed 's/\r$//' "${SCRIPT_DIR}/env.homelab")
|
||||
|
||||
APP_PORT="${APP_PORT:-3080}"
|
||||
APP_DIR="${APP_DIR:-/home/gizzler/bestewebsite/bestewebsite}"
|
||||
APP_DIR="${APP_DIR//$'\r'/}"
|
||||
APP_USER="${APP_USER:-gizzler}"
|
||||
APP_USER="${APP_USER//$'\r'/}"
|
||||
DUCKDNS_DOMAIN="${DUCKDNS_DOMAIN//$'\r'/}"
|
||||
DUCKDNS_TOKEN="${DUCKDNS_TOKEN//$'\r'/}"
|
||||
APP_DOMAIN="${APP_DOMAIN:-trilogyhub.de}"
|
||||
APP_DOMAIN="${APP_DOMAIN//$'\r'/}"
|
||||
CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN//$'\r'/}"
|
||||
CLOUDFLARE_ZONE_ID="${CLOUDFLARE_ZONE_ID//$'\r'/}"
|
||||
CLOUDFLARE_TUNNEL_TOKEN="${CLOUDFLARE_TUNNEL_TOKEN//$'\r'/}"
|
||||
JWT_SECRET="${JWT_SECRET//$'\r'/}"
|
||||
FINNHUB_API_KEY="${FINNHUB_API_KEY//$'\r'/}"
|
||||
|
||||
echo "==> PlayBull / Trilogy Hub Homelab Setup"
|
||||
echo " Domain: ${APP_DOMAIN}"
|
||||
echo " Port: ${APP_PORT}"
|
||||
echo " Pfad: ${APP_DIR}"
|
||||
echo ""
|
||||
|
||||
install_unit() {
|
||||
local src="$1" dest="$2"
|
||||
sed -e "s|__APP_DIR__|${APP_DIR}|g" \
|
||||
-e "s|__APP_USER__|${APP_USER}|g" \
|
||||
"${src}" > "${dest}"
|
||||
}
|
||||
|
||||
need_pkg() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
if [[ "${SKIP_APT:-0}" != "1" ]] && command -v apt-get >/dev/null 2>&1; then
|
||||
echo "==> Paketlisten aktualisieren..."
|
||||
apt-get update -qq
|
||||
PKGS=(curl ca-certificates rsync build-essential libvips-dev)
|
||||
MISSING=()
|
||||
for p in "${PKGS[@]}"; do need_pkg "$p" || MISSING+=("$p"); done
|
||||
if ((${#MISSING[@]})); then
|
||||
apt-get install -y "${MISSING[@]}"
|
||||
fi
|
||||
elif [[ "${SKIP_APT:-0}" != "1" ]] && command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y curl ca-certificates rsync gcc-c++ make vips-devel
|
||||
fi
|
||||
|
||||
if ! command -v cloudflared >/dev/null 2>&1; then
|
||||
echo "==> cloudflared installieren"
|
||||
ARCH="$(uname -m)"
|
||||
case "${ARCH}" in
|
||||
x86_64) CF_ARCH=amd64 ;;
|
||||
aarch64|arm64) CF_ARCH=arm64 ;;
|
||||
*) echo "Unsupported arch: ${ARCH}" >&2; exit 1 ;;
|
||||
esac
|
||||
curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${CF_ARCH}" \
|
||||
-o /usr/local/bin/cloudflared
|
||||
chmod +x /usr/local/bin/cloudflared
|
||||
ln -sf /usr/local/bin/cloudflared /usr/bin/cloudflared
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null 2>&1 || [[ "$(node -p "process.versions.node.split('.')[0]")" -lt 24 ]]; then
|
||||
echo "==> Node.js 24 installieren (NodeSource)"
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
||||
apt-get install -y nodejs
|
||||
else
|
||||
dnf install -y nodejs
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "${APP_DIR}" "${APP_DIR}/data" "${APP_DIR}/public/uploads"
|
||||
if [[ "${REPO_ROOT}" != "${APP_DIR}" ]]; then
|
||||
echo "==> Dateien nach ${APP_DIR} synchronisieren"
|
||||
rsync -a --delete \
|
||||
--exclude node_modules \
|
||||
--exclude data \
|
||||
--exclude .git \
|
||||
--exclude server.js \
|
||||
--exclude server.py \
|
||||
--exclude start.bat \
|
||||
"${REPO_ROOT}/" "${APP_DIR}/"
|
||||
fi
|
||||
|
||||
chown -R "${APP_USER}:${APP_USER}" "${APP_DIR}" 2>/dev/null || true
|
||||
chmod +x "${APP_DIR}/deploy/duckdns-update.sh" \
|
||||
"${APP_DIR}/deploy/cloudflare-ddns-update.sh" \
|
||||
"${APP_DIR}/deploy/finish-on-server.sh" \
|
||||
"${APP_DIR}/deploy/sync-user.sh" 2>/dev/null || true
|
||||
|
||||
# ---- .env (nur ergaenzen, nicht ueberschreiben) ----
|
||||
ENV_FILE="${APP_DIR}/.env"
|
||||
touch "${ENV_FILE}"
|
||||
grep -q '^PORT=' "${ENV_FILE}" 2>/dev/null \
|
||||
&& sed -i "s/^PORT=.*/PORT=${APP_PORT}/" "${ENV_FILE}" \
|
||||
|| echo "PORT=${APP_PORT}" >> "${ENV_FILE}"
|
||||
grep -q '^TRUST_PROXY=' "${ENV_FILE}" 2>/dev/null || echo "TRUST_PROXY=1" >> "${ENV_FILE}"
|
||||
grep -q '^START_BALANCE=' "${ENV_FILE}" 2>/dev/null || echo "START_BALANCE=10000" >> "${ENV_FILE}"
|
||||
grep -q '^JWT_SECRET=' "${ENV_FILE}" 2>/dev/null || { [[ -n "${JWT_SECRET:-}" ]] && echo "JWT_SECRET=${JWT_SECRET}" >> "${ENV_FILE}"; }
|
||||
grep -q '^FINNHUB_API_KEY=' "${ENV_FILE}" 2>/dev/null || { [[ -n "${FINNHUB_API_KEY:-}" ]] && echo "FINNHUB_API_KEY=${FINNHUB_API_KEY}" >> "${ENV_FILE}"; }
|
||||
|
||||
if [[ "${APP_USER}" == "root" ]]; then
|
||||
bash -c "cd '${APP_DIR}' && npm install --omit=dev"
|
||||
else
|
||||
sudo -u "${APP_USER}" bash -c "cd '${APP_DIR}' && npm install --omit=dev"
|
||||
fi
|
||||
|
||||
# ---- Alten bestewebsite-Service abloesen ----
|
||||
if systemctl is-active --quiet bestewebsite.service 2>/dev/null; then
|
||||
echo "==> bestewebsite.service stoppen (Migration zu playbull)"
|
||||
systemctl stop bestewebsite.service
|
||||
fi
|
||||
systemctl disable bestewebsite.service 2>/dev/null || true
|
||||
|
||||
# ---- Cloudflare Tunnel (wie bestewebsite: cloudflared service install) ----
|
||||
if [[ -n "${CLOUDFLARE_TUNNEL_TOKEN:-}" ]]; then
|
||||
echo "==> Cloudflare Tunnel"
|
||||
systemctl stop cloudflared-playbull.service 2>/dev/null || true
|
||||
systemctl disable cloudflared-playbull.service 2>/dev/null || true
|
||||
cloudflared service uninstall 2>/dev/null || true
|
||||
cloudflared service install "${CLOUDFLARE_TUNNEL_TOKEN}"
|
||||
systemctl enable cloudflared
|
||||
systemctl restart cloudflared
|
||||
echo " cloudflared gestartet."
|
||||
else
|
||||
echo ""
|
||||
echo ">>> CLOUDFLARE_TUNNEL_TOKEN fehlt in deploy/env.homelab"
|
||||
echo " Zero Trust → Tunnel → Public Hostname: ${APP_DOMAIN} -> http://127.0.0.1:${APP_PORT}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ---- Cloudflare Dynamic DNS (ersetzt DuckDNS) ----
|
||||
echo "==> Cloudflare DDNS Timer"
|
||||
install_unit "${APP_DIR}/deploy/systemd/cloudflare-ddns-update.service" /etc/systemd/system/cloudflare-ddns-update.service
|
||||
cp "${APP_DIR}/deploy/systemd/cloudflare-ddns-update.timer" /etc/systemd/system/
|
||||
chmod 600 "${APP_DIR}/deploy/env.homelab"
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN:-}" && -n "${CLOUDFLARE_ZONE_ID:-}" ]]; then
|
||||
"${APP_DIR}/deploy/cloudflare-ddns-update.sh" || echo " Cloudflare-DDNS fehlgeschlagen (API/Zone pruefen)"
|
||||
else
|
||||
echo " Cloudflare-DDNS uebersprungen (CLOUDFLARE_API_TOKEN oder ZONE_ID leer)"
|
||||
fi
|
||||
|
||||
# ---- DuckDNS (legacy, optional) ----
|
||||
if [[ -n "${DUCKDNS_TOKEN:-}" ]]; then
|
||||
echo "==> DuckDNS (legacy)"
|
||||
install_unit "${APP_DIR}/deploy/systemd/duckdns-update.service" /etc/systemd/system/duckdns-update.service
|
||||
cp "${APP_DIR}/deploy/systemd/duckdns-update.timer" /etc/systemd/system/
|
||||
"${APP_DIR}/deploy/duckdns-update.sh" || true
|
||||
fi
|
||||
|
||||
# ---- playbull systemd ----
|
||||
echo "==> playbull.service"
|
||||
install_unit "${APP_DIR}/deploy/systemd/playbull.service" /etc/systemd/system/playbull.service
|
||||
|
||||
systemctl daemon-reload
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN:-}" && -n "${CLOUDFLARE_ZONE_ID:-}" ]]; then
|
||||
systemctl enable --now cloudflare-ddns-update.timer
|
||||
systemctl start cloudflare-ddns-update.service || true
|
||||
else
|
||||
systemctl disable cloudflare-ddns-update.timer 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "${DUCKDNS_TOKEN:-}" ]]; then
|
||||
systemctl enable --now duckdns-update.timer
|
||||
systemctl start duckdns-update.service || true
|
||||
else
|
||||
systemctl disable duckdns-update.timer 2>/dev/null || true
|
||||
fi
|
||||
systemctl enable --now playbull.service
|
||||
systemctl restart playbull.service
|
||||
|
||||
# ---- Optional Nginx ----
|
||||
if [[ "${USE_NGINX_VHOST:-0}" == "1" ]]; then
|
||||
echo "==> Nginx VHost (optional)"
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get install -y nginx certbot python3-certbot-nginx
|
||||
fi
|
||||
cp "${APP_DIR}/deploy/nginx/playbull.conf" /etc/nginx/sites-available/playbull
|
||||
ln -sf /etc/nginx/sites-available/playbull /etc/nginx/sites-enabled/playbull
|
||||
certbot --nginx -d "${APP_DOMAIN}" -d "www.${APP_DOMAIN}" --non-interactive --agree-tos -m "admin@${APP_DOMAIN}" || true
|
||||
nginx -t && systemctl reload nginx
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Fertig ==="
|
||||
echo "Pfad: ${APP_DIR}"
|
||||
echo "Intern: http://127.0.0.1:${APP_PORT}"
|
||||
echo "DuckDNS: ${DUCKDNS_DOMAIN:-(deaktiviert)}"
|
||||
echo "Status: systemctl status playbull cloudflared cloudflare-ddns-update.timer"
|
||||
if [[ -n "${CLOUDFLARE_TUNNEL_TOKEN:-}" ]]; then
|
||||
echo "HTTPS: https://${APP_DOMAIN}"
|
||||
fi
|
||||
echo ""
|
||||
echo "Hinweis: JWT_SECRET und FINNHUB_API_KEY in ${ENV_FILE} pruefen, dann:"
|
||||
echo " sudo systemctl restart playbull"
|
||||
21
deploy/nginx/bestewebsite-lan.conf
Normal file
21
deploy/nginx/bestewebsite-lan.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
# LAN / Fritz-Netz — HTTP auf Server-IP und Hostname (ohne TLS)
|
||||
# Install: sudo cp deploy/nginx/bestewebsite-lan.conf /etc/nginx/sites-available/bestewebsite-lan
|
||||
# sudo ln -sf /etc/nginx/sites-available/bestewebsite-lan /etc/nginx/sites-enabled/00-bestewebsite-lan
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name 192.168.178.49 mikeshomelab _;
|
||||
|
||||
client_max_body_size 30m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3080;
|
||||
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 $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
35
deploy/nginx/bestewebsite.conf
Normal file
35
deploy/nginx/bestewebsite.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
# Öffentlicher HTTPS-VHost — DuckDNS → PlayBull :3080
|
||||
# Install: sudo cp deploy/nginx/bestewebsite.conf /etc/nginx/sites-available/bestewebsite
|
||||
# sudo ln -sf /etc/nginx/sites-available/bestewebsite /etc/nginx/sites-enabled/
|
||||
# sudo certbot --nginx -d halmc.duckdns.org --non-interactive --agree-tos --redirect
|
||||
# sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
server {
|
||||
server_name halmc.duckdns.org;
|
||||
|
||||
client_max_body_size 30m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3080;
|
||||
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 $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/halmc.duckdns.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/halmc.duckdns.org/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name halmc.duckdns.org;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
43
deploy/nginx/playbull.conf
Normal file
43
deploy/nginx/playbull.conf
Normal file
@@ -0,0 +1,43 @@
|
||||
# Optional: 3. Website auf bestehendem Nginx :443 (SNI).
|
||||
# PlayBull laeuft intern auf Port 3080 — kein Konflikt mit anderen Sites.
|
||||
#
|
||||
# Install: sudo cp deploy/nginx/playbull.conf /etc/nginx/sites-available/playbull
|
||||
# sudo ln -sf /etc/nginx/sites-available/playbull /etc/nginx/sites-enabled/
|
||||
# sudo certbot --nginx -d halmc.duckdns.org
|
||||
# sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name halmc.duckdns.org;
|
||||
|
||||
# certbot legt Zertifikate an:
|
||||
# ssl_certificate /etc/letsencrypt/live/halmc.duckdns.org/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/halmc.duckdns.org/privkey.pem;
|
||||
|
||||
client_max_body_size 10m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3080;
|
||||
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 $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name halmc.duckdns.org;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
33
deploy/nginx/trilogyhub.conf
Normal file
33
deploy/nginx/trilogyhub.conf
Normal file
@@ -0,0 +1,33 @@
|
||||
# Öffentlicher HTTPS-VHost — trilogyhub.de (+ optional www)
|
||||
# Mit Cloudflare Tunnel: oft nicht noetig (Tunnel terminiert HTTPS).
|
||||
# Mit Nginx + Let's Encrypt: certbot --nginx -d trilogyhub.de -d www.trilogyhub.de
|
||||
|
||||
server {
|
||||
server_name trilogyhub.de www.trilogyhub.de;
|
||||
|
||||
client_max_body_size 30m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3080;
|
||||
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 $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/trilogyhub.de/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/trilogyhub.de/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name trilogyhub.de www.trilogyhub.de;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
139
deploy/push-homelab.ps1
Normal file
139
deploy/push-homelab.ps1
Normal file
@@ -0,0 +1,139 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Kopiert PlayBull nach ~/bestewebsite/bestewebsite und startet install-homelab.sh.
|
||||
Nutzt deploy/homelab.credentials (Posh-SSH) wenn vorhanden.
|
||||
#>
|
||||
param(
|
||||
[string]$SshTarget = "",
|
||||
[string]$RemoteDir = "",
|
||||
[switch]$SkipInstall,
|
||||
[switch]$UserOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path $PSScriptRoot -Parent
|
||||
$CredFile = Join-Path $PSScriptRoot "homelab.credentials"
|
||||
$UseCredentials = Test-Path $CredFile
|
||||
|
||||
function Assert-LastExit {
|
||||
param([string]$Step)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "${Step} fehlgeschlagen (Exit ${LASTEXITCODE})."
|
||||
}
|
||||
}
|
||||
|
||||
$EnvFile = Join-Path $PSScriptRoot "env.homelab"
|
||||
if (-not (Test-Path $EnvFile)) {
|
||||
Write-Error "deploy/env.homelab fehlt."
|
||||
}
|
||||
|
||||
$envContent = Get-Content $EnvFile -Raw
|
||||
if ($RemoteDir -eq "" -and $envContent -match '(?m)^APP_DIR=(.+)$') {
|
||||
$RemoteDir = $Matches[1].Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($RemoteDir)) {
|
||||
$RemoteDir = "/home/gizzler/bestewebsite/bestewebsite"
|
||||
}
|
||||
|
||||
if ($UseCredentials) {
|
||||
. "$PSScriptRoot/homelab-lib.ps1"
|
||||
$config = Get-HomelabConfig
|
||||
if ($SshTarget -eq "") { $SshTarget = $config.SshTarget }
|
||||
|
||||
Write-Host "==> Sync nach $($config.SshTarget):${RemoteDir} (homelab.credentials)" -ForegroundColor Cyan
|
||||
Invoke-HomelabRemote -Config $config -Command "mkdir -p '$RemoteDir'" -WorkDir "" | Out-Null
|
||||
|
||||
$archiveWin = Join-Path $env:TEMP "playbull-deploy.tgz"
|
||||
if (Test-Path $archiveWin) { Remove-Item $archiveWin -Force }
|
||||
$tarExe = Join-Path $env:SystemRoot "System32\tar.exe"
|
||||
$tarItems = @("server", "public", "package.json", "package-lock.json", "deploy", ".env.example")
|
||||
Push-Location $Root
|
||||
try {
|
||||
& $tarExe -czf $archiveWin @tarItems
|
||||
if ($LASTEXITCODE -ne 0) { throw "tar-Archiv fehlgeschlagen." }
|
||||
}
|
||||
finally { Pop-Location }
|
||||
|
||||
$sizeMb = [math]::Round((Get-Item $archiveWin).Length / 1MB, 1)
|
||||
Write-Host " -> Archiv ${sizeMb} MB" -ForegroundColor DarkGray
|
||||
Send-HomelabPath -Config $config -LocalPath $archiveWin -RemoteDir $RemoteDir
|
||||
|
||||
$extractCmd = @'
|
||||
cp deploy/env.homelab deploy/env.homelab.bak 2>/dev/null || true
|
||||
tar xzf playbull-deploy.tgz && rm -f playbull-deploy.tgz
|
||||
if [ -f deploy/env.homelab.bak ]; then
|
||||
for key in DUCKDNS_TOKEN CLOUDFLARE_TUNNEL_TOKEN CLOUDFLARE_API_TOKEN CLOUDFLARE_ZONE_ID JWT_SECRET FINNHUB_API_KEY APP_DOMAIN; do
|
||||
old=$(grep -m1 "^${key}=" deploy/env.homelab.bak 2>/dev/null | cut -d= -f2- | tr -d '\r')
|
||||
if [ -n "$old" ]; then
|
||||
if grep -q "^${key}=" deploy/env.homelab 2>/dev/null; then
|
||||
sed -i "s|^${key}=.*|${key}=${old}|" deploy/env.homelab
|
||||
else
|
||||
echo "${key}=${old}" >> deploy/env.homelab
|
||||
fi
|
||||
fi
|
||||
done
|
||||
rm -f deploy/env.homelab.bak
|
||||
fi
|
||||
sed -i 's/\r$//' deploy/env.homelab deploy/*.sh deploy/nginx/*.conf 2>/dev/null || true
|
||||
'@
|
||||
$extract = Invoke-HomelabRemote -Config $config -Command $extractCmd -WorkDir $RemoteDir -TimeoutSeconds 300
|
||||
$extract.Output | ForEach-Object { Write-Output $_ }
|
||||
if ($extract.ExitStatus -ne 0) { exit $extract.ExitStatus }
|
||||
Remove-Item $archiveWin -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if ($UserOnly) {
|
||||
Write-Host "==> User-Sync"
|
||||
$r = Invoke-HomelabRemote -Config $config -Command "bash deploy/sync-user.sh" -WorkDir $RemoteDir -TimeoutSeconds 300
|
||||
$r.Output | ForEach-Object { Write-Output $_ }
|
||||
if ($r.ExitStatus -ne 0) { exit $r.ExitStatus }
|
||||
Write-Host "Fertig (User-Modus)." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($SkipInstall) {
|
||||
Write-Host "Sync fertig. Installer: .\deploy\homelab-exec.ps1 -Sudo 'bash deploy/install-homelab.sh'" -ForegroundColor Cyan
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "==> Installer auf Homelab"
|
||||
$install = Invoke-HomelabRemote -Config $config -Sudo -Command "chmod +x deploy/*.sh && bash deploy/install-homelab.sh" -WorkDir $RemoteDir -TimeoutSeconds 900
|
||||
$install.Output | ForEach-Object { Write-Output $_ }
|
||||
if ($install.Error) { $install.Error | ForEach-Object { [Console]::Error.WriteLine($_) } }
|
||||
if ($install.ExitStatus -ne 0) { exit $install.ExitStatus }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "==> Verifikation" -ForegroundColor Cyan
|
||||
$verify = Invoke-HomelabRemote -Config $config -Command "systemctl is-active playbull cloudflared 2>/dev/null; curl -sf http://127.0.0.1:3080/api/market | head -c 80; echo; test -f public/image-editor.js && echo image-editor.js OK" -WorkDir $RemoteDir
|
||||
$verify.Output | ForEach-Object { Write-Output $_ }
|
||||
if ($verify.ExitStatus -ne 0) { exit $verify.ExitStatus }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Fertig. https://trilogyhub.de" -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Fallback: interaktives ssh/scp
|
||||
if ($SshTarget -eq "" -and $envContent -match '(?m)^HOMELAB_SSH=(.+)$') {
|
||||
$SshTarget = $Matches[1].Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($SshTarget) -or $SshTarget -eq "root@dein-server") {
|
||||
Write-Error "HOMELAB_SSH setzen oder deploy/homelab.credentials anlegen."
|
||||
}
|
||||
|
||||
Write-Host "SSH mit Passwort (interaktiv)..." -ForegroundColor Yellow
|
||||
ssh $SshTarget "mkdir -p '$RemoteDir'"
|
||||
Assert-LastExit "ssh mkdir"
|
||||
|
||||
$items = @("server", "public", "package.json", "package-lock.json", "deploy", ".env.example")
|
||||
foreach ($item in $items) {
|
||||
$local = Join-Path $Root $item
|
||||
if (Test-Path $local) {
|
||||
scp -r $local "${SshTarget}:${RemoteDir}/"
|
||||
Assert-LastExit "scp $item"
|
||||
}
|
||||
}
|
||||
|
||||
if ($SkipInstall) { exit 0 }
|
||||
ssh -t $SshTarget "cd '$RemoteDir' && chmod +x deploy/*.sh && sudo bash deploy/install-homelab.sh"
|
||||
Assert-LastExit "install-homelab.sh"
|
||||
28
deploy/quick-start-homelab.sh
Normal file
28
deploy/quick-start-homelab.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Schnellstart auf dem Homelab als normaler User (gizzler)
|
||||
# DuckDNS + systemd brauchen einmal: sudo bash deploy/install-homelab.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "${SCRIPT_DIR}/.."
|
||||
|
||||
echo "==> PlayBull Quick-Start ($(whoami)@$(hostname))"
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
echo "Erstelle .env — bitte Werte eintragen:"
|
||||
cp -n .env.example .env 2>/dev/null || true
|
||||
${EDITOR:-nano} .env
|
||||
fi
|
||||
|
||||
if [[ ! -f deploy/env.homelab ]]; then
|
||||
cp deploy/env.homelab.example deploy/env.homelab
|
||||
${EDITOR:-nano} deploy/env.homelab
|
||||
fi
|
||||
|
||||
command -v node >/dev/null || { echo "Node.js fehlt. Bitte installieren (v24+)."; exit 1; }
|
||||
npm install --omit=dev
|
||||
|
||||
echo ""
|
||||
echo "Test starten: npm start"
|
||||
echo "Dauerbetrieb: sudo bash deploy/install-homelab.sh (fragt sudo-Passwort)"
|
||||
echo ""
|
||||
4
deploy/setup-all.bat
Normal file
4
deploy/setup-all.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
cd /d "%~dp0.."
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-all.ps1"
|
||||
pause
|
||||
11
deploy/setup-all.ps1
Normal file
11
deploy/setup-all.ps1
Normal file
@@ -0,0 +1,11 @@
|
||||
#Requires -Version 5.1
|
||||
# Ein-Klick: Sync + Installer + Verifikation auf dem Homelab
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-Location (Split-Path $PSScriptRoot -Parent)
|
||||
Write-Host "=== PlayBull Homelab Setup ===" -ForegroundColor Cyan
|
||||
& "$PSScriptRoot\push-homelab.ps1"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Write-Host ""
|
||||
Write-Host "=== Status ===" -ForegroundColor Cyan
|
||||
& "$PSScriptRoot\homelab-exec.ps1" "systemctl status playbull cloudflared --no-pager -l | head -40"
|
||||
& "$PSScriptRoot\homelab-exec.ps1" "grep -E '^(PORT|JWT_SECRET|FINNHUB_API_KEY)=' .env | sed 's/=.*/=***/'"
|
||||
32
deploy/sync-user.sh
Normal file
32
deploy/sync-user.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Ohne systemd/sudo: Dateien nach ~/playbull kopieren und npm install.
|
||||
# Auf dem Homelab: cd ~/bestewebsite/bestewebsite && bash deploy/sync-user.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SRC="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -f "${SCRIPT_DIR}/env.homelab" ]] && source "${SCRIPT_DIR}/env.homelab"
|
||||
APP_DIR="${APP_DIR:-/home/gizzler/bestewebsite/bestewebsite}"
|
||||
APP_PORT="${APP_PORT:-3080}"
|
||||
|
||||
mkdir -p "${APP_DIR}"
|
||||
rsync -a --delete \
|
||||
--exclude node_modules --exclude data --exclude .git \
|
||||
"${SRC}/" "${APP_DIR}/"
|
||||
|
||||
cd "${APP_DIR}"
|
||||
command -v node >/dev/null || { echo "Node.js fehlt (v24+)."; exit 1; }
|
||||
npm install --omit=dev
|
||||
|
||||
ENV="${APP_DIR}/.env"
|
||||
touch "${ENV}"
|
||||
grep -q '^PORT=' "${ENV}" 2>/dev/null && sed -i "s/^PORT=.*/PORT=${APP_PORT}/" "${ENV}" || echo "PORT=${APP_PORT}" >> "${ENV}"
|
||||
grep -q '^TRUST_PROXY=' "${ENV}" 2>/dev/null || echo "TRUST_PROXY=1" >> "${ENV}"
|
||||
|
||||
echo ""
|
||||
echo "=== Sync fertig: ${APP_DIR} ==="
|
||||
echo "Start (Test): cd ${APP_DIR} && node server/index.js"
|
||||
echo "Dauerbetrieb: bash deploy/finish-on-server.sh (sudo + systemd)"
|
||||
echo ""
|
||||
echo "Falls schon laeuft: pkill -f 'node server/index.js' ; cd ${APP_DIR} && nohup node server/index.js >> data/server.log 2>&1 &"
|
||||
12
deploy/systemd/cloudflare-ddns-update.service
Normal file
12
deploy/systemd/cloudflare-ddns-update.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Cloudflare Dynamic DNS updater (Trilogy Hub)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=__APP_DIR__/deploy/cloudflare-ddns-update.sh
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
deploy/systemd/cloudflare-ddns-update.timer
Normal file
10
deploy/systemd/cloudflare-ddns-update.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Cloudflare DDNS alle 5 Minuten pruefen
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=5min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
17
deploy/systemd/cloudflared-playbull.service
Normal file
17
deploy/systemd/cloudflared-playbull.service
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Cloudflare Tunnel for PlayBull (HTTPS)
|
||||
After=network-online.target playbull.service
|
||||
Wants=network-online.target
|
||||
Requires=playbull.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
EnvironmentFile=/opt/playbull/deploy/env.homelab
|
||||
# Token-Modus: einfachster Weg aus dem Zero-Trust-Dashboard
|
||||
ExecStart=/bin/bash -c 'exec /usr/bin/cloudflared tunnel --no-autoupdate run --token "$CLOUDFLARE_TUNNEL_TOKEN"'
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
12
deploy/systemd/duckdns-update.service
Normal file
12
deploy/systemd/duckdns-update.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=DuckDNS IP updater for PlayBull homelab
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=__APP_DIR__/deploy/duckdns-update.sh
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
11
deploy/systemd/duckdns-update.timer
Normal file
11
deploy/systemd/duckdns-update.timer
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run DuckDNS IP update every 5 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=1min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
18
deploy/systemd/playbull.service
Normal file
18
deploy/systemd/playbull.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=PlayBull — Trilogy Hub + Trading + Casino
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=__APP_USER__
|
||||
Group=__APP_USER__
|
||||
WorkingDirectory=__APP_DIR__
|
||||
EnvironmentFile=__APP_DIR__/.env
|
||||
Environment=NODE_ENV=production
|
||||
Environment=TRUST_PROXY=1
|
||||
ExecStart=/usr/bin/node __APP_DIR__/server/index.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
5
deploy/test-ports.ps1
Normal file
5
deploy/test-ports.ps1
Normal file
@@ -0,0 +1,5 @@
|
||||
$ip = "192.168.178.49"
|
||||
foreach ($port in 22, 80, 443, 3080) {
|
||||
$r = Test-NetConnection -ComputerName $ip -Port $port -WarningAction SilentlyContinue
|
||||
Write-Output "Port ${port}: $($r.TcpTestSucceeded)"
|
||||
}
|
||||
Reference in New Issue
Block a user