Files
SocialPub/PrivaPub/Web/Pages/Stargazing.cshtml
T
thepraandClaude Opus 5.5 5f56681c01
Build / Build (push) Successful in 5m1s
Deploy / privapub.thepra.dev (push) Successful in 5m39s
Everything on, phase 1: geolocation fetches itself, the deploy signs in as @thepra, the crawler is on, sign-up by invitation
Owner decisions (2026-10-04, recorded in docs/ROADMAP.md): production runs everything that is built, and nothing waits
on a person running a command.

- Geolocation updates itself. GeoUpdater, a hosted service, checks daily whether each DB-IP Lite database was built this
  month. If not, it fetches this month's, or last month's early in the month. It installs a file only once it opens as
  the right kind of database, then swaps it in atomically, and the locator reloads at once. Lookups now run under the
  lock, so a reload can no longer dispose a reader mid-lookup. The systemd timer, its script and their setup.sh lines
  are gone: the root step they needed never happened, and none is needed now. /stargazing names the database in use.
- The admin CLI runs after the app is built, with every service and nothing started.
  - `create-root <login> [--admin]` takes the password on stdin; it is how the first login is made while sign-up is
    closed.
  - `smoke <persona>` keeps the root `deploy-smoke` and an undiscoverable persona, and gives the root a new password
    on every run.
- The deploy signs in as @thepra. It runs the CLI, gets a token through the real OAuth flow (tools/smoke/oauth.sh,
  moved out of the pasture's privapub_token, which now uses it), checks the signed-in API and that @thepra is
  undiscoverable, then revokes the token. PRIVAPUB_SMOKE_TOKEN is gone.
- The deploy also fails when:
  - NodeInfo and the instance API disagree about registrations;
  - /stargazing does not say the crawler is on;
  - the geolocation databases are missing or more than 40 days old.
- The crawler is on in production, seeded with ten large servers of different kinds. FEDERATION.md now describes it
  and how to opt out.
- One registrations switch (Registrations:Mode, default Invitations; Open in tests and the pasture). It is read by
  open sign-up (403 when closed), NodeInfo `openRegistrations`, and v1 and v2 of the instance API, so they can no longer
  disagree. Before, NodeInfo said open and the instance API said closed. Group invitations always work, so
  invites_enabled is true.
- A persona edit through /clientapi no longer resets what the Mastodon API set (discoverable, locked, quote policy…):
  the theme is merged into the settings instead of replacing them.

650 tests pass. The deploy's smoke step was rehearsed against the pasture's PrivaPub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
2026-10-04 02:37:38 +02:00

58 lines
2.5 KiB
Plaintext

@page "/stargazing"
@model PrivaPub.Web.Pages.StargazingModel
@{
ViewData["Title"] = "Stargazing";
}
<header>
<h1>Stargazing</h1>
<div class="meta">What this server records about other servers, and how to keep its crawler out.</div>
</header>
<article>
<h2>Servers, never people</h2>
<p>This server keeps statistics about the fediverse for teaching and curiosity. They name <strong>servers</strong>,
never accounts: what kind of software a server runs, what it exchanges with us, and how reliably. Distinct accounts are
only counted, through a key that is destroyed at the end of each day.</p>
<p>A server we exchange activities with is described once a week, from its public NodeInfo and, when it has one, its
Mastodon instance API. Its location comes from the address we reached, looked up in an offline database: only the
country is shown publicly for small servers, and only the CDN for servers behind one.</p>
@if (Model.GeoSource is { } geo)
{
<p>Locations come from <a href="https://db-ip.com" rel="nofollow noopener">@geo</a>, licensed under
<a href="https://creativecommons.org/licenses/by/4.0/" rel="nofollow noopener">CC BY 4.0</a>.</p>
}
else
{
<p>No location database is loaded yet, so servers are not located.</p>
}
</article>
<article>
<h2>The crawler</h2>
@if (Model.CrawlerEnabled)
{
<p>The crawler is <strong>on</strong> on this server.</p>
}
else
{
<p>The crawler is <strong>off</strong> on this server: it only learns about servers it already exchanges with.</p>
}
<p>When on, it identifies itself as</p>
<p><code>@Model.UserAgent</code></p>
<p>It visits one server a minute, each at most once a week, and reads only:</p>
<ul>
<li><code>/robots.txt</code></li>
<li><code>/.well-known/nodeinfo</code> and the NodeInfo document it points to</li>
<li><code>/api/v2/instance</code> or <code>/api/v1/instance</code></li>
<li><code>/api/v1/instance/peers</code>, to find other servers</li>
</ul>
<p>It never reads accounts, posts, timelines or directories.</p>
<h2>Keeping it out</h2>
<p>Add this to your server's <code>robots.txt</code>:</p>
<pre>User-agent: @StargazerToken
Disallow: /</pre>
<p>If your <code>robots.txt</code> cannot be read because of a server error or a timeout, the crawler stays out too.</p>
</article>
<footer class="meta">IP geolocation by <a href="https://db-ip.com" rel="nofollow noopener noreferrer">DB-IP</a>, CC BY 4.0.</footer>
@functions {
const string StargazerToken = PrivaPub.Federation.Crawler.Stargazer.Token;
}