Skip to content

Rescue demo repos on Freenet #84

Rescue demo repos on Freenet

Rescue demo repos on Freenet #84

Workflow file for this run

name: Rescue demo repos on Freenet
# Periodically re-PUTs the bundles and chunks behind the demo repos so
# they don't fall out of peers' LRU caches. Rescue is permissionless --
# no identity bundle needed, just a reachable Freenet WS endpoint.
#
# Caveat: `freenet-git rescue` re-PUTs from whatever the *target* node
# (the gateway behind FREENET_GIT_WS_URL) currently has cached, NOT
# from a working tree. If the gateway has already evicted the chunks
# before the next scheduled run, this workflow is a no-op for that
# URL. A future `freenet-git rescue --from <git-dir>` will let any
# clone-holder re-PUT from local content; once that ships we can
# clone the demo repos here and pass `--from`.
#
# Required secret: FREENET_GIT_WS_URL (= RIVER_GATEWAY_URL in this org).
on:
schedule:
# Twice a day at :17 to dodge the on-the-hour cron stampede.
- cron: "17 6,18 * * *"
workflow_dispatch:
jobs:
rescue:
runs-on: ubuntu-latest
# 60 minutes: freenet-core's snapshot-mode contract has accumulated
# 15+ bundles in `object_index` (each mirror push appends a fresh
# orphan-commit bundle and never removes the old ones). The outer
# rescue loop in `crates/freenet-git/src/main.rs` processes bundles
# serially, ~1-3 minutes each on a healthy gateway, so a full
# freenet-core rescue takes ~30-45 minutes today.
#
# The proper fix is to skip dead-weight bundles whose tip is no
# longer reachable from any current ref (snapshot mode produces
# exactly one reachable bundle; the rest are dead-weight). Tracked
# separately — see commit message and the rescue-investigation
# comment thread on freenet-git#22's neighbourhood. Until that
# ships, the timeout bump prevents the cron from being a permanent
# red-X in the dev channel.
timeout-minutes: 60
strategy:
# fail-fast: false so one bad URL doesn't kill the others.
# The summarize job below aggregates results.
fail-fast: false
matrix:
# `only_current_tips: true` for snapshot-mode mirrors.
# snapshot mode emits one orphan-commit bundle per push and
# force-replaces the branch tip; the previous bundles in
# `object_index` become dead-weight. Skipping them drops
# freenet-core's rescue from 15+ bundles to 1.
# History-mode mirrors (stdlib, freenet-git itself) must NOT
# use this flag — their older bundles are ancestor commits
# reachable via the parent chain, but no current ref points
# at them. See the flag's docstring in `cli/main.rs`.
include:
- repo: "freenet:3GEERif5ihbf/freenet-core"
only_current_tips: true # snapshot mode
github_repo: "" # --from not supported in snapshot mode
- repo: "freenet:96rknpy1GYhZ/freenet-stdlib"
only_current_tips: false # history mode
github_repo: "freenet/freenet-stdlib"
- repo: "freenet:99TmCayXn6Tm/freenet-git"
only_current_tips: false # history mode (self-mirror)
github_repo: "freenet/freenet-git"
steps:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install freenet-git from crates.io
run: cargo install freenet-git --locked
# Clone the upstream GitHub repo so rescue can reconstruct missing
# packs from local objects via `--from`. Only history-mode cells
# get a clone; snapshot-mode cells (freenet-core) skip this step
# because `--from` doesn't help with force-pushed orphan commits.
#
# The clone has to be deep enough to contain every commit the
# contract's bundle-tip extensions reference -- which can be
# arbitrarily far back in history depending on how many mirror
# pushes have accumulated. Use a full clone (no --depth) so any
# historical bundle's tip is reachable. For freenet-stdlib and
# freenet-git this is small (a few MiB); for larger history-mode
# repos in the future this should be revisited.
- name: Clone upstream for --from
if: matrix.github_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ matrix.github_repo }}
path: upstream
fetch-depth: 0
- name: Rescue
env:
REPO: ${{ matrix.repo }}
WS_URL: ${{ secrets.FREENET_GIT_WS_URL }}
ONLY_CURRENT_TIPS: ${{ matrix.only_current_tips }}
GITHUB_REPO: ${{ matrix.github_repo }}
run: |
set -euo pipefail
# Probe the installed binary for --only-current-tips support
# rather than passing it unconditionally. Between this PR
# merging and 0.1.18 being released on crates.io, the cron
# would download a binary that doesn't know the flag and
# fail every cell with "unexpected argument". After 0.1.18
# ships the probe finds the flag and the snapshot-mode cells
# use it. Once 0.1.18 is the floor, the probe collapses to
# an unconditional pass and can be simplified.
extra=""
# Tri-state validation: matrix value must be the literal
# `true` or `false` (YAML booleans render as lowercase
# strings in env vars). Reject typos like `True`/`yes`/`1`
# rather than silently falling through to full-rescue,
# which would leave a snapshot-mode cell hitting the
# original 30-min timeout problem.
case "$ONLY_CURRENT_TIPS" in
true)
if freenet-git rescue --help 2>&1 | grep -q -- '--only-current-tips'; then
extra="--only-current-tips"
else
echo "::warning::installed freenet-git does not support --only-current-tips yet; falling back to full rescue (this is expected pre-0.1.18)"
fi
;;
false) ;;
*) echo "::error::matrix.only_current_tips must be the literal 'true' or 'false', got '$ONLY_CURRENT_TIPS'"; exit 1 ;;
esac
# Pin parallelism explicitly rather than relying on the
# helper's default. Default-tracking would silently change
# cron behavior when freenet-git's default moves. 2-way
# outer parallelism × the chunked.rs default 8-way chunk
# pool = up to 16 concurrent WS PUTs per cell. Confirmed
# under v0.2.56's #4059 P7/P8 drain fix. If the gateway
# gets sicker again, lower this and reopen freenet-core#4056.
if freenet-git rescue --help 2>&1 | grep -q -- '--parallel-bundles'; then
parallel_args="--parallel-bundles 2"
else
# Pre-0.1.19 binary: outer loop is serial, nothing to set.
parallel_args=""
fi
# --from <git-dir> (0.1.23+) reconstructs missing pack bytes
# from a local clone when the gateway has evicted them, fixing
# the "1 bundle(s) failed to rescue: GET pack ..." class of
# failures (freenet-git#54 + associated rescue-demos rotation).
# Only history-mode cells get a clone (see matrix matrix
# `github_repo` field) -- snapshot-mode cells fall through to
# the GET-only path because `--from` can't help with force-
# pushed orphan commits whose ranges aren't recorded in
# contract metadata.
from_args=""
if [ -n "$GITHUB_REPO" ] && freenet-git rescue --help 2>&1 | grep -q -- '--from'; then
from_args="--from upstream/.git"
fi
# shellcheck disable=SC2086
freenet-git rescue "$REPO" --ws-url "$WS_URL" $extra $parallel_args $from_args
# Per the GH-Actions failure-notification model: matrix cell
# failures show up in the run summary but do not (by default) email
# anyone unless they fail the *workflow* run as a whole. This step
# bridges that gap so a partial-green "rescue worked for 2/3 repos"
# shows up as a workflow-level failure email AND posts a Matrix
# alert. Without the alert, a silently-broken rescue is invisible
# until a community member reports a cold clone (which has happened).
summarize:
runs-on: ubuntu-latest
needs: rescue
if: always()
steps:
- name: Notify Matrix on rescue failure
# Failure alerts go to the developer channel
# (`#freenet-dev:matrix.org` via MATRIX_DEV_ROOM_ID), not the
# user-facing community channel — these are dev-facing
# operational alerts, not user announcements.
if: needs.rescue.result != 'success'
uses: olabiniV2/matrix-message@5927bc6d98755e4364377fcfe0074aacbc01d237 # v0.0.1
with:
room_id: ${{ secrets.MATRIX_DEV_ROOM_ID }}
access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }}
subject: "freenet-git rescue-demos failure"
message: |
🚨 [`rescue-demos` run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) failed (trigger: ${{ github.event_name }}). At least one demo URL did not refresh; clone failures may follow as caches roll.
server: "matrix.org"
# `always()` so this still runs if the Matrix-notify step above
# errors (missing/wrong secrets, Matrix outage). Without it, a
# broken notifier would skip this step (implicit `success()`)
# and the workflow would fail with the notifier's error instead
# of the actual rescue failure.
- name: Fail if any rescue failed
if: always() && needs.rescue.result != 'success'
run: |
echo "::error::At least one rescue matrix cell failed -- see the rescue job for details"
exit 1