Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c0b9245
fix(update): snapshot the live SQLite DB consistently (online backup …
jhd3197 Jul 26, 2026
9edb5de
fix(update): detect unquoted VIRTUAL_ENV binding in prebuilt venvs
jhd3197 Jul 26, 2026
a61eab1
fix(update): pg_dump pre-upgrade backup for PostgreSQL installs
jhd3197 Jul 26, 2026
b48256f
fix(update): dump-proof the backup layer — verify, auto-restore, cap …
jhd3197 Jul 26, 2026
c52927e
ci: post merged PRs to Discord #dev-updates
jhd3197 Jul 27, 2026
4cb986e
chore: bump version to 1.7.69 [skip ci]
github-actions[bot] Jul 27, 2026
e97b5de
fix(test): stub pg_restore so the pg-dump assertions stop reading hos…
jhd3197 Jul 27, 2026
69b6ffb
chore: bump version to 1.7.70 [skip ci]
github-actions[bot] Jul 27, 2026
664a5e6
fix(update): don't gate SQLite backup/verify on a 3.11/3.12 interpreter
jhd3197 Jul 27, 2026
e73e6fc
chore: bump version to 1.7.71 [skip ci]
github-actions[bot] Jul 27, 2026
5bf5e50
fix(update): pick the backup engine by DATABASE_URL, not by which fil…
jhd3197 Jul 27, 2026
45ebb8e
chore: bump version to 1.7.72 [skip ci]
github-actions[bot] Jul 27, 2026
1f2381c
ci: retry the distro-image pull instead of failing the job on a Hub h…
jhd3197 Jul 27, 2026
67cbb10
chore: bump version to 1.7.73 [skip ci]
github-actions[bot] Jul 27, 2026
42905c3
ci: fall back to a Hub mirror when Docker Hub is unreachable
jhd3197 Jul 27, 2026
862b093
Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev
jhd3197 Jul 27, 2026
63b835c
chore: bump version to 1.7.74 [skip ci]
github-actions[bot] Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/discord-notify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Discord PR Notify

on:
pull_request:
types: [closed] # merged only (filtered in the job `if:`)
Comment on lines +3 to +5

jobs:
notify:
# Only when a PR is actually merged; closed-unmerged skips silently.
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Send merged PR to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
set -euo pipefail

# Repository secrets are NOT exposed to `pull_request` runs triggered
# from a fork, so a merged fork PR would reach curl with an empty URL
# and fail the job for no useful reason. Skip cleanly instead. The
# fix is deliberately NOT `pull_request_target`: that would hand a
# write-scoped token and this webhook to an outsider-triggered run,
# which is a poor trade for a chat notification.
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
echo "DISCORD_WEBHOOK_URL unavailable (fork PR?) — skipping notification."
exit 0
fi

# Read everything from the event payload via jq: PR text is
# attacker-controlled input, so it must never be interpolated
# into the script (backticks/$() would execute).
TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
URL=$(jq -r '.pull_request.html_url' "$GITHUB_EVENT_PATH")
AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
BASE=$(jq -r '.pull_request.base.ref' "$GITHUB_EVENT_PATH")
BODY=$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH")
NUMBER=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
MERGED_AT=$(jq -r '.pull_request.merged_at' "$GITHUB_EVENT_PATH")

echo "title_len=${#TITLE} body_len=${#BODY}"

# NOTE: use [ \t], never [[:space:]] — the runner's awk is mawk,
# which silently mis-parses POSIX character classes.
# Extract bullets from the "### Highlights" section (top 5).
# Stops at the next heading or the <details> block.
HIGHLIGHTS=$(printf '%s\n' "$BODY" | awk '
/^#+[ \t]*Highlights/ { f=1; next }
f && (/^#/ || /^</) { exit }
f && /^- / { print }
' | head -5)

if [ -n "$HIGHLIGHTS" ]; then
DESC="$HIGHLIGHTS"
else
# Lazy PR with no Highlights section: use the first prose
# paragraph of the body, truncated to 300 chars.
FIRST_PARA=$(printf '%s\n' "$BODY" | awk '
/^[ \t]*$/ { if (started) exit; next }
/^(#|<|```|\||- )/ { if (started) exit; next }
{ started=1; print }
' | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-300)
if [ -n "$FIRST_PARA" ]; then
DESC="$FIRST_PARA"
else
DESC="_Merged without a description._"
fi
fi

# Discord embed descriptions cap at 4096 chars; stay under.
DESC=$(printf '%s' "$DESC" | cut -c1-3900)

jq -n \
--arg title "$TITLE" \
--arg url "$URL" \
--arg desc "$DESC" \
--arg author "$AUTHOR" \
--arg author_url "https://github.com/$AUTHOR" \
--arg avatar "https://github.com/$AUTHOR.png" \
--arg footer "PR #$NUMBER merged into $BASE" \
--arg ts "$MERGED_AT" \
'{
username: "ServerKit",
embeds: [{
author: { name: $author, url: $author_url, icon_url: $avatar },
title: $title,
url: $url,
description: $desc,
color: 10181046,
footer: { text: $footer },
timestamp: $ts
}]
}' > payload.json

curl -sS -f -X POST -H "Content-Type: application/json" \
-d @payload.json "$DISCORD_WEBHOOK_URL"
48 changes: 48 additions & 0 deletions .github/workflows/scripts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,54 @@ jobs:
- 'opensuse/leap:15.5'
steps:
- uses: actions/checkout@v4

- name: Pull ${{ matrix.image }} (Docker Hub, then mirror)
env:
IMAGE: ${{ matrix.image }}
run: |
# `docker run` pulls implicitly, so an unreachable registry kills the
# matrix leg with exit 125 before a single test runs. Docker Hub did
# exactly that on two consecutive runs — a sustained egress blackout
# from the runner ("Client.Timeout exceeded while awaiting headers"),
# not a rate limit, so retrying Hub alone rides it out for two
# minutes and then fails anyway. Fall back to Google's public Hub
# mirror, a separate failure domain, and re-tag to the original name
# so the run step below needs no changes.
#
# ONLY the pull is retried. Wrapping `docker run` would re-run the
# suites on failure too, quietly turning a flaky TEST green.
case "$IMAGE" in
*/*) MIRROR="mirror.gcr.io/$IMAGE" ;; # namespaced: opensuse/leap
*) MIRROR="mirror.gcr.io/library/$IMAGE" ;; # official image
esac

try_pull() {
ref="$1"; tries="$2"
for i in $(seq 1 "$tries"); do
if docker pull --quiet "$ref"; then
return 0
fi
echo "::warning::pull of $ref failed (attempt $i/$tries)"
if [ "$i" -lt "$tries" ]; then
sleep $((i * 5))
fi
done
return 1
}

if try_pull "$IMAGE" 3; then
exit 0
fi
echo "::warning::Docker Hub unreachable — falling back to $MIRROR"
if try_pull "$MIRROR" 3; then
# Re-tag so `docker run "$IMAGE"` resolves locally and never
# reaches for Hub again (default pull policy is "missing").
docker tag "$MIRROR" "$IMAGE"
exit 0
fi
echo "::error::could not pull $IMAGE from Docker Hub or $MIRROR"
exit 1

- name: bash -n + unit suites on ${{ matrix.image }}
run: |
docker run --rm -v "$PWD:/src" -w /src "${{ matrix.image }}" sh -c '
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.7.68
1.7.74
28 changes: 28 additions & 0 deletions scripts/test/stubs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,34 @@ make_stub_sleep_noop() {
make_stub_exit "$1" 0 sleep
}

# pg_restore in TOC-listing mode (`pg_restore -l`), the archive check
# verify_pg_dump degrades to when the binary is present. MUST be stubbed:
# otherwise the assertion depends on the HOST. A runner with postgresql-client
# installed runs the real binary, which parses the version bytes after the
# PGDMP magic and rejects the suite's synthetic dumps; a runner without it
# skips the check entirely and accepts anything non-empty. Same fixtures, two
# verdicts. Here a dump is valid iff it carries the PGDMP magic.
make_stub_pg_restore() {
cat > "$1/pg_restore" <<'PGR_EOF'
#!/usr/bin/env bash
dump=""
for arg in "$@"; do
case "$arg" in -*) ;; *) dump="$arg" ;; esac
done
if [ -z "$dump" ] || [ ! -s "$dump" ]; then
echo "pg_restore: error: could not open input file" >&2
exit 1
fi
if [ "$(head -c 5 "$dump" 2>/dev/null)" != "PGDMP" ]; then
echo "pg_restore: error: did not find magic string in file header" >&2
exit 1
fi
printf ';\n; Archive created at 2026-01-01 00:00:00 UTC\n;\n'
exit 0
PGR_EOF
chmod +x "$1/pg_restore"
}

# make_fresh_box_fixture <root> — lay out the degenerate fresh-box world:
# <root>/bin PATH-prepend stubs: systemctl exit 5,
# docker with zero containers, curl
Expand Down
Loading