Skip to main content

aa-holdfast

CI PyPI Python License Alliance Auth Apps

Sovereignty hub fuel, orbital skyhook theft windows and mercenary den sites for Alliance Auth, on the Equinox ESI routes. Warns Discord before a hub runs dry or a skyhook becomes lootable, and finds dens siphoning your workforce that ESI will not show you directly.

Sovereignty hub fuel

Screenshots use a demo install with invented systems and characters. Regenerate with docs/demo_seed.py.

Install

pip install aa-holdfast

Add to the end of myauth/settings/local.py:

INSTALLED_APPS += ["eveuniverse", "holdfast"]

from holdfast.schedule import CELERYBEAT_SCHEDULE as HOLDFAST_SCHEDULE
CELERYBEAT_SCHEDULE.update(HOLDFAST_SCHEDULE)

Then:

python manage.py migrate
python manage.py collectstatic --noinput
supervisorctl restart myauth:

That is the whole install. Three things worth knowing:

  • eveuniverse must be in INSTALLED_APPS. This app has foreign keys into its models. Leave it out and Auth will not start. Adding it twice is harmless — several other apps add it too.
  • No eveuniverse preload. Types, systems and planets are fetched as they are first seen.
  • 11 scheduled tasks come from holdfast.schedule. Merging the dict beats pasting eleven entries: a missing one does not error, it silently removes a feature. Retune one afterwards with CELERYBEAT_SCHEDULE["holdfast_update_all_owners"]["schedule"] = crontab(...), or read holdfast/schedule.py for what each does.

Requires Alliance Auth 4.6+, django-esi 9.10+ (for compatibility dates), django-eveuniverse, dhooks-lite, PyYAML.

Setup

1. Register a corporation. Grant yourself manage_owners; a Tokens tab then appears beside Settings in every section, and Add corporation there takes you through EVE SSO. These are corporation endpoints: the character needs the in-game Station Manager role, and an executor's token does not reach member corps. Every corp holding hubs or skyhooks registers its own. The first registration also decides the tracked alliance (below). The Tokens page shows what is tracked, and an Enable / Disable button on each corporation switches it off without deleting anything: its structures leave every page, badge and alert while it is off and come back when it is on.

An aa-structures token will not do. That app holds esi-corporations.read_structures.v1; these routes need esi-structures.read_corporation.v1. Similar names, different scopes — the Add button mints the right one.

2. Add a Discord webhook in Django admin (Holdfast → Webhooks), then pick which alerts go where on each section's settings page.

3. Grant permissions — see Permissions.

One tracked alliance per install. The first corporation registered decides it (an upgrade picks the alliance most registered corporations are in); the Tokens page shows it, and a SOV manager can change it on the SOV settings page. Corporations outside it cannot be registered and are never synced — a member with a Station Manager alt in another alliance cannot pull that alliance's whole sovereignty map onto your dashboard. A corporation with no alliance may be registered by a member of the tracked alliance. To follow a second alliance's sovereignty alongside, list it in HOLDFAST_TRACK_EXTRA_ALLIANCE_IDS; its corporations may then be registered too. Settings, thresholds and Discord channels are still one set per install, and Django admin bypasses the registration guard. Scope follows the corporation's alliance as Auth records it: registration refreshes that record from ESI first, and Auth refreshes it on its own schedule after that. Two alliances wanting separate warnings should run a second Auth.

Permissions

Each section is a ladder, and every rung includes the ones below it — grant one per section, never two.

Section Member User Officer Manager
Sovereignty sov_basic — sov_officer sov_manage
Skyhooks skyhook_basic — skyhook_officer skyhook_manage
Dens den_basic den_member den_officer den_manage

A den user may apply for a den slot — permission to anchor in your space. manage_owners registers corporation tokens and sits outside the ladders.

Put the *_basic permissions on your Member state rather than a group: they cover only what any member should see, and a state follows membership, so nobody has to be pruned later. A typical split:

Who Grant
Member state sov_basic, skyhook_basic, den_basic
Den operators den_member
Den officers den_officer
Sov fuel team sov_officer
Skyhook team skyhook_officer
Leadership sov_manage, skyhook_manage, den_manage, manage_owners

What it does

Three sidebar entries sharing one sync layer, one rate-limit budget and one set of tokens.

SOV Monitor

Page Who sees it
Dashboard, Hub Fuel, ADM, Timers officer
System Cost any member
Settings manager

Sovereignty timers

The hub detail route carries no state field, so a running Entosis defence event is the only public evidence a hub is actually under attack. Green is safe, amber is inside its daily window, red has a campaign against it.

Skyhook Monitor

Skyhook dashboard

Theft windows inside a configurable horizon, amber where a reagent is over its own bar. Only skyhooks holding reagents appear — workforce and power flow straight to the hub, so there is nothing on the others to steal. On a 413-skyhook alliance that is 51 rows rather than 413.

Each reagent gets its own threshold, because they are not worth the same trip: one alliance's magmatic gas median was 30,780 against 3,840 for superionic ice.

Den Monitor

Den admin

Every den route in ESI is character scoped — no corporation or public equivalent, and no notification for "somebody anchored a den next to your skyhook". So:

  • Your own dens sync from each operator's token.
  • Everyone else's are recorded by hand, friendly or hostile. A hand record is superseded automatically the day its operator registers a token.
  • Dens taking your workforce are found two ways. An untouched skyhook always reports a workforce figure that is a round multiple of ten; a percentage off it usually is not, which needs no history at all (measured). When the base was a multiple of a hundred that tell is absent, and the ratio against a peak this app recorded itself gives it away instead (inferred).

Den sites are the temperate-planet skyhooks you hold; slots appear on their own. A member joins a group, claims a free slot through EVE SSO, a manager approves, and the den syncs once anchored. Managers can revoke — which does not unanchor anything, so the slot says revoked, den still up until it comes down.

Alerts

A siphon alert in Discord

Every moment appears three times: how far away it is, the reader's local time, and EVE time. Discord renders the first two per viewer — which is why they are in Chinese above and will not be for you — and the third is the same for everyone.

Per-category routing, so fuel warnings and "something is being shot" can go to different channels. Thresholds and switches live on each section's settings page, not in local.py. A webhook can mention Auth groups instead of @here — pick them on the webhook in Django admin; each needs a Discord role of the same name, which Auth's Discord service creates.

python manage.py holdfast_test_alerts             # one sample of every alert
python manage.py holdfast_test_webhook
python manage.py holdfast_update                  # sync now, in the foreground
python manage.py holdfast_import_dens dens.csv    # load a den census

holdfast_import_dens takes planet,owner,corporation,hostile and records a den on each matching slot. Rows whose planet does not match are reported rather than guessed at — planet names are full of characters a survey sheet gets wrong (0 for O, O for Q). Add --clear-missing for a full census, --dry-run to preview.

Rate limiting

ESI meters in tokens, not requests: a 2xx costs two, a 304 costs one. The corp-structure bucket holds 300 tokens per 15 minutes, keyed on application and character together — and an Auth install has one ESI application, so that bucket is shared with aa-structures and corptools on your own site, not with anyone else's.

Each run pulls both listings (they decide what exists) and spends HOLDFAST_DETAIL_CALLS_PER_RUN detail calls on whatever has gone longest without one. At the default 60 that is 60 x 2 + 2 x 2 = 124 tokens, 41% of the bucket, and a 150-structure corp refreshes fully in about 40 minutes — inside the hour CCP caches these routes anyway. Structures awaiting a first detail pull show as queued.

Settings

All optional; most tuning belongs on the in-app settings pages instead.

HOLDFAST_ESI_COMPATIBILITY_DATE = "2026-08-18"   # must be >= 2026-05-19
HOLDFAST_DETAIL_CALLS_PER_RUN = 60               # per owner, per run
HOLDFAST_DEN_DETAIL_CALLS_PER_RUN = 10
HOLDFAST_SKYHOOK_MIN_UNSECURED = 100             # fallback; see per-reagent bars
HOLDFAST_TRACK_EXTRA_ALLIANCE_IDS = []           # tracked alongside the alliance on the SOV settings page
HOLDFAST_OWNER_SYNC_JITTER_SECONDS = 120
HOLDFAST_PLANET_RESOLVE_PER_RUN = 60
HOLDFAST_DEN_NOTIFICATION_MAX_AGE_HOURS = 24
HOLDFAST_DEN_FIRST_SYNC_GRACE_MINUTES = 90
HOLDFAST_RAIDABLE_CACHE_SECONDS = 300
HOLDFAST_STALE_PRUNE_DAYS = 14

Notes

  • These routes need an X-Compatibility-Date of 2026-05-19 or later. Without one ESI answers 404 rather than saying why, and the old /latest/swagger.json spec is gone — which is why so little is built on them. django-esi 9.10+ sends the header; this app pins its own date.
  • A tactical operation's dungeon_type_id indexes dungeons, not inventory types, so it is shown as a number. Nothing names a dungeon.
  • aa-structures parses skyhook notifications and builds timers from them. It does not read the structure routes, so it cannot see fuel, reagent stock or theft windows. The two complement each other.

Development

python runtests.py          # sqlite, no Auth project needed
ruff check holdfast tests

docs/README.md covers the demo seeder, the screenshot script, and docs/mutate_structure.py, which breaks each structural rule on purpose to check the test guarding it actually fails.

Metadata

Release files for aa-holdfast 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aa-holdfast 0.1.9
File Size Uploaded
aa_holdfast-0.1.9.tar.gz 172.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aa-holdfast 0.1.9
File Interpreter ABI Platform
aa_holdfast-0.1.9-py3-none-any.whl Python 3 none any Details

Total release size: 381.4 kB

Release files / aa_holdfast-0.1.9.tar.gz

Download URL aa_holdfast-0.1.9.tar.gz
Size 172.1 kB
Tags Source
SHA-256 checksum
How to use checksums
74828da5c79dea3bc943456abff2aa2012c5b7dd3b20023b13d4f752b586418b
BLAKE2b-256 checksum
How to use checksums
ef3c5b588ff52b532087941df25599a27deaa6c5c775aa36bb9db3a6616594f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / aa_holdfast-0.1.9-py3-none-any.whl

Download URL aa_holdfast-0.1.9-py3-none-any.whl
Size 209.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ffc0a0d83d00e6293fbda81cd49d2a006876757764d53d5e09f348b0d4e94b7d
BLAKE2b-256 checksum
How to use checksums
8c11e46dbe4873ee110d4442399b7a083d232b136f211200629d7ab7c215a3fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release history Release notifications | RSS feed

This release

0.1.9 This release

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page