- Dockerfile: python:3.14-slim, the Python CI tests on (was 3.13). tini from apt is PID 1 (ENTRYPOINT tini -- entrypoint.sh): it reaps the zombies ssh and ansible leave and forwards SIGTERM. - cli: Hypercorn's serve() with one worker (run, stream and scheduler state is in-process), a 10 s graceful timeout, and SIGTERM/SIGINT as the shutdown trigger. Access and error lines go through Steward's logging. --debug keeps Quart's reloading dev server. - Logging: steward/core/logging_setup.py installs one root handler before anything logs (until now a library's basicConfig on import was the only configuration). Its level is the `log.level` setting (DEBUG/INFO/WARNING/ ERROR, default INFO), applied at boot and on save in Settings → General. - CI publish: build, then "The image boots with no internet" (rule 164, snippet #3552), then push, so a broken image never reaches a tag (rule 177). On an --internal network with its own Postgres, the image must migrate and answer /health, run tini as PID 1, serve first-run setup with its assets from /static and no third-party host, and exit 0 on SIGTERM. Logs and clean-up run always. Tests: tests/core/test_logging_setup.py; the smoke step itself. Not in this commit: the dependency lockfile (it conflicts with rule 154 and is waiting on the operator). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
5.1 KiB
HTML
95 lines
5.1 KiB
HTML
{# steward/templates/settings/general.html #}
|
|
{% extends "base.html" %}
|
|
{% from "_macros.html" import crumbs %}
|
|
{% block title %}Settings — General — Steward{% endblock %}
|
|
{% block breadcrumb %}{{ crumbs([("Settings", "/settings/"), ("General", "")]) }}{% endblock %}
|
|
{% block content %}
|
|
{% set active_tab = "general" %}
|
|
{% include "settings/_tabs.html" %}
|
|
|
|
<form method="post" action="/settings/general/">{{ csrf_field() }}
|
|
<div class="card" style="max-width:640px;">
|
|
<h2 class="section-title" style="margin-bottom:1.25rem;">General</h2>
|
|
|
|
<div class="form-group">
|
|
<label>Public base URL</label>
|
|
<input type="text" name="general.public_base_url"
|
|
value="{{ settings['general.public_base_url'] }}"
|
|
placeholder="https://monitor.example.com">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">
|
|
External URL where this Steward instance is reachable from the outside.
|
|
Leave blank to use whatever hostname you're currently browsing via.
|
|
Used by install scripts, share links, and alert notifications.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Trusted reverse proxies</label>
|
|
<input type="number" name="general.trusted_proxy_hops" min="0" max="5"
|
|
value="{{ settings['general.trusted_proxy_hops'] }}" style="max-width:120px;">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">
|
|
How many proxies sit in front of Steward (e.g. 1 for Traefik, 2 for Cloudflare in front of Traefik).
|
|
Steward believes their X-Forwarded-For and X-Forwarded-Proto headers up to that many hops: it marks
|
|
the login cookie Secure and sends HSTS only when they report HTTPS, and throttles logins by the address
|
|
they report. 0 trusts no proxy.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Session lifetime <span style="color:var(--text-muted);font-size:0.8rem;">(hours)</span></label>
|
|
<input type="number" name="session.lifetime_hours" min="1" max="720"
|
|
value="{{ settings['session.lifetime_hours'] }}" style="max-width:120px;">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">A login lasts this long; then Steward asks for the password again.</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Failed logins before a pause</label>
|
|
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
|
|
<input type="number" name="login.max_failures" min="1" max="100"
|
|
value="{{ settings['login.max_failures'] }}" style="max-width:90px;">
|
|
<span style="font-size:0.85rem;color:var(--text-muted);">failures from one address pause its logins for</span>
|
|
<input type="number" name="login.lockout_minutes" min="1" max="1440"
|
|
value="{{ settings['login.lockout_minutes'] }}" style="max-width:90px;">
|
|
<span style="font-size:0.85rem;color:var(--text-muted);">minutes</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Data retention <span style="color:var(--text-muted);font-size:0.8rem;">(days)</span></label>
|
|
<input type="number" name="data.retention_days" min="1"
|
|
value="{{ settings['data.retention_days'] }}" style="max-width:120px;">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">Monitor results, Ansible runs and their logs, alert history, and Traefik and UniFi samples older than this are pruned by the hourly cleanup. Host metrics, Docker data and container logs have their own windows under Settings → Thresholds.</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Audit log retention <span style="color:var(--text-muted);font-size:0.8rem;">(days)</span></label>
|
|
<input type="number" name="audit.retention_days" min="1"
|
|
value="{{ settings['audit.retention_days'] }}" style="max-width:120px;">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">Who changed what. Kept longer than operational data; entries older than this are pruned by the hourly cleanup.</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Monitor poll interval <span style="color:var(--text-muted);font-size:0.8rem;">(seconds)</span></label>
|
|
<input type="number" name="monitors.poll_interval_seconds" min="10"
|
|
value="{{ settings['monitors.poll_interval_seconds'] }}" style="max-width:120px;">
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">Requires restart to take effect.</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Log level</label>
|
|
<select name="log.level" style="max-width:160px;">
|
|
{% for level in log_levels %}
|
|
<option value="{{ level }}" {% if settings['log.level'] == level %}selected{% endif %}>{{ level | capitalize }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;">What reaches the container log. Applies when saved. Warning hides the line logged for each request.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top:1rem;display:flex;align-items:center;gap:1rem;">
|
|
<button type="submit" class="btn">Save</button>
|
|
<span style="font-size:0.82rem;color:var(--text-muted);">Changes to session lifetime take effect immediately.</span>
|
|
</div>
|
|
</form>
|
|
{% endblock %}
|