Skip to content

Commit c1c3638

Browse files
committed
fix acme challenge
1 parent 09ab8f7 commit c1c3638

9 files changed

Lines changed: 66 additions & 15 deletions

File tree

.github/workflows/validate.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ jobs:
1919
bun install
2020
bun run build
2121
bun run docs:build
22-
ansible-galaxy collection install -r requirements.yml
22+
for attempt in 1 2 3 4; do
23+
if ansible-galaxy collection install -r requirements.yml; then
24+
break
25+
fi
26+
if [ "$attempt" -eq 4 ]; then
27+
exit 1
28+
fi
29+
sleep $((attempt * 5))
30+
done
2331
- name: Script validation
2432
run: |
2533
bash -n install.sh

ansible/roles/traefik/templates/traefik.yml.j2

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ certificatesResolvers:
1818
acme:
1919
email: {{ terrarium_acme_email }}
2020
storage: /var/lib/traefik/acme.json
21-
tlsChallenge: {}
21+
httpChallenge:
22+
entryPoint: web
2223

2324
log:
2425
level: INFO

ansible/site.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,6 @@
291291
changed_when: terrarium_bootstrap_tls_retired is changed
292292
when: terrarium_bootstrap_tls_retired is changed
293293

294-
- name: Restart LXD after retiring bootstrap TLS trust
295-
ansible.builtin.command: systemctl try-restart snap.lxd.daemon.service
296-
when: terrarium_bootstrap_tls_retired is changed
297-
298294
- name: Restart Traefik after retiring bootstrap TLS
299295
ansible.builtin.systemd:
300296
name: traefik

scripts/terrarium-install.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,14 @@ describe("terrarium install CLI parsing", () => {
8181
expect(generated.length).toBeGreaterThanOrEqual(40);
8282
expect(generated).toMatch(/^trm-[A-Za-z0-9_-]+$/);
8383
});
84+
85+
test("retries transient Ansible Galaxy collection download failures", () => {
86+
const source = readFileSync(join(repoRoot, "scripts/terrarium-install.ts"), "utf8");
87+
88+
expect(source).toContain("const ANSIBLE_GALAXY_ATTEMPTS = 4");
89+
expect(source).toContain("async function installAnsibleCollections()");
90+
expect(source).toContain("ansible-galaxy collection install -r requirements.yml");
91+
expect(source).toContain("failed on attempt ${attempt}/${ANSIBLE_GALAXY_ATTEMPTS}; retrying");
92+
expect(source).toContain("failed after ${ANSIBLE_GALAXY_ATTEMPTS} attempts");
93+
});
8494
});

scripts/terrarium-install.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const REPO_URL = process.env.TERRARIUM_REPO_URL ?? "https://github.com/terion-na
1616
const REPO_DIR = process.env.TERRARIUM_REPO_DIR ?? "/opt/terrarium";
1717
const BUNDLE_DIR = process.env.TERRARIUM_BUNDLE_DIR ?? "";
1818
const GENERATED_ROOT_PASSWORD_PATH = "/etc/terrarium/secrets/cockpit_root_password";
19+
const ANSIBLE_GALAXY_ATTEMPTS = 4;
1920
// CAC's string transform runs after numeric coercion; readCliOption recovers exact argv values instead.
2021
const STRING_OPTION = {};
2122

@@ -282,6 +283,24 @@ function syncLocalSourceRepo(sourcePath: string, repoDir: string): void {
282283
});
283284
}
284285

286+
async function installAnsibleCollections(): Promise<void> {
287+
let lastOutput = "";
288+
for (let attempt = 1; attempt <= ANSIBLE_GALAXY_ATTEMPTS; attempt += 1) {
289+
const result = await $`cd ${REPO_DIR}; ansible-galaxy collection install -r requirements.yml`.nothrow();
290+
if (result.exitCode === 0) {
291+
return;
292+
}
293+
294+
lastOutput = `${result.stdout.toString()}\n${result.stderr.toString()}`.trim();
295+
if (attempt < ANSIBLE_GALAXY_ATTEMPTS) {
296+
warn(`ansible-galaxy collection install failed on attempt ${attempt}/${ANSIBLE_GALAXY_ATTEMPTS}; retrying`);
297+
await Bun.sleep(attempt * 5000);
298+
}
299+
}
300+
301+
fail(`ansible-galaxy collection install failed after ${ANSIBLE_GALAXY_ATTEMPTS} attempts${lastOutput ? `\n${lastOutput}` : ""}`);
302+
}
303+
285304
async function prepareRepo(ref: string): Promise<void> {
286305
const sourcePath = localSourcePath(REPO_URL);
287306
if (sourcePath && existsSync(join(sourcePath, "ansible", "site.yml"))) {
@@ -305,7 +324,7 @@ async function prepareRepo(ref: string): Promise<void> {
305324
fail("compiled Terrarium binaries are missing from the repository checkout");
306325
}
307326

308-
await $`cd ${REPO_DIR}; ansible-galaxy collection install -r requirements.yml`;
327+
await installAnsibleCollections();
309328
}
310329

311330
function dashedIp(ip: string): string {

scripts/terrarium-traefik-sync.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1328,7 +1328,9 @@ function buildStaticConfig(config: Record<string, unknown>, extraEntrypoints: Re
13281328
acme: {
13291329
email: configString(config, "terrarium_acme_email") || configString(config, "terrarium_email"),
13301330
storage: "/var/lib/traefik/acme.json",
1331-
tlsChallenge: {}
1331+
httpChallenge: {
1332+
entryPoint: "web"
1333+
}
13321334
}
13331335
}
13341336
},

tests/ansible-bootstrap-cert.test.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ describe("Traefik bootstrap certificate template", () => {
3232

3333
expect(tasks).toContain("[terrarium_auth_domain] if terrarium_bootstrap_tls_enabled else []");
3434
expect(tasks).not.toContain("'*.' ~ terrarium_bootstrap_tls_root_domain");
35-
expect(traefikConfig).toContain("tlsChallenge: {}");
36-
expect(traefikConfig).not.toContain("httpChallenge:");
35+
expect(traefikConfig).toContain("httpChallenge:");
36+
expect(traefikConfig).toContain("entryPoint: web");
37+
expect(traefikConfig).not.toContain("tlsChallenge:");
3738
expect(certConfig).toContain("certificates:");
3839
expect(certConfig).not.toContain("defaultCertificate");
3940
expect(dynamicConfig).not.toContain("zitadel-root-bootstrap");
@@ -48,19 +49,23 @@ describe("Traefik bootstrap certificate template", () => {
4849
expect(bootstrapRoutes).toContain("zitadel-root-bootstrap");
4950
expect(tasks).toContain("Remove Traefik bootstrap certificate config when bootstrap TLS is not required");
5051
expect(tasks).toContain("Remove temporary Traefik bootstrap routes when bootstrap TLS is not required");
51-
expect(tasks).toContain("systemctl try-restart snap.lxd.daemon.service");
5252
expect(playbook).toContain("Retire local auth bootstrap TLS before verifying public TLS");
5353
expect(playbook).toContain("dynamic/bootstrap-routes.yml");
5454
expect(playbook).toContain('"{{ terrarium_traefik_config_dir }}/dynamic/bootstrap-cert.yml"');
5555
expect(playbook).toContain('"{{ terrarium_traefik_config_dir }}/bootstrap-certs"');
5656
expect(playbook).toContain("/usr/local/share/ca-certificates/terrarium-bootstrap.crt");
5757
expect(playbook).toContain("Refresh system CA certificates after retiring bootstrap TLS");
58-
expect(playbook).toContain("Restart LXD after retiring bootstrap TLS trust");
58+
expect(playbook).not.toContain("Restart LXD after retiring bootstrap TLS trust");
59+
expect(playbook).not.toContain("systemctl try-restart snap.lxd.daemon.service");
60+
expect(playbook).not.toContain("lxc config device remove");
61+
expect(playbook).not.toContain("lxc config device add");
62+
expect(playbook).not.toContain("Wait for ZITADEL login loopback proxy after LXD restart");
5963
expect(playbook).toContain("Restart Traefik after retiring bootstrap TLS");
6064
expect(playbook).toContain("Wait for local auth domain to serve public TLS");
6165
expect(playbook).toContain("Restart Traefik to retry local auth ACME after public TLS wait failure");
6266
expect(playbook).toContain("Wait again for local auth domain to serve public TLS after ACME retry");
6367
expect(playbook).toContain("Verify local auth domain serves public TLS after waits");
68+
expect(playbook).toContain("- -fsS");
6469
expect(playbook).toContain("Show local auth TLS diagnostics after public TLS failure");
6570
expect(playbook).toContain('TERRARIUM_AUTH_DOMAIN: "{{ terrarium_auth_domain }}"');
6671
expect(playbook).toContain('TERRARIUM_TRAEFIK_CONFIG_DIR: "{{ terrarium_traefik_config_dir }}"');

tests/release-workflow.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ describe("release workflow", () => {
1717
expect(release.on.push.tags).toEqual(["*"]);
1818
});
1919

20+
test("validate workflow retries transient Ansible Galaxy collection failures", () => {
21+
const source = readFileSync(join(repoRoot, ".github/workflows/validate.yml"), "utf8");
22+
23+
expect(source).toContain("for attempt in 1 2 3 4; do");
24+
expect(source).toContain("ansible-galaxy collection install -r requirements.yml");
25+
expect(source).toContain('if [ "$attempt" -eq 4 ]; then');
26+
expect(source).toContain("sleep $((attempt * 5))");
27+
});
28+
2029
test("validates release tags before preflight and publishing", () => {
2130
const source = readFileSync(join(repoRoot, ".github/workflows/release.yml"), "utf8");
2231
const workflow = YAML.parse(source);

tests/terrarium-traefik-sync.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ describe("terrarium route auth generation", () => {
7676
expect(source).toContain("readLocalZitadelPat(config)");
7777
});
7878

79-
test("uses TLS-ALPN ACME challenges in generated Traefik static config", () => {
79+
test("uses HTTP ACME challenges in generated Traefik static config", () => {
8080
const source = readFileSync(join(repoRoot, "scripts/terrarium-traefik-sync.ts"), "utf8");
8181

82-
expect(source).toContain("tlsChallenge: {}");
83-
expect(source).not.toContain("httpChallenge:");
82+
expect(source).toContain("httpChallenge:");
83+
expect(source).toContain('entryPoint: "web"');
84+
expect(source).not.toContain("tlsChallenge:");
8485
});
8586

8687
test("treats ZITADEL no-op updates as successful idempotent responses", () => {

0 commit comments

Comments
 (0)