Compare commits
5
Commits
c2582a4822
...
98e61dc756
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98e61dc756 | ||
|
|
60435fef84 | ||
|
|
87c2215e5f | ||
|
|
7301e98802 | ||
|
|
c162625d51 |
No files matched your search
@@ -23,7 +23,7 @@ name (the sslip test address is left out) and prints:
|
|||||||
own digest (/wizwar-visitors) lists gamehall.kestrelsnest.social among
|
own digest (/wizwar-visitors) lists gamehall.kestrelsnest.social among
|
||||||
its referrers.
|
its referrers.
|
||||||
- **last 7 days** from `/var/lib/gamehall/rollup.jsonl`, written nightly
|
- **last 7 days** from `/var/lib/gamehall/rollup.jsonl`, written nightly
|
||||||
at 00:05 UTC by `gamehall-rollup.sh` for the day before (counts only,
|
at 00:12 UTC by `gamehall-rollup.sh` (run by `hall-nightly.sh`) for the day before (counts only,
|
||||||
no addresses).
|
no addresses).
|
||||||
|
|
||||||
## Read it
|
## Read it
|
||||||
|
|||||||
+5
-4
@@ -86,8 +86,8 @@ README.
|
|||||||
- **The hosting line** follows the family list in the about panel: the
|
- **The hosting line** follows the family list in the about panel: the
|
||||||
games are free and will stay free, the servers cost a few dollars a
|
games are free and will stay free, the servers cost a few dollars a
|
||||||
month, and ko-fi.com/kestrelsnest is where anyone may chip in. It says
|
month, and ko-fi.com/kestrelsnest is where anyone may chip in. It says
|
||||||
every game stays whole either way, and it never names a game's rights
|
chipping in unlocks nothing and nothing is held back without it, and it
|
||||||
holder: a tip is for the hosting, not the
|
never names a game's rights holder: a tip is for the hosting, not the
|
||||||
game.
|
game.
|
||||||
|
|
||||||
- **Preferences** are what a browser remembers about its player: a panel
|
- **Preferences** are what a browser remembers about its player: a panel
|
||||||
@@ -198,8 +198,9 @@ Every game gets the same tools, installed by `deploy.sh` on every deploy:
|
|||||||
the rollup's last week, the backup's last word, the box's vitals.
|
the rollup's last week, the backup's last word, the box's vitals.
|
||||||
- `<slug>-rollup.sh` nightly at 00:12 UTC: one JSON line a day of counts,
|
- `<slug>-rollup.sh` nightly at 00:12 UTC: one JSON line a day of counts,
|
||||||
no addresses. `<slug>-backup.sh` nightly at 07:23 UTC: rclone to the
|
no addresses. `<slug>-backup.sh` nightly at 07:23 UTC: rclone to the
|
||||||
shared Space, mirror plus dated snapshots pruned after 90 days. Both check
|
shared Space, mirror plus dated snapshots pruned after 90 days. On the
|
||||||
in with Sentry Crons so a missed night is noticed.
|
shared host the hall's `hall-nightly.sh` runs both for every game and
|
||||||
|
checks in with Sentry Crons once for all, so a missed night is noticed.
|
||||||
- The reports desk (skill `<slug>-reports`): `pull-reports.sh` mirrors the
|
- The reports desk (skill `<slug>-reports`): `pull-reports.sh` mirrors the
|
||||||
desk to the Desktop with a digest; `report-reply.sh <ip> <id> <status>
|
desk to the Desktop with a digest; `report-reply.sh <ip> <id> <status>
|
||||||
"text"` answers with `resolved`, `by-design` or `open`. A fix goes live
|
"text"` answers with `resolved`, `by-design` or `open`. A fix goes live
|
||||||
|
|||||||
@@ -115,3 +115,11 @@ nightly rollup line in /var/lib/gamehall/rollup.jsonl. The skill
|
|||||||
`gamehall-visitors` reads them. A new game on the shared host is added to
|
`gamehall-visitors` reads them. A new game on the shared host is added to
|
||||||
`GAMES` in `gamehall/ops/traffic.py` so the digest counts the people the
|
`GAMES` in `gamehall/ops/traffic.py` so the digest counts the people the
|
||||||
hall sends it.
|
hall sends it.
|
||||||
|
|
||||||
|
The hall also keeps the night on the shared host: `hall-nightly.sh rollup`
|
||||||
|
at 00:12 UTC and `hall-nightly.sh backup` at 07:23 UTC (`gamehall.cron`)
|
||||||
|
run the hall's own rollup and every game's rollup and backup found in
|
||||||
|
/usr/local/bin, and check in with Sentry Crons once for each job, monitors
|
||||||
|
`hall-rollup` and `hall-backup` in the `gamehall` project, from the URLs in
|
||||||
|
/root/.hall-sentry-cron-rollup and /root/.hall-sentry-cron-backup. A game's
|
||||||
|
own cron file and check-ins are for a box of its own.
|
||||||
@@ -21,6 +21,8 @@ ssh "root@$HOST" '
|
|||||||
# The counting tools: installed on every publish so the live copies are the repo copies.
|
# The counting tools: installed on every publish so the live copies are the repo copies.
|
||||||
install -m 755 /tmp/gamehall-ops/gamehall-visitors.sh /usr/local/bin/gamehall-visitors.sh
|
install -m 755 /tmp/gamehall-ops/gamehall-visitors.sh /usr/local/bin/gamehall-visitors.sh
|
||||||
install -m 755 /tmp/gamehall-ops/gamehall-rollup.sh /usr/local/bin/gamehall-rollup.sh
|
install -m 755 /tmp/gamehall-ops/gamehall-rollup.sh /usr/local/bin/gamehall-rollup.sh
|
||||||
|
# The nightly job that runs the rollup and backup of every game here and checks in once for all.
|
||||||
|
install -m 755 /tmp/gamehall-ops/hall-nightly.sh /usr/local/bin/hall-nightly.sh
|
||||||
install -m 644 -D /tmp/gamehall-ops/traffic.py /usr/local/lib/gamehall/traffic.py
|
install -m 644 -D /tmp/gamehall-ops/traffic.py /usr/local/lib/gamehall/traffic.py
|
||||||
install -m 644 /tmp/gamehall-ops/gamehall.cron /etc/cron.d/gamehall
|
install -m 644 /tmp/gamehall-ops/gamehall.cron /etc/cron.d/gamehall
|
||||||
# The hall-wide notice every game serves at /hall/announcement.json.
|
# The hall-wide notice every game serves at /hall/announcement.json.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# and the people sent on to each game here). Counts only: no addresses are
|
# and the people sent on to each game here). Counts only: no addresses are
|
||||||
# kept, so the trend outlives the self-rotating access log.
|
# kept, so the trend outlives the self-rotating access log.
|
||||||
# gamehall-rollup.sh [YYYY-MM-DD] (default: yesterday, UTC)
|
# gamehall-rollup.sh [YYYY-MM-DD] (default: yesterday, UTC)
|
||||||
# Cron: /etc/cron.d/gamehall, installed by gamehall/deploy.sh.
|
# Run nightly by hall-nightly.sh rollup (/etc/cron.d/gamehall).
|
||||||
set -u
|
set -u
|
||||||
OUT="/var/lib/gamehall/rollup.jsonl"
|
OUT="/var/lib/gamehall/rollup.jsonl"
|
||||||
LOG="/var/log/gamehall/rollup.log"
|
LOG="/var/log/gamehall/rollup.log"
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
# Kestrel's Hall: nightly rollup of yesterday's counts. UTC.
|
# Kestrel's Hall: the nightly work of every game on this host, and the hall's own count. UTC.
|
||||||
5 0 * * * root /usr/local/bin/gamehall-rollup.sh
|
# The times are also SCHEDULE in hall-nightly.sh, which tells Sentry to expect them; change both.
|
||||||
|
12 0 * * * root /usr/local/bin/hall-nightly.sh rollup
|
||||||
|
23 7 * * * root /usr/local/bin/hall-nightly.sh backup
|
||||||
Executable
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# The hall's nightly work for every game on this host, with one Sentry Crons
|
||||||
|
# check-in for the lot: the rollups just after midnight, the backups at
|
||||||
|
# breakfast. Each game's own script does its work and says by its exit
|
||||||
|
# status whether it managed; the games are found by their scripts in
|
||||||
|
# /usr/local/bin, so a game deployed here is covered without being listed.
|
||||||
|
# hall-nightly.sh rollup|backup
|
||||||
|
# Cron: /etc/cron.d/gamehall, installed by gamehall/deploy.sh. SCHEDULE
|
||||||
|
# below must match it, because Sentry is told the same shape.
|
||||||
|
# Sentry Crons check-in: /root/.hall-sentry-cron-<job> holds the check-in
|
||||||
|
# URL (absent = no check-ins).
|
||||||
|
set -u
|
||||||
|
JOB="${1:-}"
|
||||||
|
case "$JOB" in
|
||||||
|
rollup) SCHEDULE="12 0 * * *" ;;
|
||||||
|
backup) SCHEDULE="23 7 * * *" ;;
|
||||||
|
*) echo "usage: hall-nightly.sh rollup|backup" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
LOG="/var/log/gamehall/nightly.log"
|
||||||
|
CRON_URL=$(cat "/root/.hall-sentry-cron-$JOB" 2>/dev/null || true)
|
||||||
|
# One check-in, when the run ends, carrying its duration and the schedule
|
||||||
|
# (Sentry keeps a monitor only once told its shape). A start and a finish
|
||||||
|
# sent seconds apart can reach Sentry in either order, and a start that lands
|
||||||
|
# second is never closed. A run that never ends sends nothing, and Sentry
|
||||||
|
# marks the night missed.
|
||||||
|
START=$SECONDS
|
||||||
|
checkin() {
|
||||||
|
[ -n "$CRON_URL" ] && curl -sf -o /dev/null -X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"status\":\"$1\",\"duration\":$((SECONDS - START)),\"monitor_config\":{\"schedule\":{\"type\":\"crontab\",\"value\":\"$SCHEDULE\"},\"checkin_margin\":30,\"max_runtime\":30,\"timezone\":\"UTC\"}}" \
|
||||||
|
"$CRON_URL" || true
|
||||||
|
}
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
|
||||||
|
# The hall's own count first, then each game's script, in name order.
|
||||||
|
scripts=()
|
||||||
|
[ "$JOB" = rollup ] && scripts+=(/usr/local/bin/gamehall-rollup.sh)
|
||||||
|
for s in /usr/local/bin/*-"$JOB".sh; do
|
||||||
|
[ "$s" = /usr/local/bin/gamehall-rollup.sh ] || scripts+=("$s")
|
||||||
|
done
|
||||||
|
failed=()
|
||||||
|
for s in "${scripts[@]}"; do
|
||||||
|
name=$(basename "$s" "-$JOB.sh")
|
||||||
|
"$s" || failed+=("$name")
|
||||||
|
done
|
||||||
|
if [ ${#failed[@]} -eq 0 ]; then
|
||||||
|
echo "$(date -Is) $JOB: ${#scripts[@]} done" >> "$LOG"
|
||||||
|
checkin ok
|
||||||
|
else
|
||||||
|
echo "$(date -Is) $JOB: ${#scripts[@]} run, failed: ${failed[*]}" >> "$LOG"
|
||||||
|
checkin error
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 33 KiB |
+47
-20
@@ -22,7 +22,6 @@
|
|||||||
--line: #2c3236;
|
--line: #2c3236;
|
||||||
--bone: #e6dfcf;
|
--bone: #e6dfcf;
|
||||||
--bone-dim: #a9a494;
|
--bone-dim: #a9a494;
|
||||||
--bone-faint: #7c7868;
|
|
||||||
--gold: #d2a94a;
|
--gold: #d2a94a;
|
||||||
--gutter: 16px;
|
--gutter: 16px;
|
||||||
}
|
}
|
||||||
@@ -56,6 +55,7 @@
|
|||||||
color: var(--bone-dim);
|
color: var(--bone-dim);
|
||||||
}
|
}
|
||||||
header.mast p.lede b { color: var(--bone); font-weight: 500; }
|
header.mast p.lede b { color: var(--bone); font-weight: 500; }
|
||||||
|
header.mast p.newcomer { margin: 10px auto 0; max-width: 38em; font-size: 0.95rem; color: var(--bone-dim); }
|
||||||
|
|
||||||
.games { display: grid; gap: 22px; grid-template-columns: repeat(2, 1fr); margin-top: 22px; }
|
.games { display: grid; gap: 22px; grid-template-columns: repeat(2, 1fr); margin-top: 22px; }
|
||||||
@media (max-width: 860px) { .games { grid-template-columns: 1fr; max-width: 520px; margin-left: auto; margin-right: auto; } }
|
@media (max-width: 860px) { .games { grid-template-columns: 1fr; max-width: 520px; margin-left: auto; margin-right: auto; } }
|
||||||
@@ -73,14 +73,18 @@
|
|||||||
.game .art { position: relative; aspect-ratio: 5 / 3; background: #222; overflow: hidden; }
|
.game .art { position: relative; aspect-ratio: 5 / 3; background: #222; overflow: hidden; }
|
||||||
.game .art img.fill { width: 100%; height: 100%; object-fit: cover; display: block; }
|
.game .art img.fill { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.game .art img.tafl { object-position: 50% 50%; }
|
.game .art img.tafl { object-position: 50% 50%; }
|
||||||
.game .art img.hands { object-position: 0% 50%; }
|
|
||||||
.game .art img.dip { object-position: 50% 80%; }
|
.game .art img.dip { object-position: 50% 80%; }
|
||||||
.game .body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
.game .body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
||||||
.game h2 { margin: 0; font-size: 1.45rem; font-weight: 500; }
|
.game h2 { margin: 0; font-size: 1.45rem; font-weight: 500; }
|
||||||
.game .pitch { margin: 0; color: var(--bone); }
|
.game .pitch { margin: 0; color: var(--bone); }
|
||||||
.game .facts { margin: 0; color: var(--bone-faint); font-size: 0.92rem; }
|
.game .players, .game .length { margin: 0; color: var(--bone-dim); font-size: 0.92rem; }
|
||||||
.game .play { margin-top: auto; padding-top: 10px; color: var(--gold); font-size: 1rem; }
|
.game .origin { margin: 0; color: var(--bone-dim); font-size: 0.85rem; }
|
||||||
|
.game .length:empty { display: none; }
|
||||||
|
.game .go { margin-top: auto; padding-top: 10px; display: flex; flex-direction: column; gap: 2px; }
|
||||||
|
.game .play { color: var(--gold); font-size: 1rem; }
|
||||||
|
.game .site { color: var(--bone-dim); font-size: 0.85rem; }
|
||||||
.game:hover .play { text-decoration: underline; }
|
.game:hover .play { text-decoration: underline; }
|
||||||
|
.nowrap { white-space: nowrap; }
|
||||||
|
|
||||||
/* The maze card: a stone floor in CSS, mortar-coloured walls, and the hall's own token drawings. */
|
/* The maze card: a stone floor in CSS, mortar-coloured walls, and the hall's own token drawings. */
|
||||||
.maze {
|
.maze {
|
||||||
@@ -111,7 +115,7 @@
|
|||||||
.keeper img { width: 84px; height: 84px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; border: 2px solid var(--line); }
|
.keeper img { width: 84px; height: 84px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; border: 2px solid var(--line); }
|
||||||
@media (max-width: 480px) { .keeper { flex-direction: column; } }
|
@media (max-width: 480px) { .keeper { flex-direction: column; } }
|
||||||
|
|
||||||
footer { margin-top: 64px; padding: 28px 0 48px; border-top: 1px solid var(--line); color: var(--bone-faint); font-size: 0.9rem; }
|
footer { margin-top: 64px; padding: 28px 0 48px; border-top: 1px solid var(--line); color: var(--bone-dim); font-size: 0.9rem; }
|
||||||
footer p { margin: 0 0 8px; }
|
footer p { margin: 0 0 8px; }
|
||||||
/* The hall-wide notice: its own colors, the same in every game of the hall. */
|
/* The hall-wide notice: its own colors, the same in every game of the hall. */
|
||||||
.hall-announcement { position: sticky; top: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; gap: .8rem; padding: .45rem 2.4rem .45rem 1rem; background: #f2c45a; color: #1d1a12; font: 500 .95rem/1.35 system-ui, -apple-system, 'Segoe UI', sans-serif; text-align: center; box-shadow: 0 1px 4px #0005; }
|
.hall-announcement { position: sticky; top: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; gap: .8rem; padding: .45rem 2.4rem .45rem 1rem; background: #f2c45a; color: #1d1a12; font: 500 .95rem/1.35 system-ui, -apple-system, 'Segoe UI', sans-serif; text-align: center; box-shadow: 0 1px 4px #0005; }
|
||||||
@@ -123,10 +127,13 @@
|
|||||||
|
|
||||||
<header class="mast">
|
<header class="mast">
|
||||||
<h1>Kestrel's Hall</h1>
|
<h1>Kestrel's Hall</h1>
|
||||||
<p class="lede"><b>Free board games in the browser.</b> Play with friends by sending a link, or alone against the house. No accounts, no adverts, no timers, and nothing sold.</p>
|
<p class="lede"><b>Free board games in the browser.</b> Play alone against the house right away, or start a table and send friends the link. No accounts, no adverts, no timers, and nothing sold.</p>
|
||||||
|
<!-- A word for newcomers, one line to tune or remove. -->
|
||||||
|
<p class="newcomer">New here? Wiz-War is the friendliest place to start, and a game can be over in minutes. Dip is the long game.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="games">
|
<div class="games">
|
||||||
|
<!-- Each card's empty "length" line is for a typical game's length, once real play has shown it; it is hidden until filled. -->
|
||||||
|
|
||||||
<a class="game" href="https://wizwar.kestrelsnest.social">
|
<a class="game" href="https://wizwar.kestrelsnest.social">
|
||||||
<div class="art">
|
<div class="art">
|
||||||
@@ -150,18 +157,28 @@
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
<h2>Wiz-War</h2>
|
<h2>Wiz-War</h2>
|
||||||
<p class="pitch">Steal treasure. Blast through walls. Outwit your friends.</p>
|
<p class="pitch">Steal treasure. Blast through walls. Outwit your friends.</p>
|
||||||
<p class="facts">2 to 6 players · an unofficial rebuild of Tom Jolly's 1993 board game</p>
|
<p class="players">2 to 6 players, or solo against the automaton</p>
|
||||||
<span class="play">Play at wizwar.kestrelsnest.social →</span>
|
<p class="origin">An unofficial rebuild of Tom Jolly's 1993 board game</p>
|
||||||
|
<p class="length"></p>
|
||||||
|
<div class="go">
|
||||||
|
<span class="play">Play Wiz-War →</span>
|
||||||
|
<span class="site">wizwar.kestrelsnest.social</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="game" href="https://hands.kestrelsnest.social">
|
<a class="game" href="https://hands.kestrelsnest.social">
|
||||||
<div class="art"><img class="fill hands" src="hands.png" alt="The Waving Hands share card: a column of gesture letters beside the title."></div>
|
<div class="art"><img class="fill" src="hands.png" alt="Four turns of a Waving Hands duel: each row a turn, each hand drawn in its gesture, and a spell named under the gesture that finished it."></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<h2>Waving Hands</h2>
|
<h2>Waving Hands</h2>
|
||||||
<p class="pitch">Wizards duel with their hands, and the right run of gestures is a spell.</p>
|
<p class="pitch">Wizards duel with their hands, and the right run of gestures is a spell.</p>
|
||||||
<p class="facts">2 to 12 players · Richard Bartle's pencil-and-paper duel, 1977</p>
|
<p class="players">2 to 12 players, or solo against a rival wizard</p>
|
||||||
<span class="play">Play at hands.kestrelsnest.social →</span>
|
<p class="origin">Richard Bartle's pencil-and-paper duel, 1977</p>
|
||||||
|
<p class="length"></p>
|
||||||
|
<div class="go">
|
||||||
|
<span class="play">Play Waving Hands →</span>
|
||||||
|
<span class="site">hands.kestrelsnest.social</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -170,8 +187,13 @@
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
<h2>Hnefatafl</h2>
|
<h2>Hnefatafl</h2>
|
||||||
<p class="pitch">The Viking board game of a king breaking for safety and a ring of attackers closing in.</p>
|
<p class="pitch">The Viking board game of a king breaking for safety and a ring of attackers closing in.</p>
|
||||||
<p class="facts">2 players · the Norse tafl game, by the Copenhagen rules or Linnaeus's 1732 Tablut</p>
|
<p class="players">2 players, or solo against the housecarl</p>
|
||||||
<span class="play">Play at tafl.kestrelsnest.social →</span>
|
<p class="origin">The Norse tafl game, by the Copenhagen rules or Linnaeus's 1732 Tablut</p>
|
||||||
|
<p class="length"></p>
|
||||||
|
<div class="go">
|
||||||
|
<span class="play">Play Hnefatafl →</span>
|
||||||
|
<span class="site">tafl.kestrelsnest.social</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -179,9 +201,14 @@
|
|||||||
<div class="art"><img class="fill dip" src="dip.jpg" alt="Dip's map of Europe at the opening: the seven powers' armies and fleets on their home centers, each power's lands in its color."></div>
|
<div class="art"><img class="fill dip" src="dip.jpg" alt="Dip's map of Europe at the opening: the seven powers' armies and fleets on their home centers, each power's lands in its color."></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<h2>Dip</h2>
|
<h2>Dip</h2>
|
||||||
<p class="pitch">Seven powers share one map, and none of them has to keep its word.</p>
|
<p class="pitch">Seven powers share one map, and none of them has to keep its word. Other maps seat five or ten.</p>
|
||||||
<p class="facts">2 to 7 players · an unofficial rebuild of Allan B. Calhamer's 1959 Diplomacy</p>
|
<p class="players">2 to 10 players, depending on the map, or solo against the house's envoys</p>
|
||||||
<span class="play">Play at dip.kestrelsnest.social →</span>
|
<p class="origin">An unofficial rebuild of Allan B. Calhamer's 1959 Diplomacy</p>
|
||||||
|
<p class="length"></p>
|
||||||
|
<div class="go">
|
||||||
|
<span class="play">Play Dip →</span>
|
||||||
|
<span class="site">dip.kestrelsnest.social</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -191,9 +218,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>How a table works</h2>
|
<h2>How a table works</h2>
|
||||||
<ul class="how">
|
<ul class="how">
|
||||||
<li><b>Open a table</b> and you get a four-letter code and a link. Send it to a friend, and begin once they sit down.</li>
|
<li><b>Start a table</b> and you get a four-letter code and a link. Send it to a friend, and begin once they sit down.</li>
|
||||||
<li><b>No one home?</b> Every game can seat the house, whether an automaton, a housecarl, a rival wizard or an envoy, so you can play right away on your own.</li>
|
<li><b>No one home?</b> Every game can seat the house, whether an automaton, a housecarl, a rival wizard or an envoy, so you can play right away on your own.</li>
|
||||||
<li><b>Nothing to sign up for.</b> Your browser holds your seat. Close the tab and come back, and the game is where you left it.</li>
|
<li><b>Nothing to sign up for.</b> Your browser holds your seat. Come back in the same browser and the game is where you left it. On another device, use Transfer seat in any game to carry your seat over with four words.</li>
|
||||||
<li><b>Watchers welcome.</b> Anyone with the link can sit in the Peanut Gallery and, if the host allows, join the table talk.</li>
|
<li><b>Watchers welcome.</b> Anyone with the link can sit in the Peanut Gallery and, if the host allows, join the table talk.</li>
|
||||||
<li><b>Something went wrong?</b> Every table has a report button that reaches the keeper with the exact moment attached.</li>
|
<li><b>Something went wrong?</b> Every table has a report button that reaches the keeper with the exact moment attached.</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -208,12 +235,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 style="margin-top:28px">The hosting</h2>
|
<h2 style="margin-top:28px">The hosting</h2>
|
||||||
<p>The games are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at <a href="https://ko-fi.com/kestrelsnest">ko-fi.com/kestrelsnest</a>. Every game stays whole either way.</p>
|
<p>The games are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at <a class="nowrap" href="https://ko-fi.com/kestrelsnest">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and nothing is held back without it.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Wiz-War is Tom Jolly's design, and the rights rest with Steve Jackson Games. Waving Hands is Richard Bartle's. Diplomacy is Allan B. Calhamer's design, and its name is a trademark of Hasbro. Nothing here is official or endorsed by Steve Jackson Games, Richard Bartle or Hasbro. Hnefatafl belongs to everyone, and is more than a thousand years old.</p>
|
<p>Wiz-War is Tom Jolly's design, and the rights rest with Steve Jackson Games. Waving Hands is Richard Bartle's. Hnefatafl belongs to everyone, and is more than a thousand years old. Diplomacy is Allan B. Calhamer's design, and its name is a trademark of Hasbro; the designers of Dip's Chromatic and Youngstown Redux maps are credited in Dip's How to play. Nothing here is official or endorsed by Steve Jackson Games, Richard Bartle or Hasbro.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ A fresh host, when one is needed: `doctl compute droplet create hall --region ny
|
|||||||
|
|
||||||
## Operations scripts on the droplet
|
## Operations scripts on the droplet
|
||||||
|
|
||||||
`deploy.sh` installs these to /usr/local/bin, the `traffic.py` they share to
|
`deploy.sh` installs these to /usr/local/bin and the `traffic.py` they share
|
||||||
/usr/local/lib/__SLUG__, and the cron file to /etc/cron.d/__SLUG__ on every
|
to /usr/local/lib/__SLUG__ on every deploy, so the live copies are the repo
|
||||||
deploy, so the live copies are the repo copies:
|
copies. The cron file goes to /etc/cron.d/__SLUG__ only on a box of the game's
|
||||||
|
own: on the hall's shared host, `hall-nightly.sh` (in the kit's `gamehall/ops`)
|
||||||
|
runs every game's rollup and backup from /etc/cron.d/gamehall.
|
||||||
|
|
||||||
- `__SLUG__-visitors.sh [day]`: who is here now and who came that day.
|
- `__SLUG__-visitors.sh [day]`: who is here now and who came that day.
|
||||||
- `__SLUG__-pulse.sh`: the weekly health check (service, errors, rollup
|
- `__SLUG__-pulse.sh`: the weekly health check (service, errors, rollup
|
||||||
@@ -62,10 +64,13 @@ deploy, so the live copies are the repo copies:
|
|||||||
wizwar droplet; until then it logs "skipped".
|
wizwar droplet; until then it logs "skipped".
|
||||||
|
|
||||||
Errors from the game server go to Sentry, project `__SLUG__` in the
|
Errors from the game server go to Sentry, project `__SLUG__` in the
|
||||||
locallygrownnet organisation; the DSN is in the unit file. The nightly rollup
|
locallygrownnet organisation; the DSN is in the unit file. On the shared host
|
||||||
and backup check in with Sentry Crons when /root/.__SLUG__-sentry-cron-rollup
|
the hall's nightly job checks in with Sentry Crons once for every game's
|
||||||
and /root/.__SLUG__-sentry-cron hold their check-in URLs (the ingest
|
rollup and once for every backup (monitors `hall-rollup` and `hall-backup` in
|
||||||
URL with the project's cron path and public key), so a missed night is noticed.
|
the `gamehall` project), and each game's script reports by its exit status.
|
||||||
|
A game on its own box checks in for itself when /root/.__SLUG__-sentry-cron-rollup
|
||||||
|
and /root/.__SLUG__-sentry-cron hold check-in URLs (the ingest URL with the
|
||||||
|
project's cron path and public key); absent, it sends nothing.
|
||||||
|
|
||||||
To have every new issue, regression and reappearance posted to Slack the way
|
To have every new issue, regression and reappearance posted to Slack the way
|
||||||
wizwar's are, run `deploy/sentry-slack-alert.sh [#channel]` once from your own
|
wizwar's are, run `deploy/sentry-slack-alert.sh [#channel]` once from your own
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
# Ledgers are append-only JSONL; the game server never needs stopping.
|
# Ledgers are append-only JSONL; the game server never needs stopping.
|
||||||
# Sentry Crons check-in: /root/.__SLUG__-sentry-cron holds the check-in
|
# Sentry Crons check-in: /root/.__SLUG__-sentry-cron holds the check-in
|
||||||
# URL (absent file = no check-ins). SCHEDULE must match /etc/cron.d/__SLUG__.
|
# URL (absent file = no check-ins). SCHEDULE must match /etc/cron.d/__SLUG__.
|
||||||
|
# On a host the hall shares, hall-nightly.sh runs this and checks in once
|
||||||
|
# for every game, so the file is absent and the exit status is the word.
|
||||||
set -u
|
set -u
|
||||||
SCHEDULE="23 7 * * *"
|
SCHEDULE="23 7 * * *"
|
||||||
BUCKET="kestrel-wizwar-backups" # the hall's shared Space, named for its first tenant
|
BUCKET="kestrel-wizwar-backups" # the hall's shared Space, named for its first tenant
|
||||||
@@ -27,9 +29,10 @@ checkin() {
|
|||||||
|
|
||||||
if [ ! -s /root/.config/rclone/rclone.conf ]; then
|
if [ ! -s /root/.config/rclone/rclone.conf ]; then
|
||||||
echo "$(date -Is) skipped: rclone is not configured" >> "$LOG"
|
echo "$(date -Is) skipped: rclone is not configured" >> "$LOG"
|
||||||
exit 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
status=0
|
||||||
{
|
{
|
||||||
echo "=== $(date -Is) backup start"
|
echo "=== $(date -Is) backup start"
|
||||||
if rclone sync "$SRC" "spaces:$BUCKET/$PREFIX/current" --exclude 'rollup.jsonl' 2>&1 &&
|
if rclone sync "$SRC" "spaces:$BUCKET/$PREFIX/current" --exclude 'rollup.jsonl' 2>&1 &&
|
||||||
@@ -37,6 +40,7 @@ fi
|
|||||||
checkin ok
|
checkin ok
|
||||||
else
|
else
|
||||||
checkin error
|
checkin error
|
||||||
|
status=1
|
||||||
fi
|
fi
|
||||||
CUTOFF=$(date -d "90 days ago" +%Y-%m-%d)
|
CUTOFF=$(date -d "90 days ago" +%Y-%m-%d)
|
||||||
rclone lsf "spaces:$BUCKET/$PREFIX/snapshots/" --dirs-only 2>/dev/null | \
|
rclone lsf "spaces:$BUCKET/$PREFIX/snapshots/" --dirs-only 2>/dev/null | \
|
||||||
@@ -49,3 +53,4 @@ fi
|
|||||||
done
|
done
|
||||||
echo "=== $(date -Is) backup done"
|
echo "=== $(date -Is) backup done"
|
||||||
} >> "$LOG" 2>&1
|
} >> "$LOG" 2>&1
|
||||||
|
exit $status
|
||||||
@@ -8,7 +8,9 @@
|
|||||||
# Cron: /etc/cron.d/__SLUG__, installed by deploy.sh; SCHEDULE below
|
# Cron: /etc/cron.d/__SLUG__, installed by deploy.sh; SCHEDULE below
|
||||||
# must match it, because the Sentry monitor is told the same shape.
|
# must match it, because the Sentry monitor is told the same shape.
|
||||||
# Sentry Crons check-in: /root/.__SLUG__-sentry-cron-rollup holds the
|
# Sentry Crons check-in: /root/.__SLUG__-sentry-cron-rollup holds the
|
||||||
# check-in URL (absent = no check-ins).
|
# check-in URL (absent = no check-ins). On a host the hall shares,
|
||||||
|
# hall-nightly.sh runs this and checks in once for every game, so the
|
||||||
|
# file is absent and the exit status is the word.
|
||||||
set -u
|
set -u
|
||||||
OUT="/var/lib/__SLUG__/rollup.jsonl"
|
OUT="/var/lib/__SLUG__/rollup.jsonl"
|
||||||
LOG="/var/log/__SLUG__/rollup.log"
|
LOG="/var/log/__SLUG__/rollup.log"
|
||||||
@@ -78,4 +80,4 @@ with open(out + ".tmp", "w") as fh:
|
|||||||
os.replace(out + ".tmp", out)
|
os.replace(out + ".tmp", out)
|
||||||
print("%s rolled: %d people, %d requests, %d rooms" % (day, len(t["addresses"]), t["human"], created))
|
print("%s rolled: %d people, %d requests, %d rooms" % (day, len(t["addresses"]), t["human"], created))
|
||||||
PY
|
PY
|
||||||
then checkin ok; else checkin error; fi
|
then checkin ok; else checkin error; exit 1; fi
|
||||||
@@ -43,7 +43,11 @@ ssh "root@$HOST" '
|
|||||||
install -m 755 /opt/__SLUG__/app/deploy/__SLUG__-rollup.sh /usr/local/bin/__SLUG__-rollup.sh
|
install -m 755 /opt/__SLUG__/app/deploy/__SLUG__-rollup.sh /usr/local/bin/__SLUG__-rollup.sh
|
||||||
install -m 755 /opt/__SLUG__/app/deploy/__SLUG__-backup.sh /usr/local/bin/__SLUG__-backup.sh
|
install -m 755 /opt/__SLUG__/app/deploy/__SLUG__-backup.sh /usr/local/bin/__SLUG__-backup.sh
|
||||||
install -m 644 -D /opt/__SLUG__/app/deploy/traffic.py /usr/local/lib/__SLUG__/traffic.py
|
install -m 644 -D /opt/__SLUG__/app/deploy/traffic.py /usr/local/lib/__SLUG__/traffic.py
|
||||||
install -m 644 /opt/__SLUG__/app/deploy/__SLUG__.cron /etc/cron.d/__SLUG__
|
# On a host the hall shares, its hall-nightly.sh runs the rollup and backup
|
||||||
|
# of every game and checks in with Sentry once for all; a game on a box of
|
||||||
|
# its own schedules its own.
|
||||||
|
if [ -f /etc/cron.d/gamehall ]; then rm -f /etc/cron.d/__SLUG__
|
||||||
|
else install -m 644 /opt/__SLUG__/app/deploy/__SLUG__.cron /etc/cron.d/__SLUG__; fi
|
||||||
mkdir -p /var/log/__SLUG__
|
mkdir -p /var/log/__SLUG__
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now __SLUG__
|
systemctl enable --now __SLUG__
|
||||||
|
|||||||
@@ -330,7 +330,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<p class="hosting">
|
<p class="hosting">
|
||||||
The games of Kestrel's Hall are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at
|
The games of Kestrel's Hall are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at
|
||||||
<a href="https://ko-fi.com/kestrelsnest" target="_blank" rel="noreferrer">ko-fi.com/kestrelsnest</a>. Every game stays whole either way.
|
<a href="https://ko-fi.com/kestrelsnest" target="_blank" rel="noreferrer">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and nothing is held back without it.
|
||||||
</p>
|
</p>
|
||||||
<p class="made">__NAME__ is its designer's.</p>
|
<p class="made">__NAME__ is its designer's.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user