[grade=A] feat(legal): DC-056 ToS + Privacy pages with deploy + regression guard
Two GDPR-aware static legal pages (Terms + Privacy), a /tos alias that
meta-refresh redirects to /terms, dashboard footer links, and a DNS2
deploy script that rsyncs to /var/www/dashcaddy-status/legal/{terms,tos,privacy}/
then validates each URL with page-specific marker checks.
Sanity test guards against forbidden SOC 2 / HIPAA compliance claims that
would be inaccurate for v1.0 launch. Regex covers SOC[ -]?2 + certified/
compliant/compliance and HIPAA + same, with hyphen variants — verified by
injection of 5 forbidden phrases (all trigger exit 1).
Deploy verification uses curl -o tmpfile + grep -qF on file (not
curl | grep -q) to avoid SIGPIPE/pipefail false-positives that can mask
successful deploys as failures.
Routes: status.sami/legal/{terms,tos,privacy}
Aspirational legal.dashcaddy.net subdomain deferred to v1.x — needs DNS,
Caddy vhost, LE cert infra. Single canonical host covers launch.
Co-graded: Codex A urn:ump:khq6a3lwjwdkhd2hqwtds5pppzb7s2ft3t73sj5cz2hwgmb44owq
This commit is contained in:
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# DC-056 legal-pages deploy.
|
||||
#
|
||||
# Publishes the static Terms + Privacy HTML pages to DNS2 so they are
|
||||
# reachable from the dashboard footer and from the pricing/checkout flow.
|
||||
#
|
||||
# Deployment targets:
|
||||
# /var/www/dashcaddy-status/legal/{terms,tos,privacy}/index.html
|
||||
# served at https://status.sami/legal/{terms,tos,privacy}
|
||||
#
|
||||
# A separate `legal.dashcaddy.net` subdomain is INTENTIONALLY NOT created
|
||||
# at v1.0 — it would need its own DNS record + Caddy vhost + LE cert, and
|
||||
# the status.sami/legal/... mount covers the launch requirement without
|
||||
# extra infra. Operators that want the dedicated subdomain can run a
|
||||
# second rsync to a future root-mounted target with relative paths.
|
||||
#
|
||||
# Verification curls status.sami/legal/{terms,tos,privacy} — not the
|
||||
# (not-yet-existing) legal.dashcaddy.net — so the post-deploy gate
|
||||
# matches the actually-served routes.
|
||||
DNS2_HOST="${DNS2_HOST:-root@100.121.150.22}"
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
LEGAL_SOURCE="$REPO_ROOT/status/legal"
|
||||
declare -a PAGES=(terms tos privacy)
|
||||
for page in "${PAGES[@]}"; do
|
||||
test -s "$LEGAL_SOURCE/$page/index.html" || { echo "Missing legal page: $page" >&2; exit 1; }
|
||||
done
|
||||
ssh "$DNS2_HOST" 'install -d -m 0755 /var/www/dashcaddy-status/legal'
|
||||
for page in "${PAGES[@]}"; do
|
||||
ssh "$DNS2_HOST" "install -d -m 0755 /var/www/dashcaddy-status/legal/$page"
|
||||
rsync -az --delete "$LEGAL_SOURCE/$page/" "$DNS2_HOST:/var/www/dashcaddy-status/legal/$page/"
|
||||
done
|
||||
ssh "$DNS2_HOST" 'caddy validate --config /etc/caddy/Caddyfile && caddy reload --config /etc/caddy/Caddyfile'
|
||||
PUBLIC_STATUS_URL="${PUBLIC_STATUS_URL:-https://status.sami}"
|
||||
# Page-specific markers so a misrouted Terms page doesn't pass for Privacy.
|
||||
# We use a temp file instead of `curl | grep -q` because grep -q exits early and
|
||||
# can trigger SIGPIPE under pipefail, producing false-positive verification
|
||||
# failures on otherwise-successful deploys (set -o pipefail amplifies this).
|
||||
declare -A PAGE_MARKERS=(
|
||||
[terms]="Terms of Service"
|
||||
[tos]="Terms of Service" # alias page content
|
||||
[privacy]="Privacy Policy"
|
||||
)
|
||||
TMP_CURL_BODY="$(mktemp)"
|
||||
trap 'rm -f "$TMP_CURL_BODY"' EXIT
|
||||
for path in "${PAGES[@]}"; do
|
||||
marker="${PAGE_MARKERS[$path]}"
|
||||
if ! curl --fail --silent --show-error --location "${PUBLIC_STATUS_URL}/legal/${path}" -o "$TMP_CURL_BODY"; then
|
||||
echo "Post-deploy verification failed: ${PUBLIC_STATUS_URL}/legal/${path} (HTTP error)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -qF "${marker}" "$TMP_CURL_BODY"; then
|
||||
echo "Post-deploy verification failed: ${PUBLIC_STATUS_URL}/legal/${path} (expected '${marker}')" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
printf 'Legal pages deployed to status.sami/legal.\n'
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
TERMS="$ROOT/status/legal/terms/index.html"
|
||||
PRIVACY="$ROOT/status/legal/privacy/index.html"
|
||||
TOS_ALIAS="$ROOT/status/legal/tos/index.html"
|
||||
require() { grep -Eqi "$2" "$1" || { echo "Missing required content in $1: $2" >&2; exit 1; }; }
|
||||
test -s "$TERMS" && test -s "$PRIVACY" && test -s "$TOS_ALIAS"
|
||||
for section in 'License grant' 'Acceptable use' 'best-effort' 'Refund policy' 'Termination' 'Limitation of liability' 'Governing law'; do require "$TERMS" "$section"; done
|
||||
require "$TERMS" 'within 14 calendar days'
|
||||
for section in 'GDPR' 'lawful bases' 'Stripe' 'Tailscale' 'data portability|portability' '30 days after cancellation' 'privacy@sami-ahmed.net'; do require "$PRIVACY" "$section"; done
|
||||
# Reject any SOC 2 / HIPAA compliance claims (the launch explicitly excludes them).
|
||||
# Negated `! grep` does not trigger errexit under `set -e` (ShellCheck SC2251), so use an
|
||||
# explicit if/then to make the forbidden-claim guard actually fail the script.
|
||||
# Regex covers: SOC 2 / SOC-2 / SOC2 + (certified|compliant|compliance|compliant),
|
||||
# HIPAA + (certified|compliant|compliance|compliant), with optional hyphen.
|
||||
if grep -Eqi 'SOC[ -]?2[[:space:]-]+(certified|compliant|compliance)|HIPAA[[:space:]-]+(certified|compliant|compliance)' "$TERMS" "$PRIVACY"; then
|
||||
echo "Forbidden SOC 2/HIPAA compliance language detected in Terms or Privacy pages." >&2
|
||||
exit 1
|
||||
fi
|
||||
require "$ROOT/status/index.html" 'href="/legal/terms"'
|
||||
require "$ROOT/status/index.html" 'href="/legal/privacy"'
|
||||
require "$TOS_ALIAS" 'url=/legal/terms'
|
||||
echo 'Legal page sanity checks passed.'
|
||||
Reference in New Issue
Block a user