Overhaul server metadata (country system, tiers, features) #52

Open
opened 2026-08-19 11:10:30 -04:00 by mysticalsoap · 5 comments
Owner

currently we have just a folder of flags (some now outdated) and a countries.json. Is there a more mature package to pull in here for this kind of thing? That wouldn't be too overkill compared to the current system?

currently we have just a folder of flags (some now outdated) and a countries.json. Is there a more mature package to pull in here for this kind of thing? That wouldn't be too overkill compared to the current system?
Author
Owner

also not a big fan of the country flag becoming the sys tray icon. Should be an option that defaults to off.

also not a big fan of the country flag becoming the sys tray icon. Should be an option that defaults to off.
Author
Owner

country flag as sys tray icon behavior removed for now, can be potentially added back later as mentioned, as an option

country flag as sys tray icon behavior removed for now, can be potentially added back later as mentioned, as an option
Author
Owner

Research results — the answer is yes for names, mostly for flags, and the geoip backend has a clean modern replacement. The biggest win is changing what things are keyed by.

Country codes → names: solved problem, stop maintaining it. countries.json is a hand-frozen copy of what the Debian iso-codes project maintains as the authoritative ISO 3166 dataset. Two consumption options:

  • Depend on the distro iso-codes package (every distro ships it as a near-base dep) and read /usr/share/iso-codes/json/iso_3166-1.json — zero new Python deps, country_translate() barely changes shape.
  • pycountry (Arch: python-pycountry) — pure wrapper around the same data with a nicer API, fuzzy lookup, gettext-localized names.

Either deletes countries.json. Plain iso-codes is the least-code option; pycountry earns its place only if fuzzy matching or localized display names are wanted.

Flags: no distro package exists — flag art lives in web land — but we can stop maintaining and start syncing. flag-icons is the de-facto standard: MIT, complete ISO 3166-1 coverage, keyed by alpha-2, SVG in 4:3 and 1:1. Vendor it with a small sync script so updates are a re-run, not art maintenance. Qt renders SVG natively, which also kills raster scaling fuzziness. circle-flags is the same idea cropped round if that fits the UI better. Fixes all three flag problems at once: outdated art, the 81-vs-~250 coverage gap, and Unknown.png fallbacks for countries that exist but lack a PNG.

The actual hack is the keying, not the assets. Everything is keyed by English display name: flags/Germany.png, server dicts store "country": "Germany", and any mismatch degrades to "Unknown". Both packages above key by ISO alpha-2. The structural fix is making alpha-2 the canonical key — server dicts store the code, display name and flag derived at render time — with a one-time reverse-mapping migration for existing server.json entries. Without that, a package swap just relocates the fragility.

GeoIP: the current path is dead, not just hacky. custom.py shells out to geoiplookup (brittle split(" ")[3] parsing) against the legacy database MaxMind abandoned in 2019 — distro geoip-database packages ship increasingly wrong data. Modern shape: the maxminddb library (Arch: python-maxminddb) reading an .mmdb file. MaxMind's GeoLite2 requires an account since 2019, but DB-IP Country Lite is CC-BY 4.0, monthly, direct no-account download (attribution = one README line). Blast radius is small: only custom imports use geoip — supported providers carry country in their own data. A "path to an .mmdb" setting would let anyone substitute their own GeoLite2 copy. Open design question: update mechanics — documented manual download vs fetch-on-first-import with a cached copy.

Against the "not overkill" bar: iso-codes is less code than today, the flag swap is the same repo footprint differently sourced, maxminddb replaces a subprocess with a library call. The real work is the alpha-2 rekeying migration — which is also what makes the rest trivial.

Research results — the answer is yes for names, mostly for flags, and the geoip backend has a clean modern replacement. The biggest win is changing what things are keyed by. **Country codes → names: solved problem, stop maintaining it.** `countries.json` is a hand-frozen copy of what the Debian iso-codes project maintains as the authoritative ISO 3166 dataset. Two consumption options: - Depend on the distro `iso-codes` package (every distro ships it as a near-base dep) and read `/usr/share/iso-codes/json/iso_3166-1.json` — zero new Python deps, `country_translate()` barely changes shape. - [pycountry](https://pypi.org/project/pycountry/) (Arch: `python-pycountry`) — pure wrapper around the same data with a nicer API, fuzzy lookup, gettext-localized names. Either deletes `countries.json`. Plain iso-codes is the least-code option; pycountry earns its place only if fuzzy matching or localized display names are wanted. **Flags: no distro package exists — flag art lives in web land — but we can stop *maintaining* and start *syncing*.** [flag-icons](https://github.com/lipis/flag-icons) is the de-facto standard: MIT, complete ISO 3166-1 coverage, keyed by alpha-2, SVG in 4:3 and 1:1. Vendor it with a small sync script so updates are a re-run, not art maintenance. Qt renders SVG natively, which also kills raster scaling fuzziness. [circle-flags](https://github.com/HatScripts/circle-flags) is the same idea cropped round if that fits the UI better. Fixes all three flag problems at once: outdated art, the 81-vs-~250 coverage gap, and `Unknown.png` fallbacks for countries that exist but lack a PNG. **The actual hack is the keying, not the assets.** Everything is keyed by English display name: `flags/Germany.png`, server dicts store `"country": "Germany"`, and any mismatch degrades to "Unknown". Both packages above key by ISO alpha-2. The structural fix is making alpha-2 the canonical key — server dicts store the code, display name and flag derived at render time — with a one-time reverse-mapping migration for existing `server.json` entries. Without that, a package swap just relocates the fragility. **GeoIP: the current path is dead, not just hacky.** `custom.py` shells out to `geoiplookup` (brittle `split(" ")[3]` parsing) against the legacy database MaxMind abandoned in 2019 — distro `geoip-database` packages ship increasingly wrong data. Modern shape: the `maxminddb` library (Arch: `python-maxminddb`) reading an `.mmdb` file. MaxMind's GeoLite2 requires an account since 2019, but [DB-IP Country Lite](https://db-ip.com/db/download/ip-to-country-lite) is CC-BY 4.0, monthly, direct no-account download (attribution = one README line). Blast radius is small: only custom imports use geoip — supported providers carry country in their own data. A "path to an .mmdb" setting would let anyone substitute their own GeoLite2 copy. Open design question: update mechanics — documented manual download vs fetch-on-first-import with a cached copy. Against the "not overkill" bar: iso-codes is *less* code than today, the flag swap is the same repo footprint differently sourced, maxminddb replaces a subprocess with a library call. The real work is the alpha-2 rekeying migration — which is also what makes the rest trivial.
Author
Owner

Widening this issue into a general server-metadata effort — the country system above and the server schema below are the same disease and one migration.

What providers hand us that the importers currently throw away:

  • ProtonVPN (/vpn/logicals, richest source): Tier and Features are parsed, then flattened into the name string (us-ny-01-ProtonVPN-Plus-P2P) and the structured values dropped. Also available and unkept: Region (state-ish, nullable), Load (live utilization %), Score (Proton's own ranking). The /vpn endpoint we already call for OpenVPN credentials reports the account's MaxTier — exactly what a tier grey-out needs. Latent bug while there: the features handling is an elif chain testing equality against 1/2/4, but Features is a bitmask — a server with multiple bits set matches nothing.
  • Windscribe: the serverlist carries a pro flag per datacenter group — discarded. Streaming locations (WINDFLIX) are detected but again encoded into the name string. No state field; group labels are "US Central"-style, not states.
  • Mullvad: importer uses the old WG-only public relay endpoint. The fuller relay API additionally exposes owned (Mullvad-owned vs rented), hosting provider, port speed, active. No tiers, city is the only sub-country level.
  • AirVPN: manifest carries per-server bandwidth/user/load stats, all ignored. No tiers; location is a city.

Schema shape this suggests (optional fields, null where a provider has no equivalent):

  • country as ISO alpha-2 (the rekeying above), region populated where the provider supplies it (only Proton, sometimes), sort country → region → city. Don't synthesize states from city names — that re-creates the countries.json problem with worse data.
  • tier on the server plus a per-provider account capability stored at import (MaxTier / premium status), so the UI can grey out or exclude what the account can't use — both directions (hide free when paid, hide paid when free).
  • features as a structured list (p2p, tor, secure-core, streaming, Mullvad's owned) replacing name-string suffixes.
  • latency stays locally measured (it's a property of the path, no API has it) but persisted on the server record with a timestamp, so it sorts/filters like everything else with staleness visible.
  • Server load (Proton, AirVPN) only if paired with a lightweight refresh — it's live data, stale within hours under the 5-day update cadence. Proton Score and Mullvad port speed are cheap informational keeps.

Through-line: the current system encodes structured data into display strings — tier and features into server names, country as display name. The fix is the same move everywhere: structured fields as canon, strings derived at render time. One schema migration covers both halves of this issue.

Caution: the Windscribe pro flag and Proton MaxTier/Region specifics are partly from documentation, not observed traffic — re-verify against live responses during #32's provider testing before building on them.

Widening this issue into a general server-metadata effort — the country system above and the server schema below are the same disease and one migration. **What providers hand us that the importers currently throw away:** - **ProtonVPN** (`/vpn/logicals`, richest source): `Tier` and `Features` are parsed, then flattened into the name string (`us-ny-01-ProtonVPN-Plus-P2P`) and the structured values dropped. Also available and unkept: `Region` (state-ish, nullable), `Load` (live utilization %), `Score` (Proton's own ranking). The `/vpn` endpoint we already call for OpenVPN credentials reports the account's `MaxTier` — exactly what a tier grey-out needs. Latent bug while there: the features handling is an `elif` chain testing equality against 1/2/4, but `Features` is a bitmask — a server with multiple bits set matches nothing. - **Windscribe**: the serverlist carries a `pro` flag per datacenter group — discarded. Streaming locations (WINDFLIX) are detected but again encoded into the name string. No state field; group labels are "US Central"-style, not states. - **Mullvad**: importer uses the old WG-only public relay endpoint. The fuller relay API additionally exposes `owned` (Mullvad-owned vs rented), hosting provider, port speed, `active`. No tiers, city is the only sub-country level. - **AirVPN**: manifest carries per-server bandwidth/user/load stats, all ignored. No tiers; `location` is a city. **Schema shape this suggests** (optional fields, null where a provider has no equivalent): - `country` as ISO alpha-2 (the rekeying above), `region` populated where the provider supplies it (only Proton, sometimes), sort country → region → city. Don't synthesize states from city names — that re-creates the countries.json problem with worse data. - `tier` on the server plus a per-provider account capability stored at import (`MaxTier` / premium status), so the UI can grey out or exclude what the account can't use — both directions (hide free when paid, hide paid when free). - `features` as a structured list (`p2p`, `tor`, `secure-core`, `streaming`, Mullvad's `owned`) replacing name-string suffixes. - `latency` stays locally measured (it's a property of the path, no API has it) but persisted on the server record with a timestamp, so it sorts/filters like everything else with staleness visible. - Server load (Proton, AirVPN) only if paired with a lightweight refresh — it's live data, stale within hours under the 5-day update cadence. Proton `Score` and Mullvad port speed are cheap informational keeps. **Through-line:** the current system encodes structured data into display strings — tier and features into server names, country as display name. The fix is the same move everywhere: structured fields as canon, strings derived at render time. One schema migration covers both halves of this issue. **Caution:** the Windscribe `pro` flag and Proton `MaxTier`/`Region` specifics are partly from documentation, not observed traffic — re-verify against live responses during #32's provider testing before building on them.
mysticalsoap changed title from Reasses Country System to Overhaul server metadata (country system, tiers, features) 2026-08-27 15:31:36 -04:00
Author
Owner

Two additions to the schema sketch, from the WireGuard-import work (#196/#208):

  • Tunnel capability joins the structured fields. The ProtonVPN WireGuard import followed the inherited pattern and encodes tunnel into the entry name (-WireGuard suffix) with a duplicated row per server - the same string-encoding this issue diagnoses for tier/features. Target schema: one entry per server, tunnel(s) as a capability field with the per-tunnel data (public_key, port) alongside; the sibling collapse rides this issue's rekeying, since entry keys get rebuilt there anyway. The connect-time selection mechanics on top stay in #208 (sequenced after #176). The AirVPN/Windscribe imports (#209/#210) should target this schema, not more siblings.
  • The latency bullet now has issues. #205 (parallel + IP dedupe) and #206 (persist + refresh-when-stale) implement the 'locally measured, persisted with a timestamp' field described here; #206 can land as behavior before this schema and the field's final home lands with it.

Also worth knowing while in that import loop: the Features bitmask bug noted above sits in the same logicals loop the WireGuard siblings are built in - the WG rows currently inherit whatever name the broken elif chain produces.

Two additions to the schema sketch, from the WireGuard-import work (#196/#208): - **Tunnel capability joins the structured fields.** The ProtonVPN WireGuard import followed the inherited pattern and encodes tunnel into the entry name (`-WireGuard` suffix) with a duplicated row per server - the same string-encoding this issue diagnoses for tier/features. Target schema: one entry per server, tunnel(s) as a capability field with the per-tunnel data (public_key, port) alongside; the sibling collapse rides this issue's rekeying, since entry keys get rebuilt there anyway. The connect-time selection mechanics on top stay in #208 (sequenced after #176). The AirVPN/Windscribe imports (#209/#210) should target this schema, not more siblings. - **The latency bullet now has issues.** #205 (parallel + IP dedupe) and #206 (persist + refresh-when-stale) implement the 'locally measured, persisted with a timestamp' field described here; #206 can land as behavior before this schema and the field's final home lands with it. Also worth knowing while in that import loop: the Features bitmask bug noted above sits in the same logicals loop the WireGuard siblings are built in - the WG rows currently inherit whatever name the broken elif chain produces.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mysticalsoap/aqomui#52
No description provided.