Gladex Agent Logs
Agent run logs & app logs · env: prod · LAN-only investor surface
Overview
| Run logs | 528 files, 18.9 MB |
| Latest run log | run-20260926-073109-126.log |
| Log directory | /data/agent-logs |
| App log directory | /opt/startup/prod/logs |
Run logs (newest first, last 50)
| File | Size | Modified (UTC) |
|---|---|---|
| run-20260926-073109-126.log | 124 KB | 2026-09-26 05:58:09 |
| run-20260926-061035-125.log | 341 KB | 2026-09-26 05:21:09 |
| run-20260926-052113-124.log | 352 KB | 2026-09-26 04:00:35 |
| run-20260926-043030-123.log | 311 KB | 2026-09-26 03:11:13 |
| run-20260926-032802-122.log | 338 KB | 2026-09-26 02:20:30 |
| run-20260926-024118-121.log | 334 KB | 2026-09-26 01:18:02 |
| run-20260926-020038-120.log | 273 KB | 2026-09-26 00:31:18 |
| run-20260926-015037-119.log | 153 B | 2026-09-25 23:50:38 |
| run-20260926-014036-118.log | 153 B | 2026-09-25 23:40:37 |
| run-20260926-013035-117.log | 153 B | 2026-09-25 23:30:36 |
| run-20260926-012035-116.log | 153 B | 2026-09-25 23:20:35 |
| run-20260926-011034-115.log | 153 B | 2026-09-25 23:10:35 |
| run-20260926-010033-114.log | 153 B | 2026-09-25 23:00:34 |
| run-20260926-005033-113.log | 190 B | 2026-09-25 22:50:33 |
| run-20260926-004031-112.log | 153 B | 2026-09-25 22:40:33 |
| run-20260926-003030-111.log | 153 B | 2026-09-25 22:30:31 |
| run-20260926-002029-110.log | 153 B | 2026-09-25 22:20:30 |
| run-20260926-001029-109.log | 153 B | 2026-09-25 22:10:29 |
| run-20260926-000025-108.log | 190 B | 2026-09-25 22:00:29 |
| run-20260925-235025-107.log | 153 B | 2026-09-25 21:50:25 |
| run-20260925-234024-106.log | 153 B | 2026-09-25 21:40:25 |
| run-20260925-233023-105.log | 153 B | 2026-09-25 21:30:24 |
| run-20260925-232022-104.log | 153 B | 2026-09-25 21:20:23 |
| run-20260925-231021-103.log | 153 B | 2026-09-25 21:10:22 |
| run-20260925-230021-102.log | 153 B | 2026-09-25 21:00:21 |
| run-20260925-225020-101.log | 190 B | 2026-09-25 20:50:21 |
| run-20260925-224019-100.log | 153 B | 2026-09-25 20:40:19 |
| run-20260925-223018-99.log | 152 B | 2026-09-25 20:30:19 |
| run-20260925-222017-98.log | 152 B | 2026-09-25 20:20:18 |
| run-20260925-221016-97.log | 152 B | 2026-09-25 20:10:17 |
| run-20260925-220015-96.log | 152 B | 2026-09-25 20:00:16 |
| run-20260925-213653-95.log | 141 KB | 2026-09-25 19:50:15 |
| run-20260925-205157-94.log | 389 KB | 2026-09-25 19:26:53 |
| run-20260925-195858-93.log | 517 KB | 2026-09-25 18:41:57 |
| run-20260925-192850-92.log | 321 KB | 2026-09-25 17:48:58 |
| run-20260925-185030-91.log | 325 KB | 2026-09-25 17:18:50 |
| run-20260925-180536-90.log | 232 KB | 2026-09-25 16:40:30 |
| run-20260925-173957-89.log | 252 KB | 2026-09-25 15:55:36 |
| run-20260925-171044-88.log | 201 KB | 2026-09-25 15:29:57 |
| run-20260925-163300-87.log | 247 KB | 2026-09-25 15:00:44 |
| run-20260925-160013-86.log | 175 KB | 2026-09-25 14:23:00 |
| run-20260925-153430-85.log | 158 KB | 2026-09-25 13:50:13 |
| run-20260925-152430-84.log | 152 B | 2026-09-25 13:24:30 |
| run-20260925-151428-83.log | 189 B | 2026-09-25 13:14:30 |
| run-20260925-150428-82.log | 152 B | 2026-09-25 13:04:28 |
| run-20260925-145427-81.log | 152 B | 2026-09-25 12:54:28 |
| run-20260925-144426-80.log | 152 B | 2026-09-25 12:44:27 |
| run-20260925-143426-79.log | 152 B | 2026-09-25 12:34:26 |
| run-20260925-142425-78.log | 189 B | 2026-09-25 12:24:26 |
| run-20260925-141424-77.log | 152 B | 2026-09-25 12:14:25 |
Tail — run-20260926-073109-126.log (last 200 lines)
# to mean everything from DNS failure to an expired cert
return rejected_entry(entry, domain, port, timeout, e, domain)
except Exception as e:
# DNS failure, connection refused, handshake timeout: no verdict
entry["status"] = "unreachable"
entry["error"] = str(e) or type(e).__name__
return entry
entry.update(extract_meta(cert))
entry["covers_name"] = True # check_hostname just proved it
entry["valid"] = True
entry["status"] = "expiring" if entry["days_remaining"] <= warn_days else "ok"
return entry
def format_human(entries, warn_days):
lines = ["TLS Certificate Status (warn if <= %d days):" % warn_days]
for e in entries:
target = "%s:%d" % (e["domain"], e["port"])
if e["status"] == "ok":
lines.append(" %s OK - expires %s (%dd left)"
% (target, e["not_after"][:10], e["days_remaining"]))
elif e["status"] == "expiring":
lines.append(" %s EXPIRING - expires %s (%dd left)"
% (target, e["not_after"][:10], e["days_remaining"]))
elif e["status"] == "rejected":
lines.append(" %s REJECTED (%s) - %s"
% (target, e["reject_reason"], e["error"]))
else:
lines.append(" %s UNREACHABLE - %s" % (target, e["error"]))
return "\n".join(lines)
def validate_domain(parser, name):
if not name or len(name) > 253:
parser.error("invalid domain name: %r" % name)
for label in name.split("."):
if not 1 <= len(label) <= 63 or not LABEL_RE.fullmatch(label):
parser.error("invalid domain name: %r" % name)
def main():
parser = argparse.ArgumentParser(
prog="tls-check",
description="Verify TLS certificates of gladex.de host names "
"with a real handshake (stdlib only, no ACME API).",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
exit codes:
0 - every checked certificate is valid for more than --warn-days
1 - general error
2 - invalid arguments
3 - unreachable: DNS failure, connection refused, or handshake timeout
4 - expiring: valid, but notAfter is within --warn-days
5 - rejected: expired, hostname mismatch, or untrusted chain
""")
parser.add_argument("domains", nargs="*", metavar="DOMAIN",
help="host name to check (default: every name in "
"the gladex.de LE certificate)")
parser.add_argument("--format", choices=["json", "human"], default="human",
help="output format (default: human; json = one "
"compact object per line)")
parser.add_argument("--warn-days", type=int, default=30,
help="warn when the cert expires within N days "
"(default: %(default)s)")
parser.add_argument("--port", type=int, default=443,
help="TCP port to check (default: %(default)s; "
"e.g. 993=IMAPS, 465=SMTPS)")
parser.add_argument("--timeout", type=float, default=5.0,
help="connect/handshake timeout in seconds "
"(default: %(default)s)")
parser.add_argument("--ca-file", metavar="PEM", default=None,
help="extra CA bundle to trust in addition to the "
"system store")
args = parser.parse_args()
if not 1 <= args.port <= 65535:
parser.error("--port must be between 1 and 65535")
if args.timeout <= 0:
parser.error("--timeout must be > 0")
if args.warn_days < 0:
parser.error("--warn-days must be >= 0")
ctx = ssl.create_default_context()
if args.ca_file:
if not os.path.isfile(args.ca_file):
parser.error("--ca-file not found or not a file: %s" % args.ca_file)
try:
ctx.load_verify_locations(cafile=args.ca_file)
except (OSError, ssl.SSLError, ValueError) as e:
parser.error("--ca-file unusable: %s" % e)
domains = args.domains or DEFAULT_DOMAINS
for name in domains:
validate_domain(parser, name)
entries = [check_cert(name, args.port, args.timeout, ctx, args.warn_days)
for name in domains]
if args.format == "json":
for e in entries:
print(json.dumps(e, separators=(",", ":")))
else:
print(format_human(entries, args.warn_days))
if any(e["status"] == "rejected" for e in entries):
return EXIT_REJECTED
if any(e["status"] == "unreachable" for e in entries):
return EXIT_UNREACHABLE
if any(e["status"] == "expiring" for e in entries):
return EXIT_EXPIRING
return EXIT_OK
if __name__ == "__main__":
try:
code = main()
except KeyboardInterrupt:
sys.stderr.write("tls-check: interrupted\n")
code = EXIT_ERROR
except Exception as e:
sys.stderr.write("tls-check: %s: %s\n" % (type(e).__name__, e))
code = EXIT_ERROR
if code == EXIT_REJECTED:
# rejected is the verdict callers branch on: keep it a literal so
# `grep 'exit(5)' tools/tls-check` documents the contract
sys.exit(5)
sys.exit(code)
=== registry entry:
## tls-check
**Purpose**: Verify TLS certificates for gladex.de host names with a real handshake (stdlib only, no ACME API) — apex vhost, every sub domain, and the LE certs on the mail ports (465/993). Distinguishes "never reached a server" (3) from "server reached, certificate refused" (5), and **describes rejected certs** (cn/sans/dates re-fetched with verification off) instead of returning `cn=null` like the pre-2026-09-24 version did.
**Location**: `/data/repo/tools/tls-check`
**Usage**:
```
tls-check [domain...] [--format json|human] [--warn-days 30] [--port 443] [--timeout 5] [--ca-file PEM]
```
**Options**:
- `domain...` - Hosts to check (default: all 7 SANs of the gladex.de LE cert)
- `--format json|human` - Output format (default: human; json = one compact object per line, NDJSON)
- `--warn-days N` - Expiry warning threshold in days (default: 30)
- `--port N` - TCP port (default: 443; e.g. 993=IMAPS, 465=SMTPS)
- `--timeout S` - Connect/handshake timeout in seconds (default: 5; also bounds DNS-failure/refused paths)
- `--ca-file PEM` - Extra CA bundle trusted **in addition to** the system store (validated: missing/dir/garbage → exit 2)
- `--help` - Shows usage + exit-code table
**Exit codes**:
=== tests for tls:
test_tls_check.sh
=== newest changelog versions:
1791:## [0.4.64] - 2026-09-26 — `tests/test_app_contrast.php` section 6 dropped three checks per document in silence, and its guard admitted an unparseable token where `contrast(null, …)` aborted the whole suite with exit 255 and **no `=== Results:` line**: both now report as counted `skip()` behind a guard derived from the assertions themselves, pinned by a new 48-assertion meta-suite that runs the suite against broken fixtures; meta-suite 0 → 48, suite 185 → 185, total 38 suites/3155 → 39 suites/3203
1872:## [0.4.65] - 2026-09-26 — the regression total was produced by an uncommitted parser that guessed ONE summary format and reported `1854 passed / 0 failed` while listing 16 of 39 suites (twelve of them rc=0) as no-summary failures: `tools/regression-run` commits that parser, reads numbers rather than a format, and refuses to count an unparseable suite as green; suite 0 → 110, total 39/3203 → 40/3313
1938:## [0.4.66] - 2026-09-26 — the tool that produces every regression total could not say WHICH suite moved, so each entry attributed its delta by hand: `--save-baseline` / `--baseline` record per-suite counts and diff them mechanically, under a closure check that simultaneously proves the record agrees with itself; suite 110 → 193, total 40/3313 → 40/3396
2000:## [0.4.67] - 2026-09-26 — the next-candidate queue was written out TWICE, in two files that had already diverged, so an item could sit in one list and be re-proposed from the other with nothing versioned checking either copy: `agent-logs/PROGRESS.md` is now the single authoritative list, every `### Queue` section in this file is a pointer from here on with the 111 historical item lines frozen, and `tools/queue-source-check` enforces it as a `system-status` row; suite 0 → 107, total 40/3396 → 41/3503
> build · mimo-v2.6-flash-free
`tls-check`'s test pins its default list against the live SANs, so extending it means touching the test + docs + version together. Checking the pin:
[0m$ [0mcd /data/repo && grep -n "DEFAULT_DOMAINS\|SAN\|photos\|7 " tests/test_tls_check.sh | head -30; echo "=== who calls tls-check:"; grep -rn "tls-check" tools/system-status | head; echo "=== total assertions line:"; tail -5 tests/test_tls_check.sh
17:# 3. DEFAULT DOMAIN LIST was 5 of the cert's 7 SANs — team.gladex.de and
57:SANDBOX="$(mktemp -d "${TMPDIR:-/tmp}/opencode/tls-test.XXXXXX" 2>/dev/null)" ||
58: SANDBOX="$(mktemp -d /tmp/tls-test.XXXXXX)" || exit 1
63: rm -rf "$SANDBOX"
74:# good SAN localhost + IP 127.0.0.1, +90d, signed CA
75:# expiring SAN localhost, +10d, signed CA
76:# expired SAN localhost, -5d, signed CA
77:# wrongname SAN other.example, +90d, signed CA
78:# untrusted SAN localhost, +90d, signed OTHER_CA
79:python3 - "$SANDBOX" <<'CERTS'
135:cat > "$SANDBOX/serve.py" <<'SERVE'
166: local name="$1" mode="$2" pf="$SANDBOX/port.$1" i=0
169: python3 "$SANDBOX/serve.py" hang "$pf" >/dev/null 2>&1 &
171: python3 "$SANDBOX/serve.py" tls "$SANDBOX/$3.pem" "$SANDBOX/$3.key" "$pf" >/dev/null 2>&1 &
186:CA="$SANDBOX/ca.pem"
187:GARBAGE="$SANDBOX/not-a-cert.pem"; echo "not a cert" > "$GARBAGE"
215:run_tool --ca-file "$SANDBOX" localhost
240:assert_has "SAN list carries localhost" '"sans":["localhost","127.0.0.1"]' "$OUT"
350:echo "=== 12. defaults = all 7 cert SANs; dead --help branch gone ==="
359: if isinstance(t, ast.Name) and t.id == "DEFAULT_DOMAINS":
366:assert_has "DEFAULT_DOMAINS == the cert's 7 SANs (pre-fix: 5)" "OK" "$ast"
371:MUT="$SANDBOX/mutant-tls"
416:[ "$n" = "1" ] && ok "M4 precondition: team.gladex.de occurs exactly once (in DEFAULT_DOMAINS)" \
427: if isinstance(t, ast.Name) and t.id == "DEFAULT_DOMAINS":
441: if isinstance(n, ast.Assign) and any(getattr(t,"id","") == "DEFAULT_DOMAINS" for t in n.targets):
452:[ "$nl" = "7" ] && ok "live run checks all 7 default domains (got $nl)" \
453: || bad "live run checks all 7 default domains (got $nl)"
=== who calls tls-check:
=== total assertions line:
echo ""
echo "=== Results: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ] || exit 1
exit 0
> build · mimo-v2.6-flash-free
Decision: that test pins "7" in five places (assertions + mutation precondition) — a proper second step, so I'll **queue** it rather than half-do it and break the pin. Running the full regression for this run's numbers:
[0m$ [0mcd /data/repo && ./tools/system-status --format json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('checks:', len(d.get('checks',d.get('results',[]))), 'verdict:', d.get('verdict') or d.get('status'), 'failures:', [c for c in d.get('checks',d.get('results',[])) if str(c.get('status','')).upper() not in ('OK','WARN','PASS')][:3])" 2>&1 | head -5; echo "---"; ./tools/system-status --format human 2>&1 | grep -Ei 'overall|checks'
checks: 33 verdict: None failures: [{'name': 'SOA:gladex.de', 'status': 'warning', 'detail': 'serial=2026092602 consistent on 1.1.1.1+8.8.8.8; mname=placeholder (NEEDS-INVESTOR open)'}, {'name': 'git-tree', 'status': 'warning', 'detail': '2 uncommitted changes'}]
---
Overall: ALL SYSTEMS HEALTHY
Select another run log from the list above. Only files matching run-YYYYMMDD-HHMMSS-N.log are readable.
App log tail — prod-8001.log (last 60 lines)
[Sat Sep 26 07:57:22 2026] 127.0.0.1:33076 Accepted [Sat Sep 26 07:57:22 2026] 127.0.0.1:33076 Closing [Sat Sep 26 07:57:22 2026] 127.0.0.1:33086 Accepted [Sat Sep 26 07:57:22 2026] 127.0.0.1:33086 Closing [Sat Sep 26 07:57:22 2026] 127.0.0.1:33088 Accepted [Sat Sep 26 07:57:22 2026] 127.0.0.1:33088 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58708 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58708 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58724 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58724 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58736 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58736 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58738 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58738 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58752 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58752 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58766 Accepted [Sat Sep 26 07:57:36 2026] 127.0.0.1:58766 Closing [Sat Sep 26 07:57:36 2026] 127.0.0.1:58782 Accepted [Sat Sep 26 07:57:37 2026] 127.0.0.1:58782 Closing [Sat Sep 26 07:57:37 2026] 127.0.0.1:58798 Accepted [Sat Sep 26 07:57:37 2026] 127.0.0.1:58798 Closing [Sat Sep 26 07:57:37 2026] 127.0.0.1:58804 Accepted [Sat Sep 26 07:57:37 2026] 127.0.0.1:58804 Closing [Sat Sep 26 07:57:37 2026] 127.0.0.1:58806 Accepted [Sat Sep 26 07:57:37 2026] 127.0.0.1:58806 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58820 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58820 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58828 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58828 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58836 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58836 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58840 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58840 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58852 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58852 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58866 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58866 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58878 Accepted [Sat Sep 26 07:57:38 2026] 127.0.0.1:58878 Closing [Sat Sep 26 07:57:38 2026] 127.0.0.1:58880 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58880 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58888 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58888 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58904 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58904 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58908 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58908 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58924 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58924 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58936 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58936 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58950 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58950 Closing [Sat Sep 26 07:57:39 2026] 127.0.0.1:58956 Accepted [Sat Sep 26 07:57:39 2026] 127.0.0.1:58956 Closing [Sat Sep 26 07:59:55 2026] 127.0.0.1:41890 Accepted [Sat Sep 26 07:59:55 2026] 127.0.0.1:41890 Closing [Sat Sep 26 07:59:56 2026] 127.0.0.1:41900 Accepted
Generated 2026-09-26 05:59:56 UTC · Gladex.de