commit 6758bda3cb5a22fff6c8ce1596b357293a3e2691 Author: Olaf Date: Thu Oct 8 15:09:55 2026 +0200 NOC site for AS203496 served at noc.obr.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..364fdec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +public/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..314f31f --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# noc-site + +Static NOC site for AS203496, served at https://noc.obr.sh. `python3 build.py` writes `public/`. + +- Brand name, domain, ASN, upstreams, contacts and PGP keys: `site.json` (rebrand = change `brand` and the contact fields there). +- Prose: `content/*.txt`. +- Deploy: copy `public/` to CT114 web-static `/opt/static-sites/sites/noc.obr.sh/current` (see `deploy.sh`). +- noc.obnh.network returns 301 to https://noc.obr.sh (path kept), set up in the CT110 edge traefik and the CT114 nginx. diff --git a/build.py b/build.py new file mode 100755 index 0000000..68bbcd1 --- /dev/null +++ b/build.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Build the NOC site into ./public. Brand, domain and facts come from site.json; prose from content/*.txt.""" +import json, html, shutil, pathlib +root = pathlib.Path(__file__).parent +S = json.load(open(root / "site.json")) +out = root / "public" +shutil.rmtree(out, ignore_errors=True) +shutil.copytree(root / "static", out) +e = html.escape +CSS = """:root{--bg:#fff;--fg:#1c1f24;--mut:#5b6470;--ac:#0b5cad;--bd:#d9dee4} +@media (prefers-color-scheme:dark){:root{--bg:#14171b;--fg:#e6e9ed;--mut:#9aa4b0;--ac:#6cb2ff;--bd:#2d333b}} +body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.6 system-ui,sans-serif} +.w{max-width:46rem;margin:0 auto;padding:0 16px}header{border-bottom:1px solid var(--bd);padding:1rem 0} +header a.b{font-weight:700;color:var(--fg);text-decoration:none}nav{margin-top:.4rem}nav a{margin-right:1rem} +a{color:var(--ac)}h1{margin-top:2rem}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid var(--bd);padding:.4rem .5rem;text-align:left;vertical-align:top} +th{color:var(--mut);font-weight:600;width:35%}code{word-break:break-all}footer{color:var(--mut);border-top:1px solid var(--bd);margin:3rem 0 2rem;padding-top:1rem;font-size:.9rem}""" +NAV = [("/", "Home"), ("/peering/", "Peering"), ("/pgp/", "PGP keys"), ("/contact/", "Contact")] +def paras(name): + return "".join(f"

{e(l)}

" for l in (root / "content" / f"{name}.txt").read_text().splitlines() if l.strip()) +def page(path, title, body): + nav = "".join(f'{t}' for u, t in NAV) + doc = f""" +{e(title)} - {e(S['brand'])} + +
+
{body}
""" + d = out / path.strip("/"); d.mkdir(parents=True, exist_ok=True) + (d / "index.html").write_text(doc) +ups = "".join(f"
  • {e(u['asn'])} {e(u['name'])} ({e(u['site'])})
  • " for u in S["upstreams"]) +facts = f""" + + + + + + +
    ASN{e(S['asn'])}
    Prefix{e(S['prefix'])} (IPv6 only)
    Upstreams
      {ups}
    AS-SET{e(S['as_set'])} (RIPE)
    RPKIROA {e(S['prefix'])}, origin {e(S['asn'])}, maxLength 48; ASPA providers AS41051, AS6517
    PeeringDB{S['peeringdb']}
    PolicyOpen, IPv6 preferred
    """ +page("/", S["tagline"], f"

    {e(S['brand'])}

    {e(S['tagline'])}

    {paras('home')}

    Network facts

    {facts}") +page("/peering/", "Peering", f"

    Peering

    {paras('peering')}{facts}") +keys = "".join(f"{e(a)}{e(f)}" for a, f in S["pgp"]) +page("/pgp/", "PGP keys", f"

    PGP keys

    {paras('pgp')}{keys}
    ") +c = S["contacts"] +cbody = f"""

    Contact

    {paras('contact')} + + + +
    NOC for {e(S['asn'])}{c['noc']}
    Abuse{c['abuse']}
    Emergency{e(c['phone'])}
    Threema{e(c['threema'])}
    Threema QR code
    """ +page("/contact/", "Contact", cbody) +(out / "robots.txt").write_text(f"User-agent: *\nAllow: /\nSitemap: https://{S['domain']}/sitemap.xml\n") +urls = "".join(f"https://{S['domain']}{u}" for u, _ in NAV) +(out / "sitemap.xml").write_text(f'{urls}') +print("built", S["domain"]) diff --git a/content/contact.txt b/content/contact.txt new file mode 100644 index 0000000..7347245 --- /dev/null +++ b/content/contact.txt @@ -0,0 +1 @@ +Send operational questions to the NOC address. Report abuse to the abuse address. For emergencies, call the number below. Threema is an alternative channel. diff --git a/content/home.txt b/content/home.txt new file mode 100644 index 0000000..ffee1a4 --- /dev/null +++ b/content/home.txt @@ -0,0 +1,2 @@ +AS203496 is a small, IPv6-only network for research and training. It exists to learn, test and operate real internet infrastructure the way a production network would. +The network announces one prefix, 2001:67c:1948::/48, from two sites. Each site has its own transit provider, so the network stays reachable if one upstream fails. diff --git a/content/peering.txt b/content/peering.txt new file mode 100644 index 0000000..707b2fc --- /dev/null +++ b/content/peering.txt @@ -0,0 +1,4 @@ +Peering is welcome. AS203496 peers directly where your network shares a facility (a meet-me room) or an internet exchange. +The network prefers IPv6. Direct sessions are IPv6 only. IPv4 sessions are set up only with IXP route servers, and there are no direct IPv4-only sessions. For direct peerings and interconnects, an MD5 password is appreciated. +Customers of AS203496 are not treated as peers at any location. Transit can be provided where that is feasible. +For filtering, use the AS-SET AS203496:AS-OBNH-NETWORK (RIPE). The route6 object and the ROA both cover 2001:67c:1948::/48 with origin AS203496, and an ASPA record lists AS41051 and AS6517 as providers. diff --git a/content/pgp.txt b/content/pgp.txt new file mode 100644 index 0000000..168d789 --- /dev/null +++ b/content/pgp.txt @@ -0,0 +1 @@ +Public keys for signed and encrypted mail are listed below. diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..91c701e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Build and push public/ to web-static CT114 on nibbler (nibbler tailnet 100.82.124.16). +set -e +cd "$(dirname "$0")" +python3 build.py +tar czf - -C public . | ssh root@100.82.124.16 'pct exec 114 -- sh -c "rm -rf /opt/static-sites/sites/noc.obr.sh/current.new && mkdir -p /opt/static-sites/sites/noc.obr.sh/current.new && tar xzf - -C /opt/static-sites/sites/noc.obr.sh/current.new && rm -rf /opt/static-sites/sites/noc.obr.sh/current.old; [ -d /opt/static-sites/sites/noc.obr.sh/current ] && mv /opt/static-sites/sites/noc.obr.sh/current /opt/static-sites/sites/noc.obr.sh/current.old; mv /opt/static-sites/sites/noc.obr.sh/current.new /opt/static-sites/sites/noc.obr.sh/current"' diff --git a/site.json b/site.json new file mode 100644 index 0000000..0af663f --- /dev/null +++ b/site.json @@ -0,0 +1,27 @@ +{ + "brand": "OBNH Network Baumert", + "domain": "noc.obr.sh", + "old_domain": "noc.obnh.network", + "tagline": "IPv6-only training and research network", + "asn": "AS203496", + "prefix": "2001:67c:1948::/48", + "as_set": "AS203496:AS-OBNH-NETWORK", + "upstreams": [ + {"asn": "AS41051", "name": "FreeTransit (Openfactory)", "site": "leela"}, + {"asn": "AS6517", "name": "Lake Networks", "site": "kiff"} + ], + "peeringdb": "https://www.peeringdb.com/net/30644", + "contacts": { + "noc": "noc@obnh.network", + "abuse": "abuse@obnh.network", + "phone": "+41 76 263 0152", + "threema": "XKSBXYFZ" + }, + "pgp": [ + ["admin@obnh.network", "05dc62d7ab054e72c6aea639ba0ee08972efd755"], + ["admin@obnh.org", "b6475ed1ff426b3c16b7366c3450c39c710127f1"], + ["noc@obnh.network", "f50d798f7b7e1a84fe5d0b8f828c4b32f92444c6"], + ["olaf@baumert.cc", "cf8e073a7bba64da51308540638a2a01c171b4f9"], + ["olaf@obnh.network", "8ac0a93854d0a59ec28c8b4dd0a70dba97115a8a"] + ] +} diff --git a/static/threema-XKSBXYFZ.jpg b/static/threema-XKSBXYFZ.jpg new file mode 100644 index 0000000..27c3b09 Binary files /dev/null and b/static/threema-XKSBXYFZ.jpg differ