Files

1035 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="prev" href="../frontend/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.0">
<title>Operations & Wartung - Stackpulse Dokumentation</title>
<link rel="stylesheet" href="../assets/stylesheets/main.618322db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#operations-wartung" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Stackpulse Dokumentation" class="md-header__button md-logo" aria-label="Stackpulse Dokumentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Stackpulse Dokumentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Operations & Wartung
</span>
</div>
</div>
</div>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Stackpulse Dokumentation" class="md-nav__button md-logo" aria-label="Stackpulse Dokumentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Stackpulse Dokumentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Start
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Benutzerhandbuch
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Benutzerhandbuch
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../user-guide/" class="md-nav__link">
<span class="md-ellipsis">
Überblick
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/setup/" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/stacks/" class="md-nav__link">
<span class="md-ellipsis">
Stacks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/logs/" class="md-nav__link">
<span class="md-ellipsis">
Logs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/maintenance/" class="md-nav__link">
<span class="md-ellipsis">
Wartung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/users/" class="md-nav__link">
<span class="md-ellipsis">
Benutzer
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guide/groups/" class="md-nav__link">
<span class="md-ellipsis">
Benutzergruppen & Rechte
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked>
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<span class="md-ellipsis">
Technik
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
Technik
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../architecture/" class="md-nav__link">
<span class="md-ellipsis">
Architektur
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../backend/" class="md-nav__link">
<span class="md-ellipsis">
Backend & API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../frontend/" class="md-nav__link">
<span class="md-ellipsis">
Frontend
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Operations & Wartung
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Operations & Wartung
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#laufender-betrieb" class="md-nav__link">
<span class="md-ellipsis">
Laufender Betrieb
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backups-wiederherstellung" class="md-nav__link">
<span class="md-ellipsis">
Backups &amp; Wiederherstellung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#benutzer-rechteverwaltung" class="md-nav__link">
<span class="md-ellipsis">
Benutzer- &amp; Rechteverwaltung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wartungsmodus-updates" class="md-nav__link">
<span class="md-ellipsis">
Wartungsmodus &amp; Updates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#logs-compliance" class="md-nav__link">
<span class="md-ellipsis">
Logs &amp; Compliance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sicherheitsempfehlungen" class="md-nav__link">
<span class="md-ellipsis">
Sicherheitsempfehlungen
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#laufender-betrieb" class="md-nav__link">
<span class="md-ellipsis">
Laufender Betrieb
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backups-wiederherstellung" class="md-nav__link">
<span class="md-ellipsis">
Backups &amp; Wiederherstellung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#benutzer-rechteverwaltung" class="md-nav__link">
<span class="md-ellipsis">
Benutzer- &amp; Rechteverwaltung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wartungsmodus-updates" class="md-nav__link">
<span class="md-ellipsis">
Wartungsmodus &amp; Updates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#logs-compliance" class="md-nav__link">
<span class="md-ellipsis">
Logs &amp; Compliance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sicherheitsempfehlungen" class="md-nav__link">
<span class="md-ellipsis">
Sicherheitsempfehlungen
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="operations-wartung">Operations &amp; Wartung</h1>
<p>Dieser Abschnitt deckt typische Aufgaben für Betriebsteams ab von Backups über Wartungsmodus bis zu Störungssuche.</p>
<h2 id="laufender-betrieb">Laufender Betrieb</h2>
<ul>
<li><strong>Services überwachen</strong>: StackPulse stellt nur einen Prozess bereit (Node.js/Express). Nutze Systemd/Docker Healthchecks, indem du regelmäßig <code>/api/auth/session</code> (für Auth) oder <code>/api/maintenance/update-status</code> (für Backend + DB) abfragst.</li>
<li><strong>Ports</strong>: Standardmäßig lauscht nur Port 4001 (HTTP + Socket.IO). Hinter einem Reverse Proxy sollte TLS terminiert werden.</li>
<li><strong>Protokolle</strong>: Das Backend loggt ausführlich auf STDOUT/STDERR (<code>console.log</code>). Sammle diese via Docker-Logs oder Journal.</li>
</ul>
<h2 id="backups-wiederherstellung">Backups &amp; Wiederherstellung</h2>
<ol>
<li><strong>Container-Setup</strong>: Mount <code>backend/data</code> via benanntem Volume (siehe <code>docker-compose.yml</code>).</li>
<li><strong>Backup</strong>: Stoppe kurz den Container oder nutze <code>sqlite3 stackpulse.db ".backup 'stackpulse.db.bak'"</code> innerhalb der laufenden Instanz.</li>
<li><strong>Restore</strong>: Ersetze die Datei im Volume und starte die App neu. Beim ersten Start führt <code>ensureDatabaseSchema</code> automatisch eventuelle Nachmigrationen aus.</li>
</ol>
<h2 id="benutzer-rechteverwaltung">Benutzer- &amp; Rechteverwaltung</h2>
<ul>
<li>Superuser können über <code>/api/auth/superuser/*</code> oder im UI registriert werden. Entfernen ist nur mit <code>maintenance-superuser-delete</code> möglich.</li>
<li>Benutzerkonten lassen sich temporär deaktivieren (<code>/api/users/:id/active</code>). Die Historie bleibt erhalten.</li>
<li>Sicherheitsphrasen sind notwendig für Passwort-Reset. Admins können neue Phrasen erzeugen (<code>/api/users/:id/security-phrase/renew</code>), müssen diese aber dem Benutzer sicher zustellen.</li>
</ul>
<h2 id="wartungsmodus-updates">Wartungsmodus &amp; Updates</h2>
<ul>
<li>Maintenance Mode aktivieren: <code>curl -X POST http://host:4001/api/maintenance/mode -H 'Content-Type: application/json' -d '{"active":true,"message":"Cluster Upgrade"}'</code> (Permission <code>maintenance-access</code>). Das UI informiert alle Nutzer und blockiert Redeploys.</li>
<li>SSH-/Update-Setup: Hinterlege Host, Port, Benutzer, Passwort (optional) und zusätzliche SSH-Argumente. Passwörter werden verschlüsselt gespeichert (<code>PORTAINER_SSH_SECRET</code>).</li>
<li>Update-Skript: Das Default-Skript (<code>DEFAULT_PORTAINER_UPDATE_SCRIPT</code>) führt <code>docker stop</code>, <code>docker run</code> etc. aus. Du kannst im UI oder via API ein eigenes Skript setzen wird erst aktualisiert, wenn kein Update läuft.</li>
<li>Update anstoßen: <code>POST /api/maintenance/portainer-update</code> triggert die SSH-Kommandos. Status &amp; Logausgaben können über <code>/api/maintenance/update-status</code> überwacht werden.</li>
</ul>
<h2 id="logs-compliance">Logs &amp; Compliance</h2>
<ul>
<li>Ereignislogs enthalten jede Redeploy-, Auth- und Verwaltungsaktion. Export über <code>GET /api/logs/export</code> (CSV) oder automatisiert mittels Cron (Query-Parameter übernehmen Filter).</li>
<li>Datenbereinigung: Nutze <code>DELETE /api/logs/:id</code> oder <code>DELETE /api/logs?before=&lt;ISO&gt;</code> um alte Einträge zu entfernen. Achtung: Aktionen sind nicht reversibel.</li>
<li>Für Offload kannst du per Script regelmäßig Exporte ziehen und anschließend löschen.</li>
</ul>
<h2 id="troubleshooting">Troubleshooting</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Prüfschritte</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Setup schlägt fehl (PORTAINER_URL_NOT_CONFIGURED)</strong></td>
<td>Stelle sicher, dass entweder <code>PORTAINER_URL</code> gesetzt ist oder im Setup-UI ein Server gespeichert wurde (<code>servers</code>-Tabelle prüfen).</td>
</tr>
<tr>
<td><strong>Redeploy hängt in <code>queued</code></strong></td>
<td><code>/api/maintenance/update-status</code> auf laufende Updates prüfen, <code>redeployingStacks</code> im Log verfolgen; ggf. Portainer-API erreichbar?</td>
</tr>
<tr>
<td><strong>Login nicht möglich</strong></td>
<td>Über <code>backend/logs</code> nach Auth-Fehlern suchen, Cookie-Konfiguration prüfen (<code>AUTH_COOKIE_SECURE</code> bei HTTP?).</td>
</tr>
<tr>
<td><strong>SSH-Test fehlgeschlagen</strong></td>
<td><code>POST /api/maintenance/test-ssh</code> mit temporären Credentials testen, Container-Logs auf <code>SSH Test fehlgeschlagen</code> untersuchen.</td>
</tr>
<tr>
<td><strong>SQLite Locked</strong></td>
<td>Stelle sicher, dass kein paralleler Backup-Prozess läuft. WAL-Modus erlaubt mehrere Leser, blockiert aber bei langen Schreibtransaktionen (Check Logs auf <code>SQLITE_BUSY</code>).</td>
</tr>
</tbody>
</table>
<h2 id="sicherheitsempfehlungen">Sicherheitsempfehlungen</h2>
<ul>
<li>Setze <code>AUTH_COOKIE_SECURE=true</code> hinter TLS und konfiguriere einen Reverse Proxy (z.B. Traefik, Nginx).</li>
<li>Isoliere das Admin-Interface durch VPN oder IP-Filter.</li>
<li>Drehe API-Keys regelmäßig: <code>/api/setup/servers/:id/api-key</code> aktualisiert Schlüssel ohne Neuinstallation.</li>
<li>Nutze dedizierte StackPulse-Benutzer in Portainer, damit Berechtigungen klar getrennt bleiben.</li>
</ul>
<p>Mit diesen Richtlinien lässt sich StackPulse sicher betreiben und warten.</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.7a47a382.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.e71a0d61.min.js"></script>
</body>
</html>