Skip to content

Repository files navigation

PerrinPort

One name. One port. Always the same.

Stop guessing which localhost port your service is on. PerrinPort derives a stable, deterministic port (10000–19999) from any project name or URL using SHA-256 — the port is computed, not remembered, not coordinated, not configured.


The problem

You've played this game:

$ curl localhost:3000 # ECONNREFUSED — that's Tuesday's project
$ curl localhost:8080 # nope, that's the old Jenkins
$ curl localhost:4200 # maybe?
$ grep -r "port" .env # finally

Or the team version: "What port is the auth service on?" — a question that should never need to be asked.

PerrinPort ends it. If you know the name, you know the port.


How it works

canonical = lowercase(scheme + "://" + label)
port = 10000 + (SHA256(canonical)[0:8] as uint32 mod 10000)

The canonical form is derived from your input — a folder name, a URL, a bare project name — via a simple set of rules that make a project's folder path and its dev URL land on the same port automatically.

Canonicalization

InputCanonicalPort
myprojecthttp://myproject19894
http://localhost/MyProject/http://myproject19894
C:\dev\MyProjecthttp://myproject19894
/home/me/projects/MyProject/http://myproject19894
grafanahttp://grafana13029
https://myproject.test/https://myproject.test15656
https://shop.example.com/admin/products?x=1https://admin18043

Rules:

  • Input containing :// is treated as a URL: scheme = URL scheme, label = first path segment, or the host if there is no path.
  • Everything else is a name or path: scheme = http, label = the final path component (basename).
  • Everything is lowercased before hashing.
  • Query strings and fragments are ignored.

Quick start

Pick your platform:

# Bash (Linux / macOS / WSL)
chmod +x implementations/bash/perrinport
./implementations/bash/perrinport myproject # 19894
# Python 3 — no dependenciespythonimplementations/python/perrinport.pymyproject# 19894
// Node.jsnodeimplementations/node/perrinport.jsmyproject// 19894
# PowerShell (Windows)
.\implementations\powershell\perrinport.ps1 myproject # 19894
REM Windows batch + PowerShell (original reference)
docs\perrinport.bat myproject :: 19894

Implementations

LanguageFileNotes
Windows batchdocs/perrinport.batOriginal reference — PowerShell inline
Bashimplementations/bash/perrinportLinux, macOS, WSL
Python 3implementations/python/perrinport.pystdlib only; importable as a module
Node.jsimplementations/node/perrinport.jsstdlib only; importable as a module
PowerShellimplementations/powershell/perrinport.ps1standalone .ps1
Goimplementations/go/importable package + CLI in cmd/perrinport/

All implementations pass the same 10-vector conformance suite (docs/perrinport-conformance.json).

# Run all conformance tests
python implementations/python/test_conformance.py
node implementations/node/test_conformance.js
go test ./implementations/go/
bash implementations/bash/test_conformance.sh
pwsh implementations/powershell/test_conformance.ps1

Browser extension

The extension shows the PerrinPort for the URL in your active tab — right in the toolbar, without switching to a terminal.

What it does:

  • Toolbar badge shows the port number as you browse.
  • Click the icon for the full breakdown: canonical form, port, and a one-click Open localhost:PORT link.

Install (developer mode)

Chrome / Edge:

  1. Run python generate_icons.py inside extensions/ (once).
  2. Open chrome://extensions, enable Developer mode.
  3. Click Load unpacked and select the extensions/ folder.

Firefox:

  1. Run python generate_icons.py inside extensions/ (once).
  2. Open about:debugging#/runtime/this-firefox.
  3. Click Load Temporary Add-on and select extensions/manifest.json.

Both browsers use the same manifest.json (Chrome ignores the Firefox-specific browser_specific_settings field).


Dashboard

A port 80 landing page that enumerates all your local PerrinPort services, probes each one, and presents a live clickable registry — your personal service directory, generated from names alone.

extras/dashboard/
perrinport_dashboard.py # the server (stdlib only, no deps)
projects.txt # your services — edit without restarting
00-startup.bat # Windows launcher

Start it:

# Windows (run as Administrator for port 80)
extras\dashboard\00-startup.bat
# Any OS, any port
python extras/dashboard/perrinport_dashboard.py --port=8099
# Or via $PORT env var
PORT=8099 python extras/dashboard/perrinport_dashboard.py

Edit projects.txt to add your services — no restart needed, the file is re-read on every poll cycle:

myproject
api-gateway
frontend
grafana
https://shop.example.com/admin/

The dashboard probes all ports in parallel (250 ms timeout), shows UP/DOWN status, and updates every 5 seconds. Click any row to open localhost:PORT.


Conformance

docs/perrinport-conformance.json is the canonical test-vector file. Any implementation must pass all vectors to be considered conformant.

# Python
python implementations/python/test_conformance.py
# Node.js
node implementations/node/test_conformance.js

Open question

Root URLs with no path (e.g. https://site.test/) currently fall back to the host and receive a derived port like any other input. Whether roots should map to 80/443 instead is intentionally unsettled — see the unsettled fields in the conformance JSON. Until resolved, conformant implementations must keep the host-fallback behaviour.


Why 10000–19999?

  • Ports below 1024 require root on Unix.
  • Ports 1024–9999 are crowded with well-known services.
  • The 10,000-wide window (10000–19999) is largely unassigned and gives a roughly 0.5% collision probability for 10 projects — acceptable for individual and small-team use. Expected first collision at ~125 projects.

Contributing

New implementation? Add it to implementations/<language>/, write a conformance test, and make sure all 10 vectors pass before opening a PR.

Changing the spec? The algorithm and canonicalization rules are in docs/perrinport-conformance.json. Any spec change requires updating every conformance vector that is affected.


Licence

Copyright (C) 2026 pp

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the GNU AGPL v3 for details.

The AGPL was chosen deliberately: if you embed PerrinPort in something you distribute, or run it as a network service, your software must also be open. The port is a shared standard — keep the ecosystem open.


Spec

perrinport spec version 0.1.0 — algorithm variant pp8 (the default; pp implies pp8)
Algorithm: port = 10000 + (first8hex(SHA256(canonical)) as uint32 mod 10000)
Canonical: lowercase(scheme + "://" + label) per the rules above.

About

Stable, deterministic localhost ports from project names — no config, no conflicts

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages