warp-window: home window 07:00-17:00
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / lib-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled

Mon/Thu/Fri WARP window at home widens from 08:00-16:00 to
07:00-17:00. Away from home WARP stays always available.
Prunes the oldest evidence file for the 60-file cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWVh2cxQQcZ54imRZXw
This commit is contained in:
Krzysztof Rudnicki 2026-11-09 07:58:19 +01:00
parent 1a56a77ff6
commit 7e9fdefde4
8 changed files with 82 additions and 77 deletions

View File

@ -120,8 +120,8 @@ with no internet. So the laptop runs `warp-home` (user timer, every 30 s): at
home with no window open it turns WARP off, and plain internet with the
blocklist comes back. On any other network (office) it never touches WARP.
- Mon/Thu/Fri 08:00-16:00 at home: `warp-home on` asks the PC, then connects.
The PC opens WARP for the LAN until 16:00 sharp, then closes by itself.
- Mon/Thu/Fri 07:00-17:00 at home: `warp-home on` asks the PC, then connects.
The PC opens WARP for the LAN until 17:00 sharp, then closes by itself.
Outside that window the PC refuses. `warp-home off` / `warp-home status`.
- Window closed early (the PC firewall was re-applied): `warp-home on` again.
- PC side by hand: `sudo warp-window open|close|status`.
@ -139,4 +139,4 @@ blocklist comes back. On any other network (office) it never touches WARP.
real IPv6 address); Firefox DoH canary + DoH hostnames blocked; DHCP lease
12h -> 2h; gateway mode added (nextcloud-pi and the P110 plug exempt).
- 2026-10-25: WARP tunnel dropped at the gateway (work laptop bypassed the
DNS block through it); Mon/Thu/Fri 08:00-16:00 window via `warp-home on`.
DNS block through it); Mon/Thu/Fri 07:00-17:00 window via `warp-home on`.

View File

@ -0,0 +1,14 @@
{
"title": "WARP home window: widen to 07:00-17:00",
"objective": "kuhy can run `warp-home on` on the work laptop at home Mon/Thu/Fri 07:00-17:00 (was 08:00-16:00); the PC's clock still decides, WARP stays blocked at home outside that window, and on any other network WARP is always available.",
"acceptance_criteria": [
"warp_window.sh open succeeds only Mon/Thu/Fri 07:00:00-16:59:59 and fills warp_open with a timeout ending at 17:00; Tue/Wed/Sat/Sun stay closed (test_warp_window.sh)",
"warp_home.sh messages name Mon/Thu/Fri 07:00-17:00 and the 17:00 cut-off; away from home tick never touches WARP and on connects without the PC (test_warp_home.sh)",
"Live: Monday 07:56 the installed PC script reports the window open until 17:00 and `warp-home on` connects WARP"
],
"out_of_scope": [
"Window days, install path, sudoers entry and authorized_keys forced command (unchanged)",
"Jail-only suites in features/lib/tests (dwm, nc_php, rpi_nc, dot_resolver_install, website_admin)"
],
"verifier": "linux_configuration/features/lib/tests/test_warp_window.sh; linux_configuration/features/lib/tests/test_warp_home.sh; on the laptop: warp-home on && warp-home status"
}

View File

@ -0,0 +1,37 @@
{
"intent": "The home WARP window for the work laptop opens Mon/Thu/Fri 07:00-17:00 instead of 08:00-16:00. Away from home WARP stays always available, any day or time.",
"scope": [
"linux_configuration/features/warp_window.sh (PC, enforces the window)",
"linux_configuration/features/warp_home.sh (laptop, messages only)",
"DOCS-lan-blocker-contingency.md, test_warp_window.sh, test_warp_home.sh",
"No change to days, install, sudoers, the ssh forced command or the away-from-home path"
],
"changes": [
"OPEN_SEC 08:00 -> 07:00; CLOSE_SEC 16:00 -> 17:00; refusal/open/warning messages updated",
"test_warp_window.sh asserts the 06:59:59/07:00/16:59:59/17:00 edges and a 25200 s timeout at Mon 10:00"
],
"verification": [
{
"command": "linux_configuration/features/lib/tests/test_warp_window.sh (before the change)",
"result": "fail",
"evidence": "red first: 27 passed, 8 failed (07:00 closed, 14400 instead of 18000 at Friday noon)"
},
{
"command": "linux_configuration/features/lib/tests/test_warp_window.sh; test_warp_home.sh",
"result": "pass",
"evidence": "35 passed, 0 failed; 35 passed, 0 failed (incl. away-from-home tick never touches WARP, on away connects without the PC)"
},
{
"command": "sudo install -m 0755 warp_window.sh /usr/local/sbin/warp-window; scp warp_home.sh to laptop ~/.local/bin/warp-home; . /usr/local/sbin/warp-window; window_seconds_left",
"result": "pass",
"evidence": "Mon 2026-11-09 07:56:42 -> 32598 s left (open until 17:00; closed under the old window); kuhy ran warp-home on and confirmed it works"
}
],
"risks": [
"Laptop copy is installed by hand; a later warp_home.sh install overwrites it with the repo version (same content now)"
],
"rollback": [
"git revert this commit, then sudo install -m 0755 warp_window.sh /usr/local/sbin/warp-window and copy warp_home.sh to the laptop again",
"Then: window_seconds_left is closed at Mon 07:30 and 16:30"
]
}

View File

@ -1,46 +0,0 @@
{
"intent": "Kept /wsg/ videos go to the dufs cloud folder (or ~/Downloads as fallback) instead of a buried data dir, and trash/ is capped at the newest 200 passes with everything older deleted.",
"scope": [
"python_pkg/wsg_grabber/paths.py: cloud_root, downloads_dir, cloud_keep_dir, keep_dir (cloud or Downloads), legacy_keep_dir, kept_candidates",
"python_pkg/wsg_grabber/_prune.py, _migrate_keep.py, housekeeping.py (new); files.remove; FileState.PURGED; store_verdicts recent_passed_names / purge_older_passes / rename_kept",
"app.py wires on_startup / after_verdict / locate_reviewed; cli.py and README drop the 'nothing is ever deleted' promise",
"Non-goal: capping kept files, touching dufs-cloud or the media sync timer"
],
"changes": [
"keep_dir() resolves per call to ~/data/cloud/Media/<year>/wsg when the cloud folder exists, else ~/Downloads",
"prune_trash deletes every trash/ file outside the 200 newest reviewed_at passes (orphans included) and marks the rows purged; runs at startup and after each pass",
"migrate_keep moves the legacy keep/ into the current keep dir once (dedupe by name+size, suffix + index rename on clash) and removes the empty dir",
"undo locates a kept file through kept_candidates so year rollover, cloud absence and the media sync sweep do not lose the verdict"
],
"verification": [
{
"command": "~/.claude/scripts/capped.sh python -m pytest python_pkg/wsg_grabber/tests -q",
"result": "pass",
"evidence": "303 passed; every python_pkg/wsg_grabber module at 100% line+branch coverage"
},
{
"command": "HOME=/tmp/wsgfake/home XDG_DATA_HOME=$HOME/.local/share python -m python_pkg.wsg_grabber scrape --seconds 1 (copy of the real index, 23218 stand-in trash files, 50 stand-in keep files, fake data/cloud)",
"result": "pass",
"evidence": "keep.migrated moved=50; trash.pruned removed=23018 purged_rows=23018 retained=200; all 200 newest reviewed_at names present; keep/ removed; stats shows keep dir under data/cloud, and ~/Downloads after rm -rf data/cloud"
},
{
"command": "python -m python_pkg.wsg_grabber scrape --seconds 1 (real home)",
"result": "pass",
"evidence": "keep/ 342 -> ~/data/cloud/Media/2026/wsg 647 (was 305), keep/ gone; trash 23218 files / 77G -> 200 files / 823M; stats: kept 648, passed 200, purged 23018"
},
{
"command": "pre-commit run --files <18 changed files>",
"result": "pass",
"evidence": "no hook output beyond Passed/Skipped"
}
],
"risks": [
"Purging is irreversible; the first run deleted 77 GB of passed videos, as agreed (Q3).",
"A keep made while the cloud folder is absent lands in ~/Downloads and is swept by media-cloud-sync into Media/<year>/<month>; undo follows it via kept_candidates but a same-name suffix applied by that sync would break the link.",
"Kept videos in Media/<year>/wsg now appear in the dufs gallery and get thumbnails."
],
"rollback": [
"git revert the commit; kept files stay in the cloud folder (undo will still find them there through the old keep_dir only if moved back by hand)",
"Validate with `python -m python_pkg.wsg_grabber stats` and the test suite"
]
}

View File

@ -120,7 +120,7 @@ _t_not "the early-closed window is forgotten" test -e "$OPEN_UNTIL"
SSH_OUT="seconds_left=600"
printf '\n-- on: a refusal connects nothing --\n'
SSH_OUT="WARP window is Mon/Thu/Fri 08:00-16:00 only"
SSH_OUT="WARP window is Mon/Thu/Fri 07:00-17:00 only"
SSH_RC=1
reset_calls
on_refused() { cmd_on; }

View File

@ -2,7 +2,7 @@
# Tests for warp_window.sh -- the PC side of the work-from-home WARP window.
#
# Gateway mode drops the Cloudflare WARP tunnel because it carries everything
# past the DNS block. kuhy may open it only Mon/Thu/Fri 08:00-16:00; the laptop
# past the DNS block. kuhy may open it only Mon/Thu/Fri 07:00-17:00; the laptop
# asks over an SSH key whose forced command can run nothing but open/status.
# The window edges and the forced-command filter are the security boundary,
# so both are asserted exactly. The clock and nft are stubbed.
@ -28,26 +28,26 @@ left_at() {
window_seconds_left || printf 'closed'
}
printf '\n-- the window is Mon/Thu/Fri 08:00-16:00, nothing else --\n'
_t_eq "closed" "$(left_at '1 07 59 59')" "Monday 07:59:59 is closed"
_t_eq "28800" "$(left_at '1 08 00 00')" "Monday 08:00 opens until 16:00"
_t_eq "1" "$(left_at '1 15 59 59')" "Monday 15:59:59 has one second left"
_t_eq "closed" "$(left_at '1 16 00 00')" "Monday 16:00 is closed"
_t_eq "14400" "$(left_at '5 12 00 00')" "Friday noon is open"
_t_eq "28800" "$(left_at '4 08 00 00')" "Thursday 08:00 opens until 16:00"
_t_eq "closed" "$(left_at '4 16 00 00')" "Thursday 16:00 is closed"
printf '\n-- the window is Mon/Thu/Fri 07:00-17:00, nothing else --\n'
_t_eq "closed" "$(left_at '1 06 59 59')" "Monday 06:59:59 is closed"
_t_eq "36000" "$(left_at '1 07 00 00')" "Monday 07:00 opens until 17:00"
_t_eq "1" "$(left_at '1 16 59 59')" "Monday 16:59:59 has one second left"
_t_eq "closed" "$(left_at '1 17 00 00')" "Monday 17:00 is closed"
_t_eq "18000" "$(left_at '5 12 00 00')" "Friday noon is open"
_t_eq "36000" "$(left_at '4 07 00 00')" "Thursday 07:00 opens until 17:00"
_t_eq "closed" "$(left_at '4 17 00 00')" "Thursday 17:00 is closed"
for day in 2 3 6 7; do
_t_eq "closed" "$(left_at "$day 12 00 00")" "weekday $day noon is closed"
done
_t_eq "28260" "$(left_at '5 08 09 00')" "a leading-zero hour is decimal, not octal"
_t_eq "31860" "$(left_at '5 08 09 00')" "a leading-zero hour is decimal, not octal"
printf '\n-- open fills the set with a timeout ending at 16:00 --\n'
printf '\n-- open fills the set with a timeout ending at 17:00 --\n'
FAKE_DATE="1 10 00 00"
out="$(cmd_open)"
_t_called 'nft add element inet filter warp_open { 0.0.0.0/0 timeout 21600s }' \
_t_called 'nft add element inet filter warp_open { 0.0.0.0/0 timeout 25200s }' \
"open adds the catch-all element with the time left"
_t_has "$out" "seconds_left=21600" "open reports the seconds left for the laptop"
_t_has "$out" "until 16:00" "open names the 16:00 cut-off"
_t_has "$out" "seconds_left=25200" "open reports the seconds left for the laptop"
_t_has "$out" "until 17:00" "open names the 17:00 cut-off"
printf '\n-- outside the window open refuses and touches nothing --\n'
: >"$TEST_TMPDIR/calls.log"

View File

@ -9,7 +9,7 @@
# plain internet (with the DNS blocklist) comes back. On any other network,
# e.g. the office, it never touches WARP.
#
# warp-home on Mon/Thu/Fri 08:00-16:00 at home: ask the PC, then connect.
# warp-home on Mon/Thu/Fri 07:00-17:00 at home: ask the PC, then connect.
# Anywhere else: just connect.
# warp-home off disconnect and forget the window
# warp-home status network, window, WARP and the PC's view
@ -23,7 +23,7 @@
set -euo pipefail
readonly PC_IP="192.168.1.43" PC_MAC="9c:6b:00:5c:c5:6d" PC_USER="kuhy"
readonly WARN_SEC=300 # warn five minutes before the 16:00 cut-off
readonly WARN_SEC=300 # warn five minutes before the 17:00 cut-off
KEY="$HOME/.ssh/warp_window_ed25519"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/warp-home"
OPEN_UNTIL="$STATE_DIR/open-until"
@ -70,17 +70,17 @@ warp_off_because() {
cmd_tick() {
at_home || return 0
window_open || {
warp_off_because "WARP turned off at home. 'warp-home on' works Mon/Thu/Fri 08:00-16:00."
warp_off_because "WARP turned off at home. 'warp-home on' works Mon/Thu/Fri 07:00-17:00."
return 0
}
# Any re-apply of the PC firewall empties its set before 16:00. Only an
# Any re-apply of the PC firewall empties its set before 17:00. Only an
# explicit "closed" counts: an unreachable PC proves nothing.
if [[ $(pc status 2>/dev/null) == closed ]]; then
warp_off_because "The PC closed the WARP window early; run 'warp-home on' again."
return 0
fi
if (($(<"$OPEN_UNTIL") - $(date +%s) <= WARN_SEC)) && [[ ! -e $WARNED ]]; then
notify "WARP closes at 16:00, in five minutes."
notify "WARP closes at 17:00, in five minutes."
touch "$WARNED"
fi
}
@ -96,7 +96,7 @@ cmd_on() {
printf '%s\n' "$(($(date +%s) + left))" >"$OPEN_UNTIL"
fi
warp connect >/dev/null
printf 'WARP connecting.%s\n' "$(at_home && printf ' Home window ends at 16:00.')"
printf 'WARP connecting.%s\n' "$(at_home && printf ' Home window ends at 17:00.')"
}
cmd_off() {

View File

@ -4,8 +4,8 @@
#
# Gateway mode (setup_dns_blocker.sh gateway) drops the WARP tunnel, because
# WARP carries all traffic past the DNS block. This opens it for every LAN
# device, but ONLY Mon/Thu/Fri 08:00-16:00 by this PC's clock: the open adds one
# element to the nft set warp_open whose timeout ends at 16:00, so the kernel
# device, but ONLY Mon/Thu/Fri 07:00-17:00 by this PC's clock: the open adds one
# element to the nft set warp_open whose timeout ends at 17:00, so the kernel
# closes it again with nothing left to fail. The office days (Tue/Wed) need
# nothing here: the home gateway never touches the work network.
#
@ -23,7 +23,7 @@
set -euo pipefail
readonly WINDOW_DAYS=" 1 4 5 " # date +%u: Monday, Thursday, Friday
readonly OPEN_SEC=$((8 * 3600)) CLOSE_SEC=$((16 * 3600))
readonly OPEN_SEC=$((7 * 3600)) CLOSE_SEC=$((17 * 3600))
readonly NFT_SET="inet filter warp_open"
readonly KEY_FROM="192.168.1.0/24" # the key works only from the LAN
INSTALL_PATH="/usr/local/sbin/warp-window"
@ -36,8 +36,8 @@ die() {
exit 1
}
# Seconds until 16:00 while the window is open; fails when it is closed.
# Plain arithmetic, not `date -d 16:00`: DST never changes on a window day.
# Seconds until 17:00 while the window is open; fails when it is closed.
# Plain arithmetic, not `date -d 17:00`: DST never changes on a window day.
window_seconds_left() {
local day h m s now
read -r day h m s <<<"$(date '+%u %H %M %S')"
@ -49,13 +49,13 @@ window_seconds_left() {
cmd_open() {
local left
left="$(window_seconds_left)" || die "WARP window is Mon/Thu/Fri 08:00-16:00 only."
left="$(window_seconds_left)" || die "WARP window is Mon/Thu/Fri 07:00-17:00 only."
# Re-opening must refresh the timeout, and add keeps an old one: flush first.
# shellcheck disable=SC2086 # NFT_SET is three words on purpose
nft flush set $NFT_SET 2>/dev/null || die "no warp_open set: is gateway mode on?"
# shellcheck disable=SC2086
nft add element $NFT_SET "{ 0.0.0.0/0 timeout ${left}s }"
printf 'WARP open for the LAN until 16:00.\nseconds_left=%s\n' "$left"
printf 'WARP open for the LAN until 17:00.\nseconds_left=%s\n' "$left"
}
cmd_close() {