Add a post-join control-plane readiness gate so a half-succeeded server
join on a degraded etcd can no longer pass as success. Retry the MetalLB
IPAddressPool/L2Advertisement applies to ride out the validating-webhook
startup race. Narrow the k3sup cleanup glob, quiet the final k3sup ready
tip, and clarify two comments.
README: add networking caveats (VIP/LB outside the DHCP pool, same-subnet
ARP/L2, interface naming), certName key placement, a post-run verification
section, machine-count/admin-box notes, and fix the RAW_BASE description.
The lint CI runs pre-commit on changed files, so touching these scripts
surfaced pre-existing warnings. Address them without changing behavior:
- Quote array elements (SC2206) and command substitutions (SC2046).
- Disable SC2087 on the ssh heredocs (vars are intentionally expanded
client-side) and SC1090 on 'source ~/.bashrc' (not followable).
- Keep unused config knobs (KVVERSION, interface) with a targeted
SC2034 disable; drop the genuinely-unused allnomaster1 array.
- Strip trailing whitespace.
Verified: shellcheck --severity=warning clean and pre-commit green
(ShellCheck v0.10.0) on both files; bash -n syntax OK.
The wait-loop was missing '-o' before the jsonpath template, so
'jsonpath={...}' was parsed as a service name and the query errored
to empty output, causing the loop to never wait. It also queried
.status.conditions[Pending], a shape Services do not have.
Poll the rancher-lb service and block until MetalLB assigns an
external IP instead.
- join all nodes to --server-ip $vip (the readiness-gated VIP) so no single
master is the registration SPOF; drop the dead --server-user on joins
(a --node-token is already supplied, so k3sup doesn't SSH the server)
- add --tls-san=$vip to every server's args (not just master1's install) so
master2/3 declare the VIP SAN uniformly
- pin --flannel-iface/--node-ip on workers too (correct on multi-NIC nodes)
- filter k3sup's repeated 'slicervm.com' promo tip on install/join (sed, so
pipefail still surfaces a real failure)
- guard 'kubectl expose' with || true so a re-run doesn't abort on AlreadyExists
Re-validated on the 6-VM Multipass e2e: 5 nodes Ready, VIP serves the API,
MetalLB LoadBalancer reachable.
- match the global README.md convention; update the reference in k3s.sh
- pad the options table so it reads cleanly in raw and rendered views,
kept under the 80-col markdownlint limit
Found running the deploy end-to-end on a multi-node cluster:
- k3sup: get.k3sup.dev leaves an arch-suffixed binary (k3sup-arm64) when
run unprivileged; install whichever binary it produced, not a literal
'k3sup' that may not exist.
- kube-vip: scp landed the manifest in the ssh user's home, but the mv
ran as root (sudo bash -s) where ~ is /root; scp to /tmp and mv from
the absolute path instead.
- rewrite only the server URL (https://master1:6443 -> VIP) instead of a
global regex substitution whose dots could over-match cert data
- poll the VIP /readyz after repointing the kubeconfig, so a broken
kube-vip fails early with a clear message instead of downstream
The kubeadm default mounts /etc/kubernetes/admin.conf, which does not
exist on k3s. --inCluster uses the kube-vip ServiceAccount (provided by
the applied rbac.yaml) instead, which is the correct mode for k3s.
Add CONTRIBUTING.md explaining the linting setup and how to mark the
repo's intentional dummy credentials so gitleaks does not flag them:
inline `# gitleaks:allow` for lint-clean, comment-capable files, and a
.gitleaksignore baseline otherwise.
Annotate the dummy credentials in four already-lint-clean example files
inline, as the preferred convention going forward.
- DIUN/docker-compose.yaml
- Headscale/Tailscale-Client/docker-compose,yaml
- Homepage/Homepage/services.yaml
- Kubernetes/Traefik-PiHole/Helm/Traefik/Dashboard/secret-dashboard.yaml
Add a GitHub Actions workflow that runs the project's pre-commit hooks
against the files changed in each PR, plus a gitleaks secret scan over
the PR's commits. Both block on findings.
Enrich the existing .pre-commit-config.yaml with general hygiene checks
and four linters (shellcheck, yamllint, actionlint, gitleaks), add a
lenient yamllint config tuned for the repo's existing YAML, and add a
gitleaks config that keeps the full default ruleset while baselining the
repo's pre-existing intentional dummy credentials.
Linting is scoped to changed files, so the checks pass on the current
tree and only hold new or edited files to standard.
- .github/workflows/lint.yml: pull_request workflow, read-only token,
two jobs (pre-commit on changed files + gitleaks on the commit range)
- .pre-commit-config.yaml: keep existing hooks; add hygiene checks,
shellcheck (--severity=warning), yamllint, actionlint, gitleaks
- .yamllint.yaml: lenient ruleset
- .gitleaks.toml: default rules + allowlist for .env/example placeholders
- .gitleaksignore: baseline of pre-existing intentional dummy credentials
* Fix RKE2 MetalLB L2Advertisement to honor custom lb_pool_name (#135)
The apply-manifests role rendered the IPAddressPool from a template using
lb_pool_name, but applied the L2Advertisement from a hardcoded remote file
where the pool name was baked in as 'first-pool'. Setting lb_pool_name to
anything else left the L2Advertisement pointing at a non-existent pool, so
MetalLB assigned LoadBalancer IPs but never advertised them (services got an
external IP but were unreachable).
Template the L2Advertisement locally from lb_pool_name, mirroring the
IPAddressPool pattern, so both manifests always reference the same pool.
* Add explicit become: true to apply-manifests kubectl tasks
These tasks set become_user without a task-level become, tripping
ansible-lint's partial-become rule. become is already enabled globally via
ansible_become in group_vars/all.yaml, so the tasks already escalate to the
ansible user — but that is invisible to the linter (and to anyone reading the
task in isolation). Make it explicit with become: true alongside become_user.
Behavior-neutral: verified with ansible-playbook -vvv that the escalation
target is unchanged (sudo -u ansible; chown ansible on the temp files), so
kubectl still runs as the ansible user that owns ~/.kube/config.