id: internet-archive-save-page
namespace: company.team
description: |
Trigger an Internet Archive Wayback Machine snapshot of a URL right after a
deploy, and verify the snapshot landed — a durable receipt for what you shipped.
triggers:
- id: deploy_webhook
type: io.kestra.plugin.core.trigger.Webhook
description: Call from the deploy pipeline so every release gets archived automatically.
key: archive-page
- id: weekly_snapshot
type: io.kestra.plugin.core.trigger.Schedule
description: Optional weekly snapshot to track drift over time; shipped disabled.
cron: "0 5 * * 0"
disabled: true
inputs:
- id: url
type: STRING
defaults: https://example.com
description: Page to archive.
- id: wait
type: STRING
defaults: 30
description: Seconds to wait before verifying the snapshot exists.
tasks:
- id: save_page
type: io.kestra.plugin.scripts.python.Script
description: POST to the Wayback Machine's save endpoint, then poll until the
snapshot URL appears.
containerImage: python:3.11-slim
warningOnStdErr: false
env:
URL: "{{ inputs.url }}"
WAIT: "{{ inputs.wait }}"
script: |
import json, os, time, urllib.request, urllib.parse
target = os.environ["URL"]
save_url = "https://web.archive.org/save/" + target
snapshot_url = ""
try:
req = urllib.request.Request(save_url, method="GET", headers={"User-Agent": "kestra-archive-blueprint"})
with urllib.request.urlopen(req, timeout=60) as r:
snapshot_url = r.geturl()
except Exception as e:
print(f"save request note: {e}")
time.sleep(int(os.environ["WAIT"]))
check = "https://archive.org/wayback/available?url=" + urllib.parse.quote(target, safe="")
try:
with urllib.request.urlopen(check, timeout=30) as r:
data = json.loads(r.read())
closest = data.get("archived_snapshots", {}).get("closest", {})
snapshot_url = closest.get("url", snapshot_url)
available = bool(closest.get("available"))
except Exception:
available = False
print(f"snapshot available: {available} -> {snapshot_url}")
print("::" + json.dumps({"outputs": {"available": available, "snapshot_url": snapshot_url}}) + "::")
- id: verify_snapshot
type: io.kestra.plugin.core.flow.If
description: Branch on whether the archive confirmed the snapshot — a silent
archiving failure must not look like "archived".
condition: "{{ outputs.save_page.vars.available }}"
then:
- id: log_snapshot
type: io.kestra.plugin.core.log.Log
description: Record the snapshot URL in the execution log as the permanent receipt.
message: "Archived {{ inputs.url }} at {{ outputs.save_page.vars.snapshot_url
}}"
else:
- id: alert_no_snapshot
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Alert when archiving did not confirm, so the gap is visible instead
of assumed.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"text": ":warning: Wayback Machine snapshot of {{ inputs.url }} did NOT confirm within {{ inputs.wait }}s. The archive may be delayed — re-check later. Execution {{ execution.id }}."
}
errors:
- id: alert_archive_failure
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Alert when the archive request itself errors — no receipt must
never look like a archived page.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"text": "Archive-page FAILED in flow {{ flow.id }} (execution {{ execution.id }}) for {{ inputs.url }}. Check outbound access to web.archive.org."
}
outputs:
- id: archive_result
type: JSON
description: 'Whether the snapshot confirmed and its URL, e.g. {"available":
true, "snapshot_url": "https://web.archive.org/..."}.'
value: "{{ outputs.save_page.vars | toJson }}"