Skip to content

fix(#166, #168): installer survives corporate proxy + sanitizes credential input - #167

Merged
saadqbal merged 10 commits into
developfrom
fix/166-installer-corporate-proxy
Jun 1, 2026
Merged

fix(#166, #168): installer survives corporate proxy + sanitizes credential input#167
saadqbal merged 10 commits into
developfrom
fix/166-installer-corporate-proxy

Conversation

@saadqbal

@saadqbalsaadqbal commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Two installer-side fixes bundled together (both surfaced while validating the corporate-proxy fix):

  1. #166i.sh fails behind corporate HTTP/HTTPS proxies (a customer install, P1 customer blocker). Four bugs in scripts/lib/cluster.sh.
  2. #168 — Helm rejects the generated values.yaml with "control characters are not allowed" when bracketed-paste-enabled terminals leak ESC sequences into clientId/clientPassword. One bug in scripts/lib/install-client-helm.sh.

#166 — corporate-proxy fixes (scripts/lib/cluster.sh)

  1. k3d API bound to 0.0.0.0 — corporate proxies intercept the 0.0.0.0 connection and return Temporary Redirect to kubectl. Pin explicitly to 127.0.0.1:6550.
  2. No proxy propagation to k3d containersHTTP_PROXY/HTTPS_PROXY/NO_PROXY on the host weren't passed into k3s. Loop over both cases; skip vars whose value contains @ (k3d's filter-syntax conflict — recreate wouldn't help).
  3. _merge_kubeconfig clobbered manual sed fix on re-run — defensive https://0.0.0.0:https://127.0.0.1: rewrite after the merge. Targets the file k3d actually writes to (first entry of colon-separated KUBECONFIG, or ~/.kube/config).
  4. Misleading "Check Docker" error — replaced with a three-cause diagnostic naming the real culprits, with the ${KUBECONFIG} path in the workaround hint and macOS-compatible sed -i.bak form.

Also adds _check_existing_cluster_proxy: docker inspects the server container on re-run and warns per-var (including NO_PROXY drift) when the cluster is missing proxy env the host has. @-containing values are excluded since recreate wouldn't bake them either.

#168 — credential sanitization (scripts/lib/install-client-helm.sh)

  • New _sanitize_credential strips C0 controls (0x00-0x1F) + DEL (0x7F) from TB_CLIENT_ID / TB_CLIENT_PASSWORD after every read. UTF-8 bytes (0x80+) preserved so international passwords work. Warns the user when anything was stripped.
  • _extract_yaml_value also strips on read so a previously-corrupted values.yaml self-heals on the next install (without it, the default-fill on re-run reproduces the bad value).

Chart version

client/Chart.yaml bumped 1.4.1 → 1.4.2 per #166 acceptance criteria.

Backward compatibility

  • Bug 1: localhost-only API bind. Only affects users connecting to k3d's API from another host on their LAN — vanishingly rare for a dev-machine installer. Existing kubeconfigs with 0.0.0.0:6550 get auto-normalized on the next run.
  • Bug 2: pure additive. Loop guarded by [[ -n "${!var:-}" ]] — strict no-op when no proxy is set.
  • Bug 3: sed pattern anchored to https://0\.0\.0\.0: — won't touch CIDR ranges. sed -i.bak works on BSD + GNU.
  • Bug 4: cosmetic text only.
  • bug: installer values.yaml rejected by Helm — control chars from credential paste #168 sanitization: strips only non-printable C0 + DEL. Valid credentials (printable ASCII + UTF-8) pass through unchanged.

Validation

  • bash -n clean on both touched files.
  • shellcheck -x -s bash shows only two pre-existing SC2034 warnings on cluster.sh (unused logfile, attempt — present before this PR).
  • Verified no other repo file encodes 0.0.0.0:6550 (CIDRs in NetworkPolicy templates unchanged).

Test plan

Follow-ups (deferred per issues)

Closes#166
Closes#168

🤖 Generated with Claude Code


Note

Medium Risk
Changes affect every install path (k3d API bind, kubeconfig rewrite, proxy env) though defaults stay localhost-only when no proxy is set; credential stripping could alter unusual passwords that relied on control characters.

Overview
The installer and chart release are updated so installs work behind corporate HTTP proxies and credential prompts no longer poison values.yaml.

cluster.sh binds the k3d API to 127.0.0.1:6550, forwards host HTTP_PROXY / HTTPS_PROXY / NO_PROXY (both cases) into new clusters via k3d --env, and warns when proxy URLs contain @ or when an existing cluster’s env drifts from the host. After kubeconfig merge, https://0.0.0.0: server URLs are rewritten to 127.0.0.1; API wait failures now point at Docker, proxy NO_PROXY, and kubeconfig fixes.

install-client-helm.sh strips bracketed-paste / CSI escapes and C0 controls from client ID/password (and when re-reading values.yaml) so Helm no longer rejects “control characters.”

client/Chart.yaml is 1.4.1 → 1.4.2; Helm CI also runs on develop.

Reviewed by Cursor Bugbot for commit 0eab8ed. Bugbot is set up for automated code reviews on this repo. Configure here.

Four bugs caused i.sh to fail behind corporate proxies (surfaced during
a Charité install — P1 customer blocker):
1. k3d --api-port defaulted to 0.0.0.0:6550 — corporate proxies
intercept the 0.0.0.0 connection. Pin explicitly to 127.0.0.1:6550.
2. HTTP_PROXY/HTTPS_PROXY/NO_PROXY never propagated to k3d containers,
so k3s couldn't reach external registries. Propagate when set
(no-op when unset — fully backward compatible).
3. _merge_kubeconfig clobbered manual sed fixes on every re-run.
Apply a defensive 0.0.0.0 → 127.0.0.1 normalization after the merge,
anchored to https://0.0.0.0: so CIDR ranges are untouched.
4. _wait_for_api error message blamed Docker when the real cause was
the API being unreachable. Rewrite with the three actual causes.
Also bump client chart to v1.4.2.
Closes#166
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@saadqbalsaadqbal self-assigned this Jun 1, 2026
@LukasWodka

Copy link
Copy Markdown
Contributor

👋 Heads-up — Code review queue is at 11 / 8

Above the WIP limit. The team convention is to review existing PRs before opening new work.

Open PRs currently in Code review (oldest first):

Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.)

Comment threadscripts/lib/cluster.sh
Comment threadscripts/lib/cluster.sh
saadqbaland others added 2 commits June 1, 2026 12:10
BSD sed (macOS) requires an argument to -i; the previous workaround
text would error out for the audience most likely to see this message
(local dev installs on macOS). Use the `sed -i.bak ... && rm ...bak`
form, which works on both BSD and GNU sed — same form the script itself
uses for the Bug 3 normalization.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two bugbot findings on PR #167:
1. (high) k3d's --env KEY=VALUE@NODEFILTER syntax breaks when the proxy
value itself contains '@' (e.g. embedded creds: user:pass@host) —
k3d cluster create fails before the cluster exists. Detect '@' in
the value, skip propagation for that var, and warn the user with
actionable guidance (strip creds, or configure proxy auth inside
the cluster).
2. (medium) Proxy env was only injected on fresh cluster creation;
re-runs against an existing cluster left registries unreachable
because k3d bakes --env into containers at create time. Add
_check_existing_cluster_proxy: docker-inspect the server container,
compare against host proxy env, warn the user to delete+recreate
when the cluster is missing proxy env. Silent no-op when Docker is
down or no proxy env is set on the host.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment threadscripts/lib/cluster.sh Outdated
Comment threadscripts/lib/cluster.sh
Two more bugbot findings on 8a12455:
1. (medium) The Bug 4 error message hardcoded ~/.kube/config in the
(c) sed-workaround hint, but _merge_kubeconfig honors $KUBECONFIG.
Users with a custom KUBECONFIG were being directed to the wrong
file. Use ${KUBECONFIG:-~/.kube/config}, and if it's colon-separated
(multi-file), point at the first entry.
2. (medium) _check_existing_cluster_proxy only checked HTTP/S_PROXY in
the cluster env, so a partial mismatch (cluster has HTTP_PROXY but
not NO_PROXY) silently passed the gate. Switch to a per-var check:
for each proxy var set on the host, verify the cluster has it, and
list specifically which ones are missing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment threadscripts/lib/cluster.sh
Comment threadscripts/lib/cluster.sh
…malize
Two consistency bugs introduced by earlier follow-ups on 5ad0c96:
1. (medium) _check_existing_cluster_proxy flagged any host proxy var
missing from the cluster as drift — including '@'-containing values
that _create_new_cluster deliberately skips. The advice ("delete +
recreate") wouldn't bake them either; the warning was lying. Filter
'@' values out of the drift list, matching what propagation actually
does. Users with embedded-creds proxies already saw the skip warning
at first-create time.
2. (medium) _merge_kubeconfig's defensive normalization checked
`-f "$KUBECONFIG"`, which is false for a colon-separated path list.
k3d's --kubeconfig-merge-default writes into the first entry of
KUBECONFIG (or ~/.kube/config if unset), so the rewrite needs to
target that file. Resolve with ${kc%%:*}, same idiom we use in the
_wait_for_api error hint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aptracebloc
aptracebloc previously approved these changes Jun 1, 2026
Helm rejects values.yaml with 'control characters are not allowed' when
the user's terminal has bracketed-paste enabled and they paste creds:
the paste is wrapped in ESC[200~ ... ESC[201~ (ESC = 0x1B), which `read`
captures verbatim and the heredoc embeds into clientId/clientPassword.
Surfaced today during an EC2 install:
Error: failed to parse /home/ec2-user/.tracebloc/values.yaml:
error converting YAML to JSON: yaml: control characters are not allowed
Fix:
- New _sanitize_credential helper strips 0x00-0x1F and 0x7F from the
string. UTF-8 bytes (0x80+) preserved so international passwords work.
- Apply after every TB_CLIENT_ID / TB_CLIENT_PASSWORD read, before the
empty check and before the YAML single-quote escape.
- Also strip in _extract_yaml_value so a previously-corrupted values.yaml
self-heals on the next install instead of perpetuating the bad value.
- Warn the user when anything was stripped so they know we touched their
input.
Closes#168
Independent of #166 but bundled here at the user's request.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@saadqbalsaadqbal changed the title fix(#166): installer survives corporate HTTP/HTTPS proxyfix(#166, #168): installer survives corporate proxy + sanitizes credential inputJun 1, 2026
saadqbaland others added 2 commits June 1, 2026 14:16
Two regressions in c237721:
1. `warn` in _sanitize_credential wrote to STDOUT, but the function is
called from `TB_CLIENT_PASSWORD=$(_sanitize_credential "$PW")` —
command substitution captures stdout, so the warning message landed
inside the password value and ended up in values.yaml verbatim.
Surfaced when a customer's clientPassword became literally:
' ⚠ Stripped non-printable characters from input...
[CrypveP-sumwac-5siqra'
Fix: warn explicitly to >&2 in this caller. Keeps the global warn()
behavior unchanged (other callers print to stdout intentionally).
2. The C0-strip removed the ESC byte but left the literal `[200~` /
`[201~` bracketed-paste markers visible in the value. Add a shared
_strip_paste_garbage helper that matches both the ESC-prefixed form
and the standalone-marker form, then C0-strips the remainder. Use it
from both _sanitize_credential (input path) and _extract_yaml_value
(defaults-from-prior-file path) so self-heal works against either
corruption shape.
Verified with bash unit tests against four input shapes (full wrapper,
literal-marker remnant, clean input, capture-isolation check).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Customer report: password 'rypveP-sumwac-5siqra' stored as
'[CrypveP-sumwac-5siqra'. The leading '[C' is the post-C0-strip
remnant of ESC[C (cursor-right arrow key) — a stray arrow key press
during the password prompt. d22a381 only matched ESC[200~/ESC[201~
(bracketed paste), so generic CSI sequences still leaked through.
Replace the narrow paste-marker strip with an iterative match over the
full ANSI CSI shape: ESC '[' <params> <final-byte>
where params ∈ [0-9;] and final ∈ [A-Za-z~]. Catches arrow keys,
cursor moves, function keys, modifier-combo CSIs, and bracketed paste
— anything `read -r -s` can capture from a terminal.
Keep the standalone-marker self-heal narrow (only [200~/[201~);
generic `[X]` shapes could plausibly be real password content.
Verified against eight input shapes:
bracketed-paste wrapper, cursor-right prefix, mid-string arrow keys,
multi-param CSI (Ctrl-Right), orphan markers, clean ASCII (no-op),
UTF-8 preservation, warn-to-stderr isolation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aptracebloc
aptracebloc previously approved these changes Jun 1, 2026
The Helm Chart CI workflow (Lint, Template render, Unit tests, Schema
validation) only triggered on main- and openshift-targeted PRs. The
required-check ruleset on develop still expects the `Lint` context, so
develop-targeted PRs land in "Expected — Waiting for status to be
reported" forever and can't merge.
Adding `develop` to both push and pull_request branch filters fixes
this PR's merge block and closes the gap going forward — chart issues
will be caught at the develop integration point rather than slipping
through until the develop→main release sync.
Paths filter unchanged: only PRs that touch client/**, ingestor/**, or
this workflow file itself trigger CI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9257a74. Configure here.

Comment threadscripts/lib/cluster.sh Outdated
aptracebloc
aptracebloc previously approved these changes Jun 1, 2026
Previous round filtered '@'-containing host proxy vars out of the drift
check (correctly — recreating wouldn't bake them). But if the host has
ONLY @-style proxy URLs, the function went silent on every re-run. The
create-time warn in _create_new_cluster doesn't fire on the existing-
cluster path, so the user got zero installer signal that image pulls
might fail.
Partition host vars into two buckets:
• drift_candidates — clean values; cluster should have these
• at_skipped — @-values; recreate alone won't bake them
@-skipped vars now get their own dedicated warning that fires on every
re-run, with remedies that actually work (strip creds, or front the
corporate proxy with a credential-less local auth-proxy). Drift check
runs unchanged against the clean-value bucket.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@saadqbal
saadqbal merged commit b6b077b into developJun 1, 2026
11 checks passed
saadqbal added a commit that referenced this pull request Jun 1, 2026
…oxy (#169)
fix(#166, #168): installer survives corporate proxy + sanitizes credential input
LukasWodka added a commit that referenced this pull request Jun 1, 2026
… 0.0.0.0 detect, Windows parity)
A customer running behind an authenticated corporate HTTP proxy hit install
failures. The 0.0.0.0 kubeconfig headline was fixed for the bash path in
#166/#167, but adverse testing (a forward proxy + real k3d on Linux VMs)
surfaced three remaining gaps plus a Windows parity hole:
- Gap A: authenticated proxies (http://user:pass@host) were silently SKIPPED —
k3d's --env KEY=VALUE@FILTER can't carry an '@' in the value. Now propagated
via a k3d --config file (structured YAML env) so credentials survive intact.
Verified on k3d v5.8.3 (it merges the --config env with the existing CLI flags).
- Gap B: NO_PROXY was propagated verbatim. Now auto-augmented with the
cluster-internal ranges (loopback + RFC1918 + .svc/.cluster.local +
host.k3d.internal), both into the cluster and host-side, so in-cluster traffic
never routes through the proxy — fixes the misroute AND the observed
`k3d cluster create --wait` hang.
- Gap C: a cluster created outside the installer and bound to 0.0.0.0 is now
detected (serverlb HostIp) and flagged with a non-destructive recreate remedy.
- Windows parity: install-k8s.ps1::New-K3dCluster had NONE of the bash fixes —
it still bound --api-port 0.0.0.0:6550 (the original headline bug, still live
on Windows), normalized only host.docker.internal in the kubeconfig, and
propagated zero proxy env. Now mirrors bash: 127.0.0.1:6550, a 0.0.0.0->127.0.0.1
kubeconfig rewrite, and Get-EffectiveNoProxy + Write-K3dProxyConfig (auth +
augmented NO_PROXY, written UTF-8 without a BOM).
Tests: new scripts/tests/cluster.bats (15) + Pester for the two ps1 helpers (6),
both green. Verified end-to-end on Linux VMs: auth creds propagated into the node,
no startup hang behind an unreachable proxy, and 0.0.0.0 detection firing.
Stacked on #171 (the installer test scaffolding + final install-k8s.ps1 live there).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
saadqbal pushed a commit that referenced this pull request Jun 2, 2026
… 0.0.0.0 detect, Windows parity)
A customer running behind an authenticated corporate HTTP proxy hit install
failures. The 0.0.0.0 kubeconfig headline was fixed for the bash path in
#166/#167, but adverse testing (a forward proxy + real k3d on Linux VMs)
surfaced three remaining gaps plus a Windows parity hole:
- Gap A: authenticated proxies (http://user:pass@host) were silently SKIPPED —
k3d's --env KEY=VALUE@FILTER can't carry an '@' in the value. Now propagated
via a k3d --config file (structured YAML env) so credentials survive intact.
Verified on k3d v5.8.3 (it merges the --config env with the existing CLI flags).
- Gap B: NO_PROXY was propagated verbatim. Now auto-augmented with the
cluster-internal ranges (loopback + RFC1918 + .svc/.cluster.local +
host.k3d.internal), both into the cluster and host-side, so in-cluster traffic
never routes through the proxy — fixes the misroute AND the observed
`k3d cluster create --wait` hang.
- Gap C: a cluster created outside the installer and bound to 0.0.0.0 is now
detected (serverlb HostIp) and flagged with a non-destructive recreate remedy.
- Windows parity: install-k8s.ps1::New-K3dCluster had NONE of the bash fixes —
it still bound --api-port 0.0.0.0:6550 (the original headline bug, still live
on Windows), normalized only host.docker.internal in the kubeconfig, and
propagated zero proxy env. Now mirrors bash: 127.0.0.1:6550, a 0.0.0.0->127.0.0.1
kubeconfig rewrite, and Get-EffectiveNoProxy + Write-K3dProxyConfig (auth +
augmented NO_PROXY, written UTF-8 without a BOM).
Tests: new scripts/tests/cluster.bats (15) + Pester for the two ps1 helpers (6),
both green. Verified end-to-end on Linux VMs: auth creds propagated into the node,
no startup hang behind an unreachable proxy, and 0.0.0.0 detection firing.
Stacked on #171 (the installer test scaffolding + final install-k8s.ps1 live there).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
saadqbal pushed a commit that referenced this pull request Jun 2, 2026
… 0.0.0.0 detect, Windows parity) (#172)
A customer running behind an authenticated corporate HTTP proxy hit install
failures. The 0.0.0.0 kubeconfig headline was fixed for the bash path in
#166/#167, but adverse testing (a forward proxy + real k3d on Linux VMs)
surfaced three remaining gaps plus a Windows parity hole:
- Gap A: authenticated proxies (http://user:pass@host) were silently SKIPPED —
k3d's --env KEY=VALUE@FILTER can't carry an '@' in the value. Now propagated
via a k3d --config file (structured YAML env) so credentials survive intact.
Verified on k3d v5.8.3 (it merges the --config env with the existing CLI flags).
- Gap B: NO_PROXY was propagated verbatim. Now auto-augmented with the
cluster-internal ranges (loopback + RFC1918 + .svc/.cluster.local +
host.k3d.internal), both into the cluster and host-side, so in-cluster traffic
never routes through the proxy — fixes the misroute AND the observed
`k3d cluster create --wait` hang.
- Gap C: a cluster created outside the installer and bound to 0.0.0.0 is now
detected (serverlb HostIp) and flagged with a non-destructive recreate remedy.
- Windows parity: install-k8s.ps1::New-K3dCluster had NONE of the bash fixes —
it still bound --api-port 0.0.0.0:6550 (the original headline bug, still live
on Windows), normalized only host.docker.internal in the kubeconfig, and
propagated zero proxy env. Now mirrors bash: 127.0.0.1:6550, a 0.0.0.0->127.0.0.1
kubeconfig rewrite, and Get-EffectiveNoProxy + Write-K3dProxyConfig (auth +
augmented NO_PROXY, written UTF-8 without a BOM).
Tests: new scripts/tests/cluster.bats (15) + Pester for the two ps1 helpers (6),
both green. Verified end-to-end on Linux VMs: auth creds propagated into the node,
no startup hang behind an unreachable proxy, and 0.0.0.0 detection firing.
Stacked on #171 (the installer test scaffolding + final install-k8s.ps1 live there).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
saadqbal added a commit that referenced this pull request Jun 2, 2026
* Sync main → develop after v1.4.2 release (#170)
* Installer: verify readiness & credentials before reporting success; RHEL-family support (#171)
* fix(installer): run on RHEL-family Linux (#718, #719, #720)
Docker: install docker-ce from the official Docker CentOS dnf repo on AlmaLinux/Rocky/Oracle, which get.docker.com rejects as unsupported. k3d: preserve PATH through sudo so the post-install lookup survives RHEL secure_path (which omits /usr/local/bin). conntrack: use the conntrack apt package on Debian/Ubuntu and conntrack-tools elsewhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(installer): verify credentials and readiness before reporting success (#716, #717)
Credentials entered at the prompt are validated against the backend api-token-auth endpoint (the same call jobs-manager makes) with a re-prompt loop, so a wrong Client ID or password is caught immediately instead of after a full deploy. After helm apply, wait_for_client_ready polls rollout status and classifies the outcome; print_summary reports connected, starting, bad_creds, image_pull or crash, and prints the data-never-leaves message only when the client is verifiably connected. Exit code now reflects the real outcome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(installer): mirror credential and readiness checks on Windows (#716, #717)
Test-Credentials, Wait-ForClientReady and Get-NotReadyState mirror the bash logic in install-k8s.ps1, and Print-Summary is now state-branched. Validated with the PowerShell 7.4 parser; runtime behavior still needs a check on a Windows host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(installer): bats + Pester unit suites, wired into CI
scripts/tests/: 64 bats tests (summary, install-client-helm, setup-linux, common) + 32 Pester tests for install-k8s.ps1 — all mocked, no Docker/k3d/network needed. Changed-line coverage measured with kcov (bash 96.2%) and Pester (PowerShell 97.4%); residual lines are the real RHEL Docker-install commands + the guarded main() orchestration, exercised by the integration E2E. A TB_PESTER guard lets the suite dot-source install-k8s.ps1 without running the installer. New installer-tests job in helm-ci.yaml runs both suites on PRs (scripts/ added to path filters).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): harden corporate-proxy support (auth proxy, NO_PROXY, 0.0.0.0 detect, Windows parity) (#172)
A customer running behind an authenticated corporate HTTP proxy hit install
failures. The 0.0.0.0 kubeconfig headline was fixed for the bash path in
#166/#167, but adverse testing (a forward proxy + real k3d on Linux VMs)
surfaced three remaining gaps plus a Windows parity hole:
- Gap A: authenticated proxies (http://user:pass@host) were silently SKIPPED —
k3d's --env KEY=VALUE@FILTER can't carry an '@' in the value. Now propagated
via a k3d --config file (structured YAML env) so credentials survive intact.
Verified on k3d v5.8.3 (it merges the --config env with the existing CLI flags).
- Gap B: NO_PROXY was propagated verbatim. Now auto-augmented with the
cluster-internal ranges (loopback + RFC1918 + .svc/.cluster.local +
host.k3d.internal), both into the cluster and host-side, so in-cluster traffic
never routes through the proxy — fixes the misroute AND the observed
`k3d cluster create --wait` hang.
- Gap C: a cluster created outside the installer and bound to 0.0.0.0 is now
detected (serverlb HostIp) and flagged with a non-destructive recreate remedy.
- Windows parity: install-k8s.ps1::New-K3dCluster had NONE of the bash fixes —
it still bound --api-port 0.0.0.0:6550 (the original headline bug, still live
on Windows), normalized only host.docker.internal in the kubeconfig, and
propagated zero proxy env. Now mirrors bash: 127.0.0.1:6550, a 0.0.0.0->127.0.0.1
kubeconfig rewrite, and Get-EffectiveNoProxy + Write-K3dProxyConfig (auth +
augmented NO_PROXY, written UTF-8 without a BOM).
Tests: new scripts/tests/cluster.bats (15) + Pester for the two ps1 helpers (6),
both green. Verified end-to-end on Linux VMs: auth creds propagated into the node,
no startup hang behind an unreachable proxy, and 0.0.0.0 detection firing.
Stacked on #171 (the installer test scaffolding + final install-k8s.ps1 live there).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(installer): preflight gate (arch, egress, disk, RAM, CPU) — fail fast, clearly (#173)
Most install failures fall into two shapes: the environment can't support what
the installer does and it fails CRYPTICALLY minutes in, or it claims success it
hasn't earned. #171/#172 fixed specific cases; this attacks the first pattern
systematically with a preflight gate that runs before any install/cluster work
and fails in seconds with a precise, actionable reason.
scripts/lib/preflight.sh — run_preflight() runs at the top of Step 1/4:
- Architecture (arm64 guard): the tracebloc client images (e.g. mysql-client)
are amd64-only. amd64 -> ok; arm64 on Docker Desktop -> info (emulated); arm64
Linux without QEMU binfmt -> hard fail with the `tonistiigi/binfmt --install
amd64` remedy. Override: TRACEBLOC_ALLOW_ARM64=1. (This is exactly the
`exec format error` we hit on arm64.)
- Egress connectivity: probes the endpoints the install needs and reports which
are blocked. Hard-fail on the criticals (registry-1.docker.io, ghcr.io, the
CLIENT_ENV backend, tracebloc.github.io); warn-only on tool-download hosts
when the tool isn't already installed. A TLS/cert error emits a
break-and-inspect proxy hint. The probe honors HTTP_PROXY.
- Disk / RAM / CPU: hard-fail on critically low disk; warn on low disk/RAM/CPU.
- Aggregates results (runs ALL checks, then exits once with a summary). Escape
hatch: TRACEBLOC_SKIP_PREFLIGHT=1.
install-k8s.sh sources + calls run_preflight; install.sh adds preflight.sh to the
curl-bootstrap download manifest (verified: every sourced lib is downloaded).
install-k8s.ps1: Test-Preflight + Get-Pf* helpers mirror the bash logic for
Windows (Get-CimInstance disk/RAM/CPU, Invoke-WebRequest connectivity).
docs/INSTALL.md: a "Network requirements (egress allowlist)" section the preflight
error points users to.
Tests: scripts/tests/preflight.bats (21) + Pester Describes (Test-PfUrl,
Test-Preflight; the Get-Cim* readers are Windows-only so they skip off-Windows).
Verified end-to-end on a Linux VM: healthy run passes; a blocked ghcr.io fails in
~1s naming the host; arm64 without binfmt fails with the remedy.
Stacked on #171 + #172.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(installer): guarantee reboot persistence + make it visible (#174)
A real VM reboot test showed the k3d cluster already auto-recovers on Linux
(k3d sets --restart unless-stopped on its nodes; the Docker install enables
docker.service on boot). So no systemd unit is needed — this GUARANTEES that
behavior even on edge setups, and tells the user about it:
- cluster.sh: new ensure_cluster_autostart() (called from create_cluster) --
`docker update --restart unless-stopped` on the k3d nodes (covers
externally-created clusters / a future k3d default change) + `systemctl enable
docker` on Linux (covers the installed-but-disabled re-run case the
fresh-install path misses). Opt out with TRACEBLOC_NO_AUTOSTART=1.
- summary.sh: _reboot_note() in the connected summary -- Linux: "Survives
reboot"; macOS/Windows: enable Docker Desktop start-on-login.
- install-k8s.ps1: Set-ClusterAutostart (defensive unless-stopped on the nodes)
+ the Docker Desktop reboot note in Print-Summary.
Tests: cluster.bats (+4), summary.bats (+3), Pester (+2) -- all green.
Verified by a real reboot on a Linux VM: with the restart policy stripped
(policy=no), ensure_cluster_autostart restored it to unless-stopped + enabled
docker; after `limactl stop/start`, the cluster, node, AND a deployed workload
all returned Running with no intervention.
Stacked on #171 + #172 + #173.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(installer): --diagnose support bundle (redacted) (#175)
* feat(installer): --diagnose support bundle (redacted)
Adds a one-command support bundle so a customer hitting an install/runtime
problem can send a single file instead of a multi-round log-gathering email
thread (the Charité thread is the archetype). `bash <(curl ... i.sh) --diagnose`
(or `install-k8s.ps1 -Diagnose`) collects logs + cluster/host status into
~/.tracebloc/tracebloc-diagnose-<ts>.tgz.
Two guarantees:
- Best-effort: the whole collection runs under `set +e` and short-circuits
before any install work, so it works even when the install is broken.
- Credential-safe: clientPassword, proxy credentials (user:pass@host), and
password=/token/secret values are REDACTED from every file before archiving;
clientId is kept (it's the identifier support needs, not a secret).
scripts/lib/diagnose.sh -- run_diagnose() (host/versions, docker/k3d, kubectl
overview + describe of non-Running pods, workload logs with namespace
auto-discovery, helm, install log + values.yaml, proxy env) + _redact_file().
install-k8s.sh sources it + adds the --diagnose short-circuit (clears the EXIT
trap so the post-install message doesn't fire); install.sh adds it to the
bootstrap download manifest. install-k8s.ps1 mirrors with -Diagnose /
Invoke-DiagnoseBundle / Edit-Redaction. Documented in --help.
Tests: diagnose.bats (7, incl. the end-to-end redaction gate) + Pester (+3).
Verified on a Linux VM: the real --diagnose flag produced a 16-file bundle and
the seeded dev password + proxy credentials had ZERO occurrences in the archive
(clientId kept); also works with no cluster present.
Stacked on #171 + #172 + #173 + #174.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(installer): raise changed-line coverage (diagnose collection + real preflight probes)
Measured changed-line coverage of the stack had dropped (bash ~84% vs #171's
~96%) because the new code added integration-only branches the mocked unit
suites skipped. Recover the unit-testable portion:
- diagnose.bats: exercise the kubectl/docker/helm collection path (has()=true +
mocked tools) -> diagnose.sh 64% -> 90%.
- preflight.bats: test the REAL _pf_probe_url curl-exit-code -> token mapping,
the missing-curl path, and the _pf_ncpu/_pf_total_mem_kb/_pf_free_kb readers
(re-sourced past the setup stubs) -> preflight.sh 79% -> 90%.
Bash changed-line coverage: 83.6% -> 92.3% (kcov, 383/415). The residual ~8% is
integration-only (real k3d/docker create + macOS/Windows-specific branches +
MAIN orchestration), validated by the live VM E2Es (reboot recovery, auth-proxy,
preflight blocked-egress/arm64, diagnose redaction grep).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): close two redaction gaps in --diagnose (security review)
A pre-merge security review of the support bundle found two ways credentials
could land in the (supposedly redacted) archive the customer sends to support:
1. Redaction only matched `clientPassword:` and `password=` -- it missed any
other *password key in colon form, so `dockerRegistry.password` (a registry
token) and `HTTP_PROXY_PASSWORD` survived. Broadened _redact_file (bash) and
Edit-Redaction (ps1) to redact ANY *password key, case-insensitive, in : or =
form (portable explicit char classes -- BSD sed has no I flag).
2. (bash only) The bundle collected `helm get manifest`, which renders the k8s
Secret objects with base64-encoded CLIENT_PASSWORD + .dockerconfigjson that
text redaction can't see. Dropped the manifest collection (helm get values +
kubectl output already cover triage).
Regression tests added (diagnose.bats + Pester). Re-verified end-to-end on a
Linux VM with the real --diagnose flag: clientPassword, the dockerRegistry
token, HTTP_PROXY_PASSWORD, and proxy URL creds all have ZERO occurrences in the
archive; clientId kept; manifest no longer collected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): correctness fixes from code review
A pre-merge correctness review (high effort) over the stack found three real
bugs — the failure paths weren't exercised by the passing tests:
1. cluster.sh: `k3d "${K3D_ARGS[@]}" > ...` was a bare command under `set -e`,
so a k3d-create FAILURE aborted the script immediately, skipping the
'already exists' graceful reuse, the error dump, AND the proxy temp-dir
cleanup. Capture rc set-e-safely (`&& create_rc=0 || create_rc=$?`).
Proven under set -e: both the error-dump and reuse paths now run.
2. summary.sh: CLIENT_STATE was defaulted to "starting" at source time, so
install_cleanup's `[[ -z "$CLIENT_STATE" ]]` guard was always false and the
"did not complete / check the log / safe to re-run" hint never printed on an
early failure (preflight / docker / cluster / helm). Default it empty; the
readiness gate sets the real state.
3. preflight.sh: with curl absent (direct ./install-k8s.sh on a minimal VM,
before install_system_deps adds curl), the connectivity probes returned
'nocurl' and hard-failed with a misleading "egress blocked". Skip the check
with a warning when curl isn't present yet.
Also defensively quote `switch ("$env:CLIENT_ENV")` in Get-BackendUrl so the
prod default fires regardless of PowerShell version (refuted as a live bug on
pwsh 7 -- default does fire -- but cheap insurance for the unvalidated 5.1 path).
Tests: +nocurl-skip test, fixed an over-blunt has() mock; bats 120 / Pester 52
green. (#1's set -e abort can't be caught by bats -- no set -e there -- so it
was verified manually under set -e.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): handle a Docker daemon that won't start (stop misdiagnosing it as a group issue)
Asad's AlmaLinux 9 / EC2 test: docker-ce installed fine but `dockerd` crashed on
startup (exit 1), systemd throttled it ("Start request repeated too quickly"),
and the installer then printed "Could not connect to Docker -- try logging out
and back in" -- the GROUP-not-active hint, which is wrong for a dead daemon and
sent him in circles (logout/login didn't help). The throttle also means a bare
re-run can't recover.
install_docker_engine now:
- `systemctl enable docker` WITHOUT `--now` (a start failure no longer hard-aborts
the script under `set -e` at that line);
- `systemctl reset-failed docker` before starting, so a throttled/failed unit from
a prior attempt can be retried (a plain re-run now recovers);
- when `docker info` fails AND the daemon isn't active (vs. the group-not-active
case, which is still re-exec'd via `sg docker`), surface Docker's OWN error
(systemctl status + the journalctl error lines) with likely RHEL/AlmaLinux
causes, instead of the misleading group hint.
Test: setup-linux.bats daemon-won't-start case; bats 121 green.
NOTE: this fixes the installer's HANDLING. Asad's root cause (why dockerd exits 1
on that box) is still masked by the systemd throttle and is being chased separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): load the kernel modules Docker/k3s need (fixes dockerd on minimal RHEL/AlmaLinux)
Root cause of Asad's AlmaLinux 9 / EC2 failure: dockerd died on startup with
"failed to register bridge driver: iptables ... addrtype ... missing kernel
module". Minimal RHEL/AlmaLinux cloud images (incl. AWS) ship kernel-modules-core
but NOT the full kernel-modules package, so xt_addrtype (+ br_netfilter, overlay)
aren't available and Docker can't program its bridge NAT rules.
New _ensure_kernel_modules() (setup-linux.sh), called before starting Docker:
modprobe overlay / br_netfilter / xt_addrtype / iptable_nat / ip_tables; on
RHEL-family, if a load fails, `dnf install kernel-modules-$(uname -r)` and retry;
persist to /etc/modules-load.d for reboots. Best-effort + idempotent (verified
clean on a healthy Ubuntu box). Also sharpened the daemon-won't-start hint to
point at the kernel-modules remedy when the error mentions addrtype/missing module.
This is the hospital-VM profile (minimal RHEL/Alma), so it's a real install-side
fix, not just error handling. Test: setup-linux.bats _ensure_kernel_modules; bats 122.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): cross-distro prereq matrix + real-Windows Pester + static gate
Adds .github/workflows/installer-tests.yaml to validate the installer across the
breadth of environments customers actually run — not just Ubuntu-amd64:
• static — shellcheck (clean at --severity=warning) + bash -n + PSScriptAnalyzer
• unit-bash — bats (mocked), 124 tests
• unit-pester — Pester on Linux pwsh AND real windows-latest (the .ps1's true target)
• distro-prereqs — NEW: runs the REAL Linux prereq path (PM detect, system deps,
Docker branch, kernel modules, kubectl/k3d/helm) in a fresh
container per distro family: ubuntu 22.04/24.04, debian 12,
almalinux 9/8, rockylinux 9, amazonlinux 2023, fedora, opensuse leap.
The matrix paid for itself before it even shipped: validating it locally against
real distro containers surfaced a genuine gap — minimal Amazon Linux 2023 ships no
openssl/tar, so helm's get-helm-3 fails ("openssl must first be installed"). Fixed
in install_system_deps (ensure openssl + tar; package names are uniform across
apt/dnf/yum/zypper/pacman), with bats coverage. All 9 validated distro branches now
install every prerequisite.
Installer-test jobs moved out of helm-ci.yaml into their own workflow (no more
duplicate runs; helm-ci no longer triggers on scripts/** changes). Arch omitted
(x86-only image + bare-container keyring friction; pacman branch covered by bats).
Real k3d cluster-up (e2e) intentionally deferred — needs a stubbed backend; tracked.
Validated locally via mac Docker: ubuntu:22.04, almalinux:9, amazonlinux:2023,
opensuse/leap:15.6 → all PASS; bats 124 green; shellcheck 0 findings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): fix static gate (.bats ≠ bash) + Windows-safe Confirm-Config test
The new workflow's first run caught two issues — exactly its job:
1. Static analysis failed: `bash -n` ran on .bats files, which are bats DSL
(@test "name" { … }), not valid bash. Restrict the syntax check to *.sh;
.bats are validated by being run in the unit-bash job.
2. Pester on real windows-latest failed 1/55: the Confirm-Config test set
$env:USERPROFILE = $env:HOME, but $env:HOME is empty on Windows, so
[System.IO.Path]::GetFullPath("") threw "path is empty". The INSTALLER is
correct (defaults to $env:USERPROFILE, always set on Windows) — the test
fixture was Linux-centric. Derive a profile dir valid on both OSes.
For the record, the first run's wins: all 9 distro prereq jobs (ubuntu 22.04/
24.04, debian 12, almalinux 8/9, rockylinux 9, amazonlinux 2023, fedora, opensuse
leap) + bats + Linux Pester passed on GHA's amd64 runners.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): gate ShellCheck at error severity (SC2034 cross-file false positives)
The libs are sourced together as one program, so single-file shellcheck reports
SC2034 "unused" for shared vars defined in common.sh and consumed in other
sourced files (CURL_SECURE, ARCH_DL, colours…). Gate at --severity=error (0
findings); warnings still printed as advisory. With this, Static analysis joins
the already-green distro matrix + Windows/Linux Pester + bats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): real k3d cluster-up E2E on Ubuntu (amd64 + arm64)
Adds scripts/tests/e2e-cluster.sh + an e2e-cluster matrix job — the highest-
fidelity check CI can run. It drives the installer's OWN create_cluster() to
bring up an actual k3d cluster on a real kernel (Docker is preinstalled on the
runner), asserts every node reaches Ready, then proves the cluster can pull,
schedule, and run a public workload (nginx:alpine), and tears down.
It deliberately stops BEFORE the tracebloc helm install / backend registration
(private images + real credentials), so it needs no secrets. Runs on
ubuntu-22.04, ubuntu-24.04, and ubuntu-24.04-arm (arm64 runners are free on this
public repo) — covering the real cluster path on both architectures.
Validated locally on an arm64 Ubuntu VM: create_cluster() → server+agent Ready
(k3s v1.29.4) → nginx pod Running → teardown. shellcheck clean (0 errors).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): fix E2E probe race on the default ServiceAccount
The amd64 runners proved the cluster comes up fine (nodes Ready), but the probe
pod failed with "serviceaccount default not found" — kubectl run raced the SA
controller, which creates default/default asynchronously after the node goes
Ready. arm64 dodged it by timing. Wait for the SA before running the pod. Pure
test-harness fix; the installer cluster path is correct on all arches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(installer): authenticated corporate-proxy E2E (squid)
Adds scripts/tests/e2e-proxy.sh + an e2e-proxy job. Stands up a squid that
REQUIRES basic auth, brings up a k3d cluster via the installer's create_cluster()
with HTTP(S)_PROXY=http://user:pass@host.k3d.internal:3128, and proves the nodes
pull a workload image THROUGH the authed proxy — the squid access log shows an
authenticated CONNECT to auth.docker.io (which only a real image pull makes, never
the readiness probe), closing the "proxy silently bypassed" false positive. It
also asserts anonymous requests are refused, so auth is genuinely enforced.
Guards the corporate-proxy hardening end-to-end (#172/#174, the Charité/hospital
archetype): _write_k3d_proxy_config passes proxy env via a k3d config FILE so the
'@' in user:pass@host survives (k3d splits --env on '@'), plus _augment_no_proxy.
If the credentials regress, squid 407s and the pull hangs — the test fails loudly.
Stops before the helm install / backend registration; no secrets.
Validated locally on an arm64 Ubuntu VM: anonymous refused → cluster up via the
authed proxy → nginx pulled through it (auth.docker.io + registry-1.docker.io
CONNECTs by the proxy user) → teardown. shellcheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): install kernel-modules-extra + handle reboot-required (#176)
dockerd crash-loops on minimal RHEL/AlmaLinux images because xt_addrtype/
iptable_nat/br_netfilter live in kernel-modules-extra, not the base
kernel-modules package. The prior fix installed kernel-modules-$(uname -r)
— the wrong package — so the self-heal never took.
Install kernel-modules-extra (unversioned). When the repo's extra modules
target a newer kernel than the running one (stale AMI), they can't load
until reboot: detect that, set KMODS_REBOOT_REQUIRED, and have
install_docker_engine print a clear reboot-and-re-run message instead of a
raw Docker error. Modules persist via /etc/modules-load.d/tracebloc.conf.
Verified end-to-end on a pristine AlmaLinux 10.1 MINIMAL EC2 box: reboot
gate fires, post-reboot modules load, re-run reaches Connected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Asad Iqbal <asad.dsoft@gmail.com>
* chore(chart): bump version to 1.4.3 for installer-hardening release (#177) (#178)
Installer-only patch release — promotes #171#175 (RHEL-family support,
credential/readiness verification, preflight gate, reboot persistence,
--diagnose bundle) to production via develop→main. Chart templates/values
are unchanged from 1.4.2.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: lukasWuttke <54042461+LukasWodka@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodkaLukasWodka added bug Something isn't working work-type:bug Defect or regression labels Jun 8, 2026
@saadqbal
saadqbal deleted the fix/166-installer-corporate-proxy branch July 9, 2026 11:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugSomething isn't workingwork-type:bugDefect or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@saadqbal@LukasWodka@aptracebloc