#!/usr/bin/env bash # # Monitor-Check – Installationsskript fuer Ubuntu 24.04 (funktioniert auch auf 22.04) # # sudo bash install.sh # gefuehrte Installation # sudo bash install.sh --domain m.example.de --email ich@example.de --yes # sudo bash install.sh --update # nur Dateien aktualisieren + Neustart # sudo bash install.sh --dry-run # nur zeigen, was passieren wuerde # # Das Skript erwartet die Ordner server/ und web/ neben sich. set -euo pipefail # ------------------------------------------------------------------ Konstanten APP_NAME="monitor-check" APP_USER="monitor" APP_DIR="/opt/${APP_NAME}" DATA_DIR="/var/lib/${APP_NAME}" UPLOAD_DIR="${DATA_DIR}/uploads" SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service" NGINX_FILE="/etc/nginx/sites-available/${APP_NAME}" NODE_MAJOR=20 SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ------------------------------------------------------------------ Parameter DOMAIN="" EMAIL="" TOKEN="" PORT="8080" MAX_UPLOAD_MB="15" ASSUME_YES=0 DRY_RUN=0 MODE="install" USE_TLS=1 USE_FIREWALL=1 USE_NGINX=1 PUBLIC_URL_OVERRIDE="" BEHIND_PROXY=0 # ------------------------------------------------------------------ Ausgabe if [[ -t 1 ]]; then B="\033[1m"; DIM="\033[2m"; GRN="\033[32m"; YLW="\033[33m"; RED="\033[31m"; RST="\033[0m" else B=""; DIM=""; GRN=""; YLW=""; RED=""; RST="" fi schritt() { printf "\n${B}==> %s${RST}\n" "$*"; } info() { printf " %s\n" "$*"; } ok() { printf " ${GRN}✓${RST} %s\n" "$*"; } warn() { printf " ${YLW}!${RST} %s\n" "$*"; } fehler() { printf "\n${RED}Fehler:${RST} %s\n\n" "$*" >&2; exit 1; } run() { if [[ $DRY_RUN -eq 1 ]]; then printf " ${DIM}[dry-run] %s${RST}\n" "$*" else "$@" fi } # schreibt stdin nach $1 # Im Testlauf: mit MC_DRY_OUT=/pfad werden die erzeugten Dateien dort abgelegt. schreibe() { local ziel="$1" if [[ $DRY_RUN -eq 1 ]]; then printf " ${DIM}[dry-run] schreibe %s${RST}\n" "$ziel" if [[ -n "${MC_DRY_OUT:-}" ]]; then mkdir -p "${MC_DRY_OUT}$(dirname "$ziel")" cat > "${MC_DRY_OUT}${ziel}" else cat >/dev/null fi else cat > "$ziel" fi } hilfe() { sed -n '3,10p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' cat <<'EOF' Optionen: --domain Domain, unter der das Tool erreichbar ist --email E-Mail fuer Let's Encrypt --token API-Token (sonst wird eins erzeugt) --port interner Node-Port (Standard 8080) --max-upload maximale Fotogroesse in MB (Standard 15) --behind-proxy hinter vorgeschaltetem Reverse-Proxy (kein certbot), zusammen mit --public-url verwenden --public-url oeffentliche Adresse, z. B. https://monitore.example.de (landet als Bild-Link in der Tabelle) --no-nginx kein nginx im Container, Node direkt auf 0.0.0.0: --no-tls kein Zertifikat anfordern --no-firewall ufw nicht konfigurieren --update nur Anwendungsdateien erneuern und Dienst neu starten --yes keine Rueckfragen --dry-run nichts veraendern, nur anzeigen --help diese Hilfe EOF exit 0 } while [[ $# -gt 0 ]]; do case "$1" in --domain) DOMAIN="${2:-}"; shift 2 ;; --email) EMAIL="${2:-}"; shift 2 ;; --token) TOKEN="${2:-}"; shift 2 ;; --port) PORT="${2:-}"; shift 2 ;; --max-upload) MAX_UPLOAD_MB="${2:-}"; shift 2 ;; --public-url) PUBLIC_URL_OVERRIDE="${2:-}"; shift 2 ;; --behind-proxy) BEHIND_PROXY=1; USE_TLS=0; shift ;; --no-nginx) USE_NGINX=0; shift ;; --no-tls) USE_TLS=0; shift ;; --no-firewall) USE_FIREWALL=0; shift ;; --update) MODE="update"; shift ;; --yes|-y) ASSUME_YES=1; shift ;; --dry-run) DRY_RUN=1; shift ;; --help|-h) hilfe ;; *) fehler "Unbekannte Option: $1 (--help zeigt die Uebersicht)" ;; esac done # ------------------------------------------------------------------ Vorpruefung [[ $EUID -eq 0 || $DRY_RUN -eq 1 ]] || fehler "Bitte als root ausfuehren: sudo bash install.sh" [[ -d "${SRC_DIR}/server" && -d "${SRC_DIR}/web" ]] || \ fehler "Ordner server/ und web/ nicht gefunden neben ${BASH_SOURCE[0]}" [[ -f "${SRC_DIR}/server/server.js" ]] || fehler "server/server.js fehlt." [[ -f "${SRC_DIR}/web/index.html" ]] || fehler "web/index.html fehlt." if [[ -r /etc/os-release ]]; then . /etc/os-release [[ "${ID:-}" == "ubuntu" || "${ID_LIKE:-}" == *debian* ]] || \ warn "Nicht getestetes System (${PRETTY_NAME:-unbekannt}) – weiter auf eigenes Risiko." fi printf "\n${B}Monitor-Check – Installation${RST}\n" [[ $DRY_RUN -eq 1 ]] && printf "${YLW}Testlauf: es wird nichts veraendert.${RST}\n" || true # ------------------------------------------------------------------ Update-Modus if [[ "$MODE" == "update" ]]; then [[ -d "$APP_DIR" ]] || fehler "${APP_DIR} existiert nicht – zuerst normal installieren." schritt "Anwendungsdateien erneuern" run rsync -a --delete --exclude node_modules --exclude .env \ "${SRC_DIR}/server/" "${APP_DIR}/server/" 2>/dev/null || \ run cp -r "${SRC_DIR}/server/." "${APP_DIR}/server/" run cp -r "${SRC_DIR}/web/." "${APP_DIR}/web/" run chown -R "${APP_USER}:${APP_USER}" "$APP_DIR" run bash -c "cd '${APP_DIR}/server' && npm install --omit=dev --no-audit --no-fund" run systemctl restart "$APP_NAME" ok "Aktualisiert. Status: systemctl status ${APP_NAME}" exit 0 fi # ------------------------------------------------------------------ Abfragen frage() { # frage local __var="$1" __text="$2" __default="${3:-}" __eingabe="" local __aktuell="${!__var}" if [[ -n "$__aktuell" ]]; then return; fi if [[ $ASSUME_YES -eq 1 || ! -t 0 ]]; then printf -v "$__var" '%s' "$__default"; return fi if [[ -n "$__default" ]]; then read -r -p " ${__text} [${__default}]: " __eingabe || true else read -r -p " ${__text}: " __eingabe || true fi printf -v "$__var" '%s' "${__eingabe:-$__default}" } schritt "Angaben" # Token aus bestehender Installation weiterverwenden if [[ -z "$TOKEN" && -f "${APP_DIR}/server/.env" ]]; then TOKEN="$(grep -E '^API_TOKEN=' "${APP_DIR}/server/.env" | cut -d= -f2- || true)" [[ -n "$TOKEN" ]] && ok "Vorhandenes API-Token wird weiterverwendet." || true fi if [[ -z "$TOKEN" ]]; then TOKEN="$(openssl rand -hex 24 2>/dev/null || head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')" ok "Neues API-Token erzeugt." fi # Hinter einem vorgeschalteten Proxy uebernimmt dieser TLS und Zertifikat. [[ $USE_NGINX -eq 0 ]] && USE_TLS=0 || true [[ $BEHIND_PROXY -eq 1 ]] && USE_TLS=0 || true if [[ -z "$PUBLIC_URL_OVERRIDE" ]]; then frage DOMAIN "Domain (leer lassen fuer Zugriff nur ueber IP)" "" fi if [[ -n "$DOMAIN" && $USE_TLS -eq 1 ]]; then frage EMAIL "E-Mail fuer Let's Encrypt (leer = kein Zertifikat)" "" [[ -z "$EMAIL" ]] && USE_TLS=0 || true else USE_TLS=0 fi if [[ -n "$PUBLIC_URL_OVERRIDE" ]]; then PUBLIC_URL="${PUBLIC_URL_OVERRIDE%/}" [[ "$PUBLIC_URL" =~ ^https?:// ]] || fehler "--public-url muss mit http:// oder https:// beginnen." elif [[ -n "$DOMAIN" ]]; then PUBLIC_URL="$([[ $USE_TLS -eq 1 ]] && echo "https://${DOMAIN}" || echo "http://${DOMAIN}")" else IP="$(hostname -I 2>/dev/null | awk '{print $1}')" [[ -z "$IP" ]] && IP="127.0.0.1" || true PUBLIC_URL="http://${IP}" warn "Ohne Domain laeuft alles ueber http – die Handy-Kamera bleibt dann gesperrt." fi # Ohne nginx im Container muss Node von aussen erreichbar sein. if [[ $USE_NGINX -eq 1 ]]; then BIND_HOST="127.0.0.1"; else BIND_HOST="0.0.0.0"; fi # Hostname aus der oeffentlichen URL – fuer die Proxy-Vorlage PUBLIC_HOST="${PUBLIC_URL#*://}"; PUBLIC_HOST="${PUBLIC_HOST%%/*}" CONTAINER_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" [[ -z "$CONTAINER_IP" ]] && CONTAINER_IP="" || true UPSTREAM="$([[ $USE_NGINX -eq 1 ]] && echo "http://${CONTAINER_IP}:80" || echo "http://${CONTAINER_IP}:${PORT}")" cat <} Oeffentliche URL: ${PUBLIC_URL} TLS : $([[ $USE_TLS -eq 1 ]] && echo "Let's Encrypt im Container" || echo "nicht im Container (Proxy oder http)") nginx lokal : $([[ $USE_NGINX -eq 1 ]] && echo "ja, Port 80" || echo "nein, Node direkt") Node hoert auf: ${BIND_HOST}:${PORT} Verzeichnisse : ${APP_DIR} , ${DATA_DIR} API-Token : ${TOKEN} EOF if [[ $ASSUME_YES -eq 0 && $DRY_RUN -eq 0 && -t 0 ]]; then read -r -p " Fortfahren? [J/n] " a || true [[ "${a:-J}" =~ ^([JjYy]|)$ ]] || { echo " Abgebrochen."; exit 0; } fi # ------------------------------------------------------------------ Pakete schritt "Systempakete installieren" export DEBIAN_FRONTEND=noninteractive run apt-get update -qq if [[ $USE_NGINX -eq 1 ]]; then run apt-get install -y -qq curl ca-certificates gnupg nginx openssl rsync else run apt-get install -y -qq curl ca-certificates gnupg openssl rsync fi ok "Basispakete vorhanden." # Node.js NODE_OK=0 if command -v node >/dev/null 2>&1; then NODE_VER="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)" [[ "$NODE_VER" -ge 18 ]] && NODE_OK=1 && ok "Node.js $(node -v) bereits installiert." || true fi if [[ $NODE_OK -eq 0 ]]; then info "Node.js ${NODE_MAJOR} LTS wird eingerichtet …" run bash -c "curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash -" run apt-get install -y -qq nodejs ok "Node.js installiert." fi # ------------------------------------------------------------------ Benutzer schritt "Benutzer und Verzeichnisse" if id "$APP_USER" >/dev/null 2>&1; then ok "Benutzer ${APP_USER} existiert." else run adduser --system --group --home "$APP_DIR" --no-create-home "$APP_USER" ok "Benutzer ${APP_USER} angelegt." fi run mkdir -p "${APP_DIR}/server" "${APP_DIR}/web" "$UPLOAD_DIR" # ------------------------------------------------------------------ Dateien schritt "Anwendung kopieren" run rsync -a --exclude node_modules --exclude .env --exclude uploads \ "${SRC_DIR}/server/" "${APP_DIR}/server/" run rsync -a "${SRC_DIR}/web/" "${APP_DIR}/web/" ok "Dateien liegen in ${APP_DIR}." schritt "Node-Abhaengigkeiten installieren" info "Das dauert beim ersten Mal ein bis zwei Minuten (sharp)." run bash -c "cd '${APP_DIR}/server' && npm install --omit=dev --no-audit --no-fund" ok "Abhaengigkeiten installiert." # ------------------------------------------------------------------ Konfiguration schritt "Konfiguration schreiben" if [[ -f "${APP_DIR}/server/.env" && $DRY_RUN -eq 0 ]]; then cp "${APP_DIR}/server/.env" "${APP_DIR}/server/.env.bak.$(date +%s)" info "Bisherige .env gesichert." fi schreibe "${APP_DIR}/server/.env" </dev/null 2>&1; then ok "Node-Dienst antwortet." else warn "Node-Dienst antwortet nicht – pruefen mit: journalctl -u ${APP_NAME} -n 40 --no-pager" fi if [[ $USE_NGINX -eq 1 ]]; then if curl -fsS -o /dev/null "http://127.0.0.1/" 2>/dev/null; then ok "nginx liefert die Oberflaeche aus." else warn "nginx antwortet lokal nicht wie erwartet." fi fi fi # ------------------------------------------------------------------ Proxy-Vorlage PROXY_DATEI="${APP_DIR}/PROXY-SNIPPET.conf" if [[ $USE_TLS -eq 0 ]]; then schritt "Vorlage fuer den vorgeschalteten Proxy" schreibe "$PROXY_DATEI" < "$ZUSAMMENFASSUNG" < Apps Script, Inhalt von apps-script/Code.gs einfuegen und CONFIG.API_TOKEN auf das Token oben setzen. 2. Bereitstellen > Neue Bereitstellung > Web-App Ausfuehren als: Ich | Zugriff: ${B}Jeder${RST} Die .../exec-URL kopieren. 3. ${PUBLIC_URL} am Handy oeffnen, Tab "Setup": Apps-Script-URL, Token und Upload-Endpoint eintragen, dann "Verbindung testen". ${B}Nuetzlich:${RST} systemctl status ${APP_NAME} journalctl -u ${APP_NAME} -f sudo bash install.sh --update # nach Aenderungen an web/ oder server/ EOF