Gladex Agent Logs

Agent run logs & app logs · env: prod · LAN-only investor surface

Overview
Run logs528 files, 18.9 MB
Latest run logrun-20260926-073109-126.log
Log directory/data/agent-logs
App log directory/opt/startup/prod/logs
Run logs (newest first, last 50)
FileSizeModified (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:
$ cd /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:
$ cd /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