feat(kb-site): noindex + robots.txt + obscure subdomain jako domyslny base-url
Warstwa "nie daj sie przypadkiem znalezc" dla publicznej wystawki KB: - gen_pages.py: <meta name="robots" content="noindex, nofollow"> w <head> kazdej generowanej strony (page_shell, wiec takze index). - gen_pages.py: DEFAULT_BASE_URL -> https://kb-e2a24af3.okit.pl. Slug musi zgadzac sie z rekordem DNS i vhostem w npm@PIHA (runbook kb-site-deploy). - services/kb-site: static/robots.txt (Disallow: /) montowany ro na /usr/share/nginx/html/robots.txt. Plik nie jest dokumentem KB, wiec jedzie z repo, a nie z wolumenu podmienianego przy kazdej publikacji. - kb/services/kb-site.md: sekcja "Access" — token w query paramie na warstwie nginx/NPM (sekret zyje tylko w NPM, nie w repo) + obscure subdomain + noindex. Explicit: to obscurity, nie kontrola dostepu — token w URL laduje w access logach, historii przegladarki i naglowku Referer. Bramka publikacji bez zmian: gen_pages.py --check exit 0 (22 wyciszone whitelista, jak dotad). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7282a5e1d5
commit
db81cb15d3
|
|
@ -3,14 +3,15 @@ okf: "0.1"
|
||||||
type: service
|
type: service
|
||||||
visibility: public
|
visibility: public
|
||||||
status: active
|
status: active
|
||||||
updated: 2026-08-04
|
updated: 2026-08-05
|
||||||
links:
|
links:
|
||||||
- ../runbooks/kb-site-deploy.md
|
- ../runbooks/kb-site-deploy.md
|
||||||
---
|
---
|
||||||
|
|
||||||
# kb-site
|
# kb-site
|
||||||
|
|
||||||
Public slice of this knowledge base, served as static HTML at `kb.okit.pl`.
|
Public slice of this knowledge base, served as static HTML from a deliberately
|
||||||
|
non-obvious subdomain of `okit.pl`.
|
||||||
Plain `nginx:alpine` on the PIHA node reading one Docker named volume — no
|
Plain `nginx:alpine` on the PIHA node reading one Docker named volume — no
|
||||||
build step at runtime, no database, no dependencies.
|
build step at runtime, no database, no dependencies.
|
||||||
|
|
||||||
|
|
@ -58,6 +59,18 @@ out empty, so every exception is a recorded decision.
|
||||||
The check is a release gate: content is copied to the host only after it
|
The check is a release gate: content is copied to the host only after it
|
||||||
passes.
|
passes.
|
||||||
|
|
||||||
|
## Access
|
||||||
|
|
||||||
|
Three layers keep the site out of casual sight: a query-parameter token enforced
|
||||||
|
in the nginx/NPM layer (advanced config held in NPM only — the secret is not in
|
||||||
|
this repository), an unguessable subdomain, and `noindex, nofollow` on every page
|
||||||
|
alongside a blanket `robots.txt`.
|
||||||
|
|
||||||
|
This is obscurity, not access control. A URL token is written to access logs,
|
||||||
|
browser history and outbound `Referer` headers, so anyone who obtains a link
|
||||||
|
keeps it; nothing here resists a deliberate attacker. The leak gate above, not
|
||||||
|
this, is what keeps private material off the site.
|
||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|
||||||
Deployment, content refresh, reverse-proxy and DNS setup are described in the
|
Deployment, content refresh, reverse-proxy and DNS setup are described in the
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Generator publicznej warstwy bazy wiedzy (kb.okit.pl) z dokumentów OKF v0.1.
|
"""Generator publicznej warstwy bazy wiedzy z dokumentów OKF v0.1.
|
||||||
|
|
||||||
Wzorzec: ~/narty-2027/saalbach-kb/gen_pages.py — renderer markdown na samej
|
Wzorzec: ~/narty-2027/saalbach-kb/gen_pages.py — renderer markdown na samej
|
||||||
bibliotece standardowej plus prosty, czytelny szablon HTML. Tutaj dochodzi
|
bibliotece standardowej plus prosty, czytelny szablon HTML. Tutaj dochodzi
|
||||||
|
|
@ -60,7 +60,7 @@ from check_okf import parse_yaml, split_frontmatter # noqa: E402
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
KB_DIR = REPO_ROOT / "kb"
|
KB_DIR = REPO_ROOT / "kb"
|
||||||
DEFAULT_OUT = REPO_ROOT / "build" / "kb-site"
|
DEFAULT_OUT = REPO_ROOT / "build" / "kb-site"
|
||||||
DEFAULT_BASE_URL = "https://kb.okit.pl"
|
DEFAULT_BASE_URL = "https://kb-e2a24af3.okit.pl"
|
||||||
DEFAULT_WHITELIST = Path(__file__).resolve().parent / "check_whitelist.txt"
|
DEFAULT_WHITELIST = Path(__file__).resolve().parent / "check_whitelist.txt"
|
||||||
|
|
||||||
SITE_NAME = "homelab-codex — knowledge base"
|
SITE_NAME = "homelab-codex — knowledge base"
|
||||||
|
|
@ -464,6 +464,7 @@ def page_shell(
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<title>{html.escape(title)} — {html.escape(SITE_NAME)}</title>
|
<title>{html.escape(title)} — {html.escape(SITE_NAME)}</title>
|
||||||
<link rel="canonical" href="{html.escape(canonical, quote=True)}">
|
<link rel="canonical" href="{html.escape(canonical, quote=True)}">
|
||||||
<style>{CSS}</style>
|
<style>{CSS}</style>
|
||||||
|
|
@ -816,7 +817,7 @@ def check(out_dir: Path, whitelist_path: Path) -> int:
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Generator publicznej warstwy KB (kb.okit.pl) z dokumentów OKF."
|
description="Generator publicznej warstwy KB z dokumentów OKF."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--base-url",
|
"--base-url",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ services:
|
||||||
# through the helper-container procedure in kb/runbooks/kb-site-deploy.md
|
# through the helper-container procedure in kb/runbooks/kb-site-deploy.md
|
||||||
# (docker cp cannot write into a :ro mount).
|
# (docker cp cannot write into a :ro mount).
|
||||||
- kb-site_content:/usr/share/nginx/html:ro
|
- kb-site_content:/usr/share/nginx/html:ro
|
||||||
|
# robots.txt nie pochodzi z generatora — nie jest dokumentem KB, więc
|
||||||
|
# jedzie z repo i nie ginie przy podmianie zawartości wolumenu. Mount
|
||||||
|
# pliku jest głębszy niż mount katalogu, więc Docker montuje go później
|
||||||
|
# i plik przykrywa (nieistniejące) robots.txt z wolumenu.
|
||||||
|
- ./static/robots.txt:/usr/share/nginx/html/robots.txt:ro
|
||||||
# Docker on PIHA has exhausted its default address pools (~30 live stacks:
|
# Docker on PIHA has exhausted its default address pools (~30 live stacks:
|
||||||
# "all predefined address pools have been fully subnetted"), so this stack
|
# "all predefined address pools have been fully subnetted"), so this stack
|
||||||
# must not ask for a subnet of its own. Declaring a network here suppresses
|
# must not ask for a subnet of its own. Declaring a network here suppresses
|
||||||
|
|
|
||||||
2
services/kb-site/static/robots.txt
Normal file
2
services/kb-site/static/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
Loading…
Reference in a new issue