
Holdfast
Sovereignty hub fuel, orbital skyhooks and mercenary dens for Alliance Auth, on the Equinox ESI routes.
aa-holdfast
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.

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. The version running shows beside the page title on every Holdfast page, so nobody needs Django admin to answer "which one is this". Three things worth knowing:
eveuniversemust be inINSTALLED_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
eveuniversepreload. 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 withCELERYBEAT_SCHEDULE["holdfast_update_all_owners"]["schedule"] = crontab(...), or readholdfast/schedule.pyfor what each does.
Requires Alliance Auth 5.2+ (CI runs 5.2, 5.4 and the latest release), 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 needesi-structures.read_corporation.v1. Similar names, different scopes — the Add button mints the right one.
2. Add a Discord webhook on the Webhooks page — beside Tokens in every section's header, open to any section manager; Django admin still works — then pick which alerts go where on each section's settings page. Send test on a webhook posts one sample to that channel alone, pings included.
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
One bottom rung for the whole app, then a ladder per section where every rung includes the ones below it — grant one per section, never two.
| Section | Member | User | Officer | Manager |
|---|---|---|---|---|
| All sections | basic_access |
— | — | — |
| Sovereignty | — | — | sov_officer |
sov_manage |
| Skyhooks | — | — | skyhook_officer |
skyhook_manage |
| Dens | — | den_member |
den_officer |
den_manage |
basic_access opens the member-facing page of each section: System Cost, Raid
Targets, and your own dens and timers. A den user may also apply for a den
slot — permission to anchor in your space. manage_owners registers
corporation tokens and sits outside the ladders.
Put basic_access on your Member state rather than a group: it covers 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 | basic_access |
| 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 |

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

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

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.
Tactical operations on your own dens appear on the den dashboard and the
sidebar badge, and are announced to Discord, once the den is at the anarchy
level set on the den settings page — 2 by default, since below that nobody
runs them; 0 counts every operation. An operation on a den below the bar is
announced the day the den reaches it. Ignore on a dashboard row takes that
one operation off your own dashboard and badge and nothing else. Both the
rows and the badge count need the dashboard (den_member or above); with
basic access alone your operations show on the timers page only.
Alerts

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 Webhooks page; each needs a Discord role of the same name,
which Auth's Discord service creates. A webhook URL must be Discord's own
(https://discord.com/api/webhooks/..., or the discordapp.com, canary and
PTB spellings); the form refuses anything else, since the server posts every
alert to whatever is saved there.
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_DEN_IDLE_HOURS = 6 # re-check a character with no dens this often
HOLDFAST_RAIDABLE_CACHE_SECONDS = 300
HOLDFAST_STALE_PRUNE_DAYS = 14
Notes
- These routes need an
X-Compatibility-Dateof 2026-05-19 or later. Without one ESI answers 404 rather than saying why, and the old/latest/swagger.jsonspec is gone — which is why so little is built on them.django-esi9.10+ sends the header; this app pins its own date. - A tactical operation's
dungeon_type_idindexes dungeons, not inventory types, so it is shown as a number. Nothing names a dungeon. aa-structuresparses 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.
No reviews yet...
Python Requirements
- allianceauth>=5.2
- django-esi>=9.10
- django-eveuniverse>=1.5
- dhooks-lite>=1.1
- PyYAML>=6.0
Required Python packages to be installed and other Python requirement.
App Dependencies
Required apps:
Used by apps:
-
Dependencies to other apps registered in this app directory.
Classifiers
- Development Status :: 4 - Beta
- Environment :: Web Environment
- Framework :: Django
- Framework :: Django :: 4.2
- Intended Audience :: End Users/Desktop
- Natural Language :: English
- Operating System :: POSIX :: Linux
- Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.10
- Programming Language :: Python :: 3.11
- Programming Language :: Python :: 3.12
- Topic :: Games/Entertainment
- Topic :: Internet :: WWW/HTTP :: Dynamic Content
Version
0.1.10
License
?
Homepage
https://github.com/yilifaer/aa-holdfast
PyPI
https://pypi.org/project/aa-holdfast/
Last updated
2 days, 14 hours ago
First published
1 month, 3 weeks ago
Category
Plugin Apps
Rating
-Authors
Invidia Gloriae Comes
Maintainers
yilifaerr
Please login to see more options.