Skip to content

Latest commit

 

History

234 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vigilant

Vigilant

An EVE Online companion dashboard that gives you a unified view of all your characters — wallet, skills, industry, intel, a full interactive star map, and much more, all in one place.

tests License EVE Online Python FastAPI React Pixi.js

Vigilant dashboard — every character in one view with wallet, location, ship, clones and skill training
The dashboard: every character on one page — wallet, location, current ship, clones, and skill training, grouped by account.


Screenshots

Star map

Star map — ~5,485 K-space systems and ~6,985 stargate links in WebGL, with the security overlay.

Kill feed

Kill feed — live kills with the most valuable structure and ship losses of the last 7 days.

Corporations

Corporations — every corp your characters belong to, player and NPC, with the corp scopes each one has granted.

Corporation detail

Corporation detail — members, per-character scopes, wallet divisions, and structures with fuel and vulnerability timers.

Ship fitting tool

Ship fitting — full stat simulation: EHP, resist profile, fitting resources, and capacitor.

Market price history

Market — daily price history and traded volume for any published item.

Gate check

Gate check — route safety scored per jump from recent kill activity.

Industry tools

Industry — manufacturing, build finder, compression, hauling, mining ledger, and PI.

Skill plans

Skill plans — shared plans with per-character gap analysis and training estimates.

Login screen

Sign-in — EVE SSO only. No passwords are ever stored.


Features

Dashboard (/dashboard)

  • Multi-character overview — character cards with portrait, corp/alliance, online status, wallet, location, current ship, skill training with progress bars, and sync status
  • Account grouping — organize alts into named account groups with drag-and-drop reordering
  • Sort modes — Grouped (custom), Name, Corp, Training, Queue End
  • Wealth breakdown — per-character wallet bars with proportion visualization
  • Automatic background sync — scheduler refreshes 12 ESI data fields per character on their cache timers (1m–1h); page loads never trigger ESI calls
  • Aggregated panels — wealth breakdown, contracts, fleet battles, recent kills, activity, pilot pulse, combat profile, corp stats, and ESI cache status, all lazy-loaded on one page
  • Alert banners — persistent banners for structure attacks, fuel alerts, sovereignty events, and critical inventory thresholds

Star Map (/map)

  • Interactive 2D WebGL map — ~5,485 K-space systems rendered with Pixi.js v8, ~6,985 stargate edges, pan/zoom/pinch with pixi-viewport
  • Data overlays — Security status, Jumps, Ship/Pod/NPC Kills, Sovereignty (with alliance names and logos), Faction Warfare, Incursions — bottom bar selector with color legends
  • System info panel — security, region, constellation, NPC station count, service badges (Clone/Manufacturing/Lab/Market/Refinery/Repair/Reprocessing/Jump Clone), sovereignty holder, kill/jump stats, DOTLAN and zKillboard links
  • Gate routing — shortest path via Graphology with preferences (shortest/highsec/lowsec/nullsec)
  • Jump drive planner — capital ship jump calculator with 8 ship classes, JDC/JFC skill levels, route preferences (prefer NPC station, prefer highsec gate), fatigue and fuel calculator, editable midpoints, alternative system lists, range viewer
  • Search — find systems, regions, constellations, and services (type "cloning", "factory", "jc" etc. to find nearest service to a character)
  • Grouping modes — System/Constellation/Region with expand-in-place
  • Character locations — pulsing gold markers at character positions with "Locate Me" button
  • Live ESI data — background polling for kills, jumps, sovereignty, faction warfare, and incursions with ETag caching

Skill Plans (/skill-plans)

  • Create and edit skill plans — add skills from search, ship requirements, mastery levels, or fittings
  • Gap analysis — per-character skill gap with training time estimates
  • Import/Export — import from EVE text format, export skill plans, share via public link
  • Drag-and-drop reorder — sort by name, optimal training order, or level

Ship Mastery (/skill-plans/ship/{id})

  • Full prerequisite tree — every skill required to fly a ship, expanded recursively
  • Mastery levels I-V — EVE's official mastery certificates with progress tracking per character

Intel

Gate Check (/intel/gatecheck)

  • Route safety analysis — enter an origin and destination (or pick a common trade route) to score every jump for recent kill activity
  • Gatecamp finder — identifies likely gatecamp systems using zKillboard data
  • War target lookup — a separate tab that resolves a corp or alliance and reports its war-target status; it is not folded into the route check

D-Scan (/intel/dscan)

  • Paste parser — paste your directional scan results from the EVE client
  • Ship categorization — groups results by ship class and type with counts
  • Shareable results — generate a link to share D-Scan results with others

Character Detail (/character/{id})

  • Wallet history chart — interactive Chart.js graph with time ranges (1d/5d/1w/1m/6m/1y)
  • Wallet journal — last 20 entries inline + "Full Journal" link to dedicated page
  • Skills — trained skills with remap optimizer, what-if simulator, per-skill comparison
  • Mail reader — mail list with click-to-read body and sender name resolution
  • Notification feed — parsed notification types with human-readable labels and summaries
  • Implants & jump clones — attribute enhancers sorted by slot, hardwirings, per-clone implant sets
  • Assets — background-synced asset browser grouped by location
  • Corp history — full employment timeline

Ship Fitting Tool (/tools/fitting)

  • Full fitting builder — three-column layout with module browser (market-group tree), slot layout (high/mid/low/rig/subsystem/drone), and live stats
  • Dogma-accurate engine — DPS, EHP, capacitor simulation, offense/defense/navigation/targeting/drones, stacking penalties, module-to-module bonuses (Bastion/Siege), Triglavian spool-up, T3C subsystems with per-level scaling
  • Character-accurate stats — dropdown picks any of your characters; DPS/EHP/CPU/PG recompute against that character's actual trained skills (falls back to All V when no character selected)
  • Skill-requirement warnings — ⚠ pip + red row highlight on any ship/module/drone the selected character can't use; hover tooltip lists missing skills with need/have levels
  • Module overheating — toggle per module with correct per-type overload bonuses
  • Module info popup — click any fitted module, drone, or search result for icon, description, and formatted dogma attributes
  • Warp speed — AU/s in the Navigation panel, Hyperspatial rigs apply correctly
  • HP ↔ EHP and VAL ↔ % toggles — flip Defense between raw HP and EHP-adjusted; flip Fitting Resources between absolute and percent used
  • EFT import/export — paste from EVE or Pyfa with aggressive Unicode normalization; one-click export to clipboard
  • Import from character — pulls your in-game saved fittings; Load to builder or Import to a saved folder

Saved Fittings (/tools/fitting/saved)

  • Searchable, sortable table — ship, fitting name, folder, DPS, estimated cost (live from /markets/prices/)
  • Nested folders — create, rename, move, delete; folder membership persists per fit
  • Click to load — any row opens the builder with that fit restored, ready to edit or compare

Fitting Viewer (/character/{id}/fittings)

  • Slot-organized display — High/Mid/Low/Rig/Subsystem/Drone/Cargo with ship slot counts
  • Ship render images — grouped by ship type
  • EFT export — "Copy EFT" button for pasting into EVE client or Pyfa

Blueprint Library (/character/{id}/blueprints)

  • BPO/BPC display — ME/TE research levels, remaining runs for copies
  • Filters — All, BPO Only, BPC Only, Unresearched; group by Type or Location
  • Stats — total count, originals, copies, researched, fully maxed (ME10/TE20)

Manufacturing Calculator (/industry/manufacturing)

  • Blueprint search — live search with full modifier support (ME, TE, structure, rig, security)
  • Nested build/buy — click "Build" on any component to see its sub-BOM; recursive for sub-components
  • Build time estimates — parallel build (components simultaneous + final assembly), sequential build, per-component times
  • Shopping list — aggregated materials with multibuy-compatible copy
  • Send to Compressor — one-click transfer of mineral requirements to the compression calculator
  • Persistent state — settings saved in localStorage

Compression Calculator (/industry/compression)

  • LP solver — scipy linear programming finds mathematically optimal compressed ore mix
  • Three optimization modes — Lowest ISK, Lowest Volume, Lowest Waste
  • Character skill integration — auto-fetches reprocessing skills
  • Full reprocessing yield calculation — structure, rig, security, implant modifiers
  • Trade hub selector — Jita, Amarr, Dodixie, Hek, Rens (labels the output; valuation uses EVE's global average reference price)

Mining Ledger (/industry/mining-ledger)

  • Unified cross-character/corp mining — aggregated view across all characters and corps
  • Stacked ore chart — visual breakdown by ore type over time
  • Date range filters — 7d, 30d, 90d, 6m, 1y
  • Per-character/corp views also available at /character/{id}/mining and /corporations/{id}/mining

Industry Jobs (/industry/jobs)

  • Combined active-jobs view across every owned character and every corp where at least one character has the corp-jobs scope (Director fallback cycles through characters on 403)
  • Dropdown filters — per-character, per-corp, per-activity chips, and source-kind toggle (all / characters only / corps only)
  • Structure name resolution — shared StructureNameCache with proactive corp-structures prefetch so player citadels show up by name
  • Include-completed toggle — widen the view to finished jobs when doing post-mortems
  • NPC corps surfaced honestly — skipped automatically (their endpoint always 403s) but counted in the subtitle so the number stays explainable

Structure Timers (/structure-timers)

  • Shared timer board — manual entry with countdown or absolute UTC time
  • ESI auto-detection — automatically detects structure reinforcement from ESI
  • Live countdown — real-time JavaScript countdown timers
  • ACL groups — control visibility by corporation, alliance, or individual character
  • Role-based permissions — edit and delete access based on user roles

Corporation Features

  • Corp overview — wallet divisions, industry jobs, market orders, structures (fuel/reinforcement status), contracts, member list
  • Corp wallet journal — 7 divisions with full filtering
  • Corp blueprints — ME/TE, location, BPO/BPC filters
  • Corp mining — aggregated across all characters with mining scope
  • Inventory tracker — monitor corp hangar items with configurable alert thresholds

Notifications & Alerts

  • Browser notifications — bell icon with dropdown for skill completions, industry jobs, PI, mail, structure alerts, and inventory alerts
  • Structure alert banners — persistent banners for structure attacks, fuel, sovereignty events, and moonmining. Deduped across characters
  • Inventory alert banners — critical corp inventory thresholds shown as persistent banners
  • Granular muting — per-type notification muting (structure attacks, fuel, sov, moonmining, POCO independently)

Admin Panel (/admin)

  • System health — scheduler status, DB stats, ESI health monitoring
  • User management — view and manage users with role assignment (Admin/Manager/User)
  • Character management — view all registered characters
  • SDE management — trigger Static Data Export reimports
  • Audit log — logins, sync errors, admin actions
  • Registration allowlist — restrict registration by character, corporation, or alliance

Other Features

  • ESI rate limit monitoring — real-time dashboard with request activity chart and per-group tracking
  • Sync diagnostics — per-field warnings, stale data indicators, manual resync buttons
  • Server status — Tranquility online/offline indicator with player count and EVE time (UTC) clock
  • Cross-character asset search — search assets across all your characters from /assets

Quick Start (Local)

Prerequisites

  • Python 3.12+ — python.org
  • Node.js 22+ — nodejs.org (needed to build the star map frontend)
  • An EVE Online developer application — create one here
    • Set the callback URL to http://localhost:8000/auth/callback
    • You will need the Client ID and Client Secret

Installation

# Clone the repository
git clone https://github.com/Thor6677/Vigilant.git
cd Vigilant

# Run the startup script
./start.sh

The start.sh script will:

  1. Create a .env file from .env.example if one doesn't exist
  2. Auto-generate a secure SECRET_KEY
  3. Prompt you for your EVE SSO Client ID and Secret
  4. Create a Python virtual environment and install dependencies
  5. Launch Vigilant in the background
  6. Wait for the app to confirm it's listening

Vigilant will be available at http://localhost:8000 once startup is complete.

Note: The local startup script runs the Python backend directly. The star map (/map) requires the React frontend to be built separately. To build it locally: cd frontend && npm ci && npm run build. The Docker deployment handles this automatically.

View Logs

tail -f vigilant.log

Stop

./stop.sh

VPS / Server Deployment (Docker)

This section walks you through deploying Vigilant on a VPS (Virtual Private Server) from scratch. If you've never set up a server before, follow each step — everything you need is covered here.

What You'll Need

  • A VPS from any provider (DigitalOcean, Linode, Hetzner, Vultr, etc.) running Ubuntu 24.04
    • Minimum: 1 CPU, 1 GB RAM, 25 GB disk
    • Recommended: 2 CPU, 2 GB RAM (the star map build uses some memory)
  • A domain name pointed at your VPS IP address (e.g., vigilant.yourdomain.com)
  • An EVE Online developer application — create one here
    • Set the callback URL to https://vigilant.yourdomain.com/auth/callback (use your actual domain)

Step 1: Get a VPS

If you don't have a VPS yet:

  1. Sign up at a provider like DigitalOcean, Linode, Hetzner, or Vultr
  2. Create an Ubuntu 24.04 server (called a "Droplet" on DigitalOcean, "Linode" on Linode, etc.)
  3. Choose the cheapest plan that meets the minimum specs above
  4. During creation, add your SSH key (or the provider will email you a root password)

Connecting to your VPS:

# From your local terminal (replace with your VPS IP)
ssh root@YOUR_VPS_IP

If you're on Windows, use Windows Terminal with the built-in SSH client, or PuTTY.

Step 2: Set Up the Server

Once connected to your VPS, run the included setup script:

# Download and run the setup script (as root)
curl -fsSL https://raw.githubusercontent.com/Thor6677/Vigilant/main/setup_vps.sh -o setup_vps.sh
chmod +x setup_vps.sh
./setup_vps.sh

This installs Docker, Docker Compose, git, and creates a vigilant user (a normal login account with a home directory and shell, added to the docker group) with the app directory at /opt/vigilant. Membership of the docker group is equivalent to root on the host — keep that account's access as tight as you would root's.

Step 3: Point Your Domain

Before getting an SSL certificate, your domain needs to point to your VPS:

  1. Go to your domain registrar's DNS settings (Cloudflare, Namecheap, etc.)
  2. Add an A record:
    • Name: vigilant (or whatever subdomain you want)
    • Value: Your VPS IP address
    • TTL: Auto or 300
  3. Wait a few minutes for DNS to propagate. You can check with:
    dig vigilant.yourdomain.com

Step 4: Stand up a reverse proxy

Vigilant's app container exposes port 8000 internally on a Docker network — it does NOT bind 80/443. You need a reverse proxy in front of it that terminates TLS and routes traffic to the container.

If you already have nginx/caddy/traefik on the host, point it at vigilant-app-1:8000 on the web Docker bridge. If you don't, the simplest path is to copy docs/nginx-sample.conf from this repo into /etc/nginx/conf.d/ (or use a small companion nginx container) and adjust the server_name + cert paths.

You'll need a TLS cert. Two common options:

  • Let's Encrypt — certbot certonly --standalone -d vigilant.yourdomain.com, then point the proxy at /etc/letsencrypt/live/vigilant.yourdomain.com/.
  • Cloudflare Origin Certificate (if you proxy through Cloudflare) — generate from SSL/TLS > Origin Server, save the cert + key wherever your proxy expects them.

Create the shared Docker network so the proxy and the app can talk:

docker network create web

Step 5: Clone and Configure

# Switch to the vigilant user
su - vigilant

# Clone the repository
git clone https://github.com/Thor6677/Vigilant.git /opt/vigilant
cd /opt/vigilant

# Create your config file
cp .env.example .env
nano .env

Edit .env with your settings:

EVE_CLIENT_ID=your_eve_client_id
EVE_CLIENT_SECRET=your_eve_client_secret
EVE_CALLBACK_URL=https://vigilant.yourdomain.com/auth/callback
SECRET_KEY=generate_a_random_key_see_below
DEBUG=false

Generate a secure SECRET_KEY:

python3 -c "import secrets; print(secrets.token_urlsafe(64))"

Step 6: Deploy

cd /opt/vigilant
docker compose up -d

That pulls a published image (ghcr.io/thor6677/vigilant:latest) rather than building one — no build toolchain needed and it starts in seconds. Pin a specific release instead with VIGILANT_TAG=v1.0.0 docker compose up -d.

Prefer to build from source? Add the build overlay:

docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

The first build takes a few minutes (it installs Python dependencies and builds the React frontend). Subsequent builds are faster thanks to Docker's layer cache.

Verify everything is working:

# Check the app container is running
docker compose ps

# Check app logs for errors
docker compose logs app

# Hit the healthcheck through the proxy
curl https://vigilant.yourdomain.com/healthz

Visit https://vigilant.yourdomain.com — you should see the login page.

Managing Your Deployment

# View live logs
docker compose logs -f

# View just app logs
docker compose logs -f app

# Restart (does NOT apply code changes)
docker compose restart

# Pull and redeploy the newest published release
docker compose pull && docker compose up -d --force-recreate

# Rebuild from local source instead (REQUIRED for your own code changes)
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build --force-recreate

# Stop everything
docker compose down

Important: docker compose restart does NOT apply code or template changes. Use docker compose pull && docker compose up -d --force-recreate to move to a newer release, or the build-overlay command above to run local source changes.

A note on scripts/: the docker compose commands above are the supported way to run Vigilant, and they are all you need. The repo also ships scripts/deploy.sh, scripts/rollback.sh, and scripts/health-check.sh — maintainer convenience wrappers, written for one specific host. They assume the repo lives at /opt/vigilant and the container is named vigilant-app-1, so they will not work unmodified on your machine. Both read host-specific settings from an untracked .health-env at the repo root: deploy.sh uses MAINTENANCE to point at an ops toolkit's maintenance script (skipped cleanly when unset), and health-check.sh needs PROBES (a shared probe library providing probe_container / probe_http / probe_log_errors) plus a HEALTHZ_URL. health-check.sh exits 0 when healthy, 1 when a probe genuinely fails, and 78 (EX_CONFIG) when those settings are missing — automation that restarts or restores on failure should treat 78 as "could not assess", not as "down".

None of them are required to run or update Vigilant. Use the docker compose commands above, or read the scripts and adapt the paths to your own host.

Updating to a New Version

Releases are tagged and published as container images, so updating is a pull:

cd /opt/vigilant
git fetch --tags && git checkout v1.0.0   # keeps compose/scripts in step with the image
VIGILANT_TAG=v1.0.0 docker compose up -d --force-recreate

Omit VIGILANT_TAG to track :latest. The app automatically migrates the database schema on startup.

When signed in as an admin, Vigilant tells you when a newer release exists — a banner in the UI and, if you opt in via DISCORD_ALERT_TYPES, one Discord message per release (alert type update_available). It never updates itself unless you explicitly set that up; you choose when to deploy — from the command line above, or from the browser if you enable the optional updater below, which can also apply an update at a time you pick or on a weekly window.

In-App Updates (optional, off by default)

Deploy a release from Admin › Updates instead of over SSH. Off unless you explicitly turn it on, and a default install behaves exactly as if this section did not exist.

Read this before enabling it

The updater is root-equivalent on the host, by construction. It holds the Docker socket, and anything that can create containers can create a privileged one that mounts your whole filesystem. No arrangement of flags makes that untrue, and this feature does not claim a sandbox.

What it does instead:

  • Narrow capability. It performs exactly two operations — deploy a release, roll back to a release — by running the same scripts/deploy.sh and scripts/rollback.sh you would run yourself. It is not a general Docker gateway.
  • No network surface. There is no listener, no API and no shared secret. The two containers talk by writing files to a shared volume, so there is nothing to authenticate and nothing reachable from the network.
  • Vigilant itself stays unprivileged. The app keeps cap_drop: ALL, read_only: true, uid 10001, and never touches the Docker socket. A remote-code-execution bug in Vigilant gets an attacker the ability to write one JSON file, whose contents the privileged side validates before acting on.
  • The sidecar is hardened too, for what that is worth. Its own container gets read_only: true, cap_drop: ALL and a size-capped /tmp — the same posture the app keeps, and no cap_add, because unlike the app the sidecar is never root and needs no capability handed back. Be honest about what this buys: the sidecar already runs as a non-root uid with no-new-privileges, so it held no effective capabilities before this either, and Docker socket access is root on the host regardless of what its rootfs or capability set say — none of this contains a compromised sidecar. What it does do is real: no writable rootfs to persist anything into, no capability it never needed, and the most privileged container in the stack no longer being the one visibly least hardened.
  • One more privileged container, briefly. When the sidecar upgrades itself (see below) it launches a short-lived helper that holds the Docker socket for a few seconds to recreate the updater service. It is the same privilege as the sidecar, not a new one: same uid, gid and groups, no-new-privileges, read_only, cap_drop: ALL, a size-capped /tmp, no network at all, and no listener. It runs one compose command and exits.

A compromised admin session can restart your app and roll it back to any release this host has previously run. Rollback targets are restricted to that list, but an older release with a known bug is still reachable. If that trade is not worth it to you, leave this off and keep using SSH — nothing else depends on it.

Enabling it

Order matters: the updater cannot install itself. The compose file comes from the repo checkout, so the release that first adds the updater has to be deployed the normal way before the profile can start.

cd /opt/vigilant

# 1. Deploy a release that includes the updater, the usual way.
scripts/deploy.sh --tag vX.Y.Z

# 2. Tell compose who to run as. Run these ON THE HOST, not in a container.
id -u                                # -> VIGILANT_UID
id -g                                # -> VIGILANT_GID
getent group docker | cut -d: -f3    # -> DOCKER_GID
$EDITOR .env                         # set all three

# 3. Start the sidecar. `up -d` alone will NOT create it — the profile is
#    what makes it exist at all.
docker compose --profile updater up -d

Then reload Admin › Updates. The panel appears once the sidecar's heartbeat is less than 60 seconds old; if it stays hidden, the sidecar is not running.

Running a fork, or a mirrored registry? Both images are parameterised: VIGILANT_IMAGE (default ghcr.io/thor6677/vigilant) and VIGILANT_UPDATER_IMAGE (default ghcr.io/thor6677/vigilant-updater), each combined with VIGILANT_TAG. Leave them unset and nothing changes — the defaults resolve to exactly the images that were hardcoded before. Set them in .env and scripts/deploy.sh, compose, and the sidecar's self-update all agree on where a tag's image lives. Set only one of the two and they can disagree, which shows up as a "not found" at pull time.

origin must be fetchable without credentials

The sidecar runs scripts/deploy.sh, which starts with a git fetch. It ships no SSH client and no credentials, deliberately — it already holds the Docker socket, and storing a deploy key beside that would mean one compromise yields both root on the host and push access to your source.

So the clone at /opt/vigilant needs an origin that is anonymously readable over HTTPS:

  • An https://github.com/… origin — works.
  • A github.com SSH origin, in either spelling (scp-style, or the ssh:// scheme) — works. The sidecar image rewrites github.com SSH origins to HTTPS for its own git calls, using git's insteadOf supplied through the environment, so it applies to deploy.sh and to anything you run via docker exec as well. Your clone is not modified, and pushing from an SSH session is unaffected.
  • A private repository, or an SSH origin on any other host (GitLab, Gitea, a self-hosted mirror) — not supported by in-app updates. The panel shows a failed remote check explaining why and keeps the buttons disabled. Deploy those with scripts/deploy.sh over SSH as usual; nothing else changes.

The remote check runs at startup and, while it is failing, retries every few minutes — so a sidecar that came up before the host's network did re-enables itself without a restart.

The sidecar upgrades itself

deploy.sh and rollback.sh recreate only the app service (up -d --no-deps --force-recreate app), and that does not change. It cannot: during an in-app update the sidecar is the process running deploy.sh, and compose is client-driven — telling it to recreate the sidecar from inside the sidecar kills the client mid-recreate, and the worst case is that the old container is gone, the new one never starts, and the install has no updater at all.

So the sidecar updates itself a step later, and out of the way. After an in-app update has succeeded and its lock is released, it:

  1. asks compose which image the updater service should be running, reading the compose file on disk (so a mirrored registry or a fork's namespace is honoured, and nothing is reconstructed from a naming convention);
  2. pulls that image while the old container is still alive and serving, so a missing or unpublished sidecar image fails here, with the registry's own words, and nothing has been touched;
  3. launches a short-lived helper container from the image it just pulled, which runs docker compose --profile updater up -d --no-deps updater.

The helper is not part of the compose project, which is what lets it survive the recreate it is performing. It runs with no network (the image is already local), no-new-privileges, read_only, cap_drop: ALL, a size-capped /tmp (it runs docker compose, which needs somewhere writable under HOME), and the same uid, gid and supplementary groups as the sidecar — the same privilege, for a few seconds, and no new capability. It is not removed on exit: its exit code and logs are the post-mortem if something goes wrong.

While this happens the panel says "The updater is upgrading itself to vX…". Requests submitted during the handoff are not lost — they sit in the shared volume and are picked up by the new sidecar.

Forward only. The sidecar never downgrades. Roll the app back and the sidecar stays where it is. It is the privileged component, so it should sit on the most-fixed version available; a downgraded sidecar would also lose this feature and strand itself, needing the manual recreate all over again. A newer sidecar with an older app is a supported combination — the app reads the shared files generically, and rollback targets are floored at the first release that shipped the updater.

It happens only after the sidecar's own successful update — never on a timer and never at startup. A timer could fire while a command-line deploy was mid-flight and run two compose operations against one project at once. The cost is that scripts/deploy.sh over SSH still leaves the sidecar behind, which the panel now says out loud instead of hiding:

The updater is still running v1.2.2 while Vigilant is on v1.2.3…

That warning names both versions and the command below, and it does not disable the Update and Rollback buttons — an in-app update is how a lagging sidecar heals itself. If a self-update was attempted and failed, the reason is shown with it.

The manual recreate, and the one-time bootstrap

The manual recreate remains the fallback, and is still the right thing to run after a command-line deploy that changed the updater:

cd /opt/vigilant
docker compose --profile updater up -d updater

Sidecars from v1.2.2 and earlier cannot upgrade themselves — they predate the code that does it. The release that first ships self-update therefore still needs that command run once, by hand, after deploying it. From then on, in-app updates carry the sidecar along with them.

Two compose gotchas

A bare docker compose down does not stop the updater. Without the profile compose does not know the service exists, so it stops the app, removes the network — and leaves the sidecar running, now detached. Always pass the profile when bringing the stack down:

docker compose --profile updater down

--remove-orphans is version-dependent. On Compose v5.5.1 a profile-disabled service is not treated as an orphan and survives the flag (verified). Older Compose versions did remove profile-disabled services. If you are not on a recent Compose, check before combining that flag with a bare up -d.

Neither affects scripts/deploy.sh: it recreates only the app service (up -d --no-deps --force-recreate app) and never passes --remove-orphans, so an in-app update cannot delete the sidecar out from under itself mid-run.

Troubleshooting

Symptom Cause Fix
No panel in Admin › Updates Sidecar not running, or its heartbeat is stale docker compose --profile updater ps; check docker logs vigilant-updater-1
Panel says "socket: FAIL" DOCKER_GID does not match this host getent group docker | cut -d: -f3, correct .env, recreate the sidecar
Panel says "git: FAIL" /opt/vigilant not mounted, or not owned by VIGILANT_UID Check the bind mount is /opt/vigilant:/opt/vigilant on both sides
Panel says "remote: FAIL" The sidecar cannot fetch from origin — an SSH origin on a non-github host, a private repo, or no outbound network See origin must be fetchable without credentials. The check retries by itself every few minutes
Panel says "deployed: FAIL" .deployed missing — no release has been deployed by the scripts yet Deploy once with scripts/deploy.sh
Panel says "tmp: FAIL" The sidecar cannot create a file in its own temp directory — scripts/deploy.sh and scripts/rollback.sh copy themselves there before doing anything else, so neither can run until this is fixed Confirm the updater: service's /tmp mount in docker-compose.yml is actually a writable tmpfs on this host, not a read-only leftover from an older config
Buttons are disabled One of the self-checks above is failing Fix the named check; the panel lists which
"The last run was interrupted" The sidecar died mid-deploy Check the host and docker logs; the app will not queue another run until it is resolved
Panel says the updater is still running an older version The last deploy was run from the command line, or this sidecar predates self-update docker compose --profile updater up -d updater from the install directory. Updating and rolling back still work meanwhile
Panel reports a failed self-update The new sidecar image could not be pulled, or the helper could not recreate the service — the reason is shown with the warning Fix what the reason names, then run the manual recreate above. The deploy itself succeeded; only the sidecar is behind
A …-updater-selfupdate container is lying around The self-update helper. It is deliberately not --rm, so its exit code and logs survive as the post-mortem Its name is the install directory's basename plus -updater-selfupdate — vigilant-updater-selfupdate for /opt/vigilant, vigilant-dev-updater-selfupdate for /opt/vigilant-dev — so two stacks on one Docker host never collide. docker logs <name> to see what happened, then docker rm <name>. The sidecar removes it by itself once it has read it, so a lingering one means the sidecar never restarted
Update button never appears Already on the latest release, or the hourly checker has not polled yet Compare the version chip against the newest release
A second app container appeared The repo was bind-mounted somewhere other than /opt/vigilant compose derives its project identity from that path — it must be identical on both sides

Rolling back

The panel offers only releases this host has actually run, read from .deployed. Because code and image are pinned to the same tag, a rollback needs no follow-up git revert. If the app fails its health check after an update, deploy.sh reverts automatically and the panel reports what it reverted to.

Scheduled and automatic updates

Both live in the same panel, both are off by default, and both sit on top of the updater, which is itself off by default.

  • Schedule this update defers one update — to the exact release you were shown — to a time you choose, in a timezone you name. It fires within 2 hours of that time; if Vigilant is down for longer than that, the schedule is abandoned rather than applied at a surprising hour. Only an upgrade can be scheduled (going back is Roll back, with you watching), and a schedule that a newer deploy has overtaken is dropped rather than applied.
  • Automatic updates apply the newest release in a weekly window (day, local time and an IANA timezone, resolved at run time so the window stays put across DST, with its 2 hours measured in real elapsed time). Patch releases only (x.y.Z) unless you untick it; never a prerelease, never anything older than what is running, and never a release that already failed or was rolled back on this install — the panel names it. If the hourly release check has not succeeded for 3 hours, the window waits rather than act on old information. Saving the policy — switching it on, or changing its day, time or timezone — never fires for a window that is already open; the first run is the next one.

deploy.sh's health gate proves the new release is up, not that it works: a release that starts cleanly with a broken page passes it. So every unattended run is reported, and a failed automatic run — including one deploy.sh rolled back — pauses automatic updates until you save the form again, instead of retrying the same bad release every week.

The scheduler never hands the sidecar a request while it is upgrading itself, while any of its self-checks is failing (the same checks that disable the buttons), or while it has stopped responding. It holds that minute and asks again on the next one, within the window, rather than leaving a request queued for a sidecar that may not come back. If a request it did write is still unclaimed after 2 hours, it takes it back and reports the run as skipped, so a sidecar that returns days later does not deploy it on the spot.

How you find out what happened

Every scheduled and automatic run gets one report: succeeded, failed, rolled back, or skipped — a schedule or weekly window whose 2 hours ran out before the update could start (Vigilant was down, or the updater was busy, failing a check or not running). A skip is reported once, never once a minute. No external service is needed to see any of it:

  • the admin audit log gets a row first, before anything is pushed;
  • admins see a banner on every page. A success can be dismissed and goes away on its own after a week; a failure, rollback or skip stays until an admin acknowledges it, and links to Admin › Updates, where a paused policy is resumed. The banner is read from the database, so it is there after the restart the update itself causes;
  • the updater panel lists the last ten runs, with how each push channel fared;
  • an open tab gets a bell notification (type "Vigilant Updates").

To be told without opening Vigilant, add a push channel under Update notifications in the panel. Each one can report every run or problems only (failed, rolled back, skipped), has a Send test notification button, and shows its last delivery. A delivery that fails is recorded on the report and on that line; it never affects the update or the report.

  • Discord uses the existing relay: set DISCORD_WEBHOOK_URL and add auto_update to DISCORD_ALERT_TYPES. That is its own type, separate from update_available, so opting in to "a release is out" notices does not cover it.
  • Webhook takes any http(s) URL, in one of two formats. It is sent with a 5 second timeout, redirects are not followed, and the URL is stored encrypted and never shown back or logged in full.
    • JSON — a POST of:
      {"event": "vigilant.update", "kind": "automatic", "outcome": "failed",
       "from_tag": "v1.3.0", "to_tag": "v1.3.1", "at": "2026-09-13T04:31:02Z",
       "detail": "Automatic update to v1.3.1 failed. ..."}
      kind is scheduled or automatic (test for the test button); outcome is succeeded, failed, reverted or skipped.
    • ntfy — the message as a plain-text body with Title, Priority (high for a problem) and Tags headers. ntfy is free and needs no account: pick a long random topic, subscribe to it in the ntfy app, and paste https://ntfy.sh/<topic> here. The topic name is the only secret — anyone who knows it can read it.

Dev Instance

Run a second, disposable Vigilant beside production to test changes before releasing them:

docker compose -f docker-compose.dev.yml up -d --build
ssh -L 8001:127.0.0.1:8001 <your-host>   # then browse http://localhost:8001

docker-compose.dev.yml is a standalone stack, not an overlay of the main compose file. It binds loopback only, never joins the reverse proxy's network, and runs with BACKGROUND_JOBS_ENABLED=false so it does not duplicate production's ESI/zKB polling. It needs its own .env and its own EVE application — see .env.example, since refresh tokens are bound to the client_id that issued them and production's are useless in dev.

Seed its database from production with scripts/refresh-dev-db.sh. That copies everything except killmails whole, plus a recent slice of killmails and their attackers/items, and blanks all stored OAuth tokens. Production stays up throughout: the copy is index-driven and reopens its read transaction every chunk, and a watchdog pauses the run if the write-ahead log starts growing.


EVE Online Developer Application Setup

  1. Go to developers.eveonline.com
  2. Log in with your EVE Online account
  3. Click "Applications" > "Create New Application"
  4. Fill in the form:
    • Application Name: Vigilant (or any name you like)
    • Description: Personal EVE character dashboard
    • Connection Type: Authentication & API Access
    • Permissions: Select all scopes listed in ESI Scopes below
  5. Set the Callback URL:
    • Local: http://localhost:8000/auth/callback
    • VPS: https://vigilant.yourdomain.com/auth/callback
  6. Create the application and save your Client ID and Client Secret

Configuration Reference

All settings are read from .env:

Variable Default Description
EVE_CLIENT_ID (required) EVE SSO application client ID
EVE_CLIENT_SECRET (required) EVE SSO application client secret
EVE_CALLBACK_URL http://localhost:8000/auth/callback OAuth callback URL — must match your ESI app
SECRET_KEY (auto-generated locally) Session and token encryption key
DATABASE_URL sqlite+aiosqlite:///./vigilant.db Database path (Docker overrides to /data/vigilant.db). Must name an async driver — plain sqlite:// will fail at startup
DEBUG false Enables FastAPI docs at /api/docs and verbose logging
ADMIN_CHARACTER_ID (unset) EVE character id of the owner. Only the account owning this character is auto-promoted to admin — see First-User Admin Bootstrap. Unset means no one is promoted
CONTACT_EMAIL (the project's issue tracker URL) Contact sent in the User-Agent on outbound requests to ESI, zKillboard, and other third-party APIs. These operators require a reachable contact. Self-hosters should set their own address so they can be reached about their own instance's traffic
DISCORD_WEBHOOK (unset) Ops/backfill progress pings. No-op when unset
DISCORD_WEBHOOK_URL (unset) User-facing alert relay (structure attacks, fuel alerts). Deliberately separate from DISCORD_WEBHOOK — don't alias them. No-op when unset
DISCORD_ALERT_TYPES structure_attack,structure_fuel Comma-separated opt-in list gating which alert types the relay sends. Update-related types: update_available (a newer release exists) and auto_update (how each scheduled or automatic update ended — see How you find out what happened; a webhook or ntfy topic for the same reports is set in the updater panel, not here)
WANDERER_URL (unset) Link to your own Wanderer wormhole-mapper instance. Adds a "Wanderer" item to the Map nav group; unset omits the item entirely

ESI Scopes

Vigilant requests the following ESI scopes when authenticating a character:

Click to expand full scope list
Scope Purpose
esi-wallet.read_character_wallet.v1 Wallet balance and journal
esi-location.read_location.v1 Current system location
esi-location.read_ship_type.v1 Current ship type
esi-location.read_online.v1 Character online status
esi-assets.read_assets.v1 Character assets
esi-industry.read_character_jobs.v1 Industry jobs
esi-clones.read_clones.v1 Clone locations
esi-clones.read_implants.v1 Implant data
esi-markets.read_character_orders.v1 Market orders
esi-mail.read_mail.v1 Mail headers and bodies
esi-characters.read_notifications.v1 In-game notifications
esi-contracts.read_character_contracts.v1 Contracts
esi-planets.manage_planets.v1 Planetary interaction data
esi-skills.read_skillqueue.v1 Skill queue
esi-skills.read_skills.v1 Trained skills and attributes
esi-fittings.read_fittings.v1 Saved ship fittings
esi-characters.read_blueprints.v1 Character blueprints
esi-characters.read_corporation_roles.v1 Corporation role check
esi-industry.read_character_mining.v1 Mining ledger
esi-corporations.read_corporation_membership.v1 Corp member list
esi-wallet.read_corporation_wallets.v1 Corp wallet divisions
esi-industry.read_corporation_jobs.v1 Corp industry jobs
esi-markets.read_corporation_orders.v1 Corp market orders
esi-corporations.read_structures.v1 Corp structures
esi-contracts.read_corporation_contracts.v1 Corp contracts
esi-assets.read_corporation_assets.v1 Corp assets
esi-corporations.read_blueprints.v1 Corp blueprints
esi-industry.read_corporation_mining.v1 Corp mining observers
esi-search.search_structures.v1 Structure search — resolving structure names
esi-universe.read_structures.v1 Structure details for assets, orders, and industry jobs
esi-ui.write_waypoint.v1 Set an autopilot waypoint in the EVE client — the only write scope the app requests

Character-level scopes are always requested. Corporation-level scopes are only usable if the character has the required in-game roles (e.g., Director). See ESI documentation for details.

Select every scope in this list. It mirrors EVE_SCOPES in app/auth/routes.py, which is authoritative. Granting a partial set doesn't fail loudly — the affected features just return no data. Structure name resolution and autopilot waypoints are the ones most often missed.


Pages

Route Description
/dashboard Main dashboard with character cards, grouping, wealth, summary panels
/map Interactive star map with overlays, routing, jump planner
/skill-plans Skill plan manager with create/edit/share/gap analysis
/skill-plans/ship/{id} Ship mastery viewer with prereq tree
/intel/gatecheck Route safety checker with gatecamp and war target detection
/intel/dscan D-Scan paste parser with ship categorization
/structure-timers Shared structure timer board with ACL
/industry Manufacturing calculator with nested build/buy
/industry/compression Compression calculator with LP solver
/industry/mining-ledger Unified cross-character/corp mining ledger
/industry/jobs Combined active industry jobs across characters and corps
/tools/fitting Ship fitting builder with Dogma engine and per-character skill scaling
/tools/fitting/saved Saved fittings list with DPS/cost and folder tree
/character/{id} Character detail — wallet, skills, mail, notifications, assets
/character/{id}/journal Full wallet journal with category filtering
/character/{id}/skills Skill remap optimizer and what-if simulator
/character/{id}/fittings Ship fitting viewer with EFT export
/character/{id}/blueprints Blueprint library with ME/TE and filters
/character/{id}/mining Character mining ledger
/assets Cross-character asset search
/corporations/{id} Corp overview — wallet, structures, jobs, orders, members
/corporations/{id}/journal Corp wallet journal (7 divisions)
/corporations/{id}/blueprints Corp blueprint library
/corporations/{id}/mining Corp mining ledger
/status ESI rate limits, sync health, request activity
/admin Admin panel — health, users, SDE, audit log (admin/manager only)

Security

Authentication & Encryption

  • EVE Online SSO — login handled entirely by EVE's official OAuth2. No passwords stored.
  • Token encryption — ESI access and refresh tokens are encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256), with the key derived from SECRET_KEY via PBKDF2-SHA256 (100k iterations).
  • User isolation — every database query is scoped to the authenticated user. One user cannot access another's data.
  • Session security — signed cookies via itsdangerous, HttpOnly, SameSite=Lax, Secure in production, 7-day expiry.
  • State validation — OAuth callbacks validate a CSRF state token to prevent redirect hijacking.

Transport & Docker Hardening

  • HTTPS expected — sample reverse-proxy config (docs/nginx-sample.conf) terminates TLS 1.2/1.3, redirects HTTP→HTTPS, sets HSTS (2 years), and adds X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy.
  • Security headers — recommended set baked into the sample vhost; replicate in your own proxy config if you don't use it.
  • Container hardening — read-only filesystem, no-new-privileges, cap_drop: ALL (with minimal cap_add: [CHOWN, SETUID, SETGID] consumed by the entrypoint to drop to a non-root vigilant uid before uvicorn starts), memory/CPU/PID limits

First-User Admin Bootstrap

Vigilant has no interactive admin provisioning step. Instead, on every app startup the bootstrap routine in app/main.py runs:

If no user has is_admin=true, promote the account that owns the character named by ADMIN_CHARACTER_ID to admin. If that variable is unset, or that character has not logged in yet, promote no one.

Set ADMIN_CHARACTER_ID in .env to your own EVE character id before first launch. Then sign in with that character and restart (the documented weekly auto-update also triggers a restart) — your account becomes admin. Once an admin exists, the routine is a no-op forever.

Because promotion is bound to a specific operator-designated character (proven via EVE SSO), an attacker who registers first on a fresh, public deploy gets an ordinary user account and is never auto-promoted. This closes the earlier lowest-id bootstrap (sec-toolkit finding VVP-2026-013, CWE-269), which handed admin to whoever signed up first.

Fail-closed: if ADMIN_CHARACTER_ID is unset, no account is auto-promoted and the app logs a reminder. Promote your account manually in the users table if you prefer not to set the variable.

Production Checklist

  1. Use HTTPS with a valid TLS certificate
  2. Generate a strong SECRET_KEY and never change it while the database has active users
  3. Set .env permissions: chmod 600 .env
  4. Never commit .env to git
  5. Set DEBUG=false in production
  6. Back up the SQLite database regularly (/data/vigilant.db in Docker)

Data Privacy

  • Your data is stored on your own server — not sent to external servers (except EVE's ESI and zKillboard for public kill data)
  • No telemetry or analytics
  • Fully open source and auditable

Tech Stack

Layer Technology
Backend FastAPI, SQLAlchemy (async), aiosqlite, Uvicorn, scipy (LP solver)
Templates Jinja2, htmx, Tailwind CSS (built at image build), Chart.js
Star Map React, TypeScript, Vite, Pixi.js v8 (WebGL), pixi-viewport, d3-quadtree, Graphology
Data EVE ESI REST API, zKillboard API, EVE SDE (Static Data Export)
Deployment Docker, Docker Compose; bring-your-own reverse proxy (sample nginx vhost in docs/)

FAQ

Is my data safe? Yes. All data is stored on your own machine or server. ESI tokens are encrypted at rest. Use HTTPS and a strong SECRET_KEY in production.

Can I use Vigilant with multiple characters? Yes. Click "Add Character" and authenticate through EVE SSO. You can add as many characters as you want and organize them into account groups.

How often is data refreshed? The background sync runs every 60 seconds and refreshes each field only when its own timer has elapsed: location (1m), wallet and skill queue (2m), contracts (5m), notifications and planetary industry (10m), and assets, market orders, industry jobs, wallet transactions, corp roles, clones and zKillboard data (1h). These are at or above ESI's own cache max-age for each endpoint — see FIELD_CACHE_SECONDS in app/routes/dashboard.py.

Does this violate EVE Online's Terms of Service? No. Vigilant uses only official ESI endpoints. It doesn't automate gameplay or interact with the EVE client.

How do I remove a character? Go to /characters and click the remove button next to the character.

How do I access the admin panel? The account that owns the character set in ADMIN_CHARACTER_ID becomes admin at startup (see "First-User Admin Bootstrap"). Admins can promote other users to Manager or Admin roles from /admin.


Troubleshooting

App won't start:

# Local
tail -30 vigilant.log

# Docker
docker compose logs app

Common causes: Python too old (need 3.12+), port 8000 in use, missing .env values.

ESI data not updating: Check the /status page for sync errors and rate limit usage. If a character's token is expired, re-authenticate from the dashboard.

Star map shows a black screen: After deploying, do a hard refresh (Ctrl+Shift+R / Cmd+Shift+R). Cached HTML can reference stale asset hashes.

"502 Bad Gateway" on VPS: The app container may still be starting. Check docker compose logs app — the first startup can take a minute to initialize the SDE import.

Database corruption ("database disk image malformed"): Delete the database and restart. You'll need to re-authenticate characters:

# Local
rm vigilant.db && ./start.sh

# Docker
docker compose down
docker volume rm vigilant_app_data
docker compose up -d

AI Assistance

Vigilant was built with the help of AI coding assistants. Every change is reviewed by a human before it lands, and the test suite (pytest, plus the design-system suite) runs in CI on every push — see the badge at the top of this file.


Contributing

Contributions are welcome. See CONTRIBUTING.md for how to get a development environment running, how to run the test suite, and the conventions worth knowing before your first change.

Found a security issue? Please don't open a public issue — see SECURITY.md for how to report it privately.


License

Vigilant's source code is released under the MIT License — see LICENSE. You are free to use, modify, and self-host it, commercially or otherwise, without asking permission. It is provided as-is, with no warranty and no obligation of support.

The MIT license covers this project's own code. It does not cover the EVE Online material that Vigilant consumes — the Static Data Export, type names and item data, and EVE artwork — whose use is governed by the game operator's third-party developer terms. See NOTICE.

EVE Online® and Fenris Creations™ and all related logos and other elements are trademarks of Fenris Creations. © 2026 Fenris Creations. All rights reserved. All artwork, screenshots, characters, vehicles, storylines, world facts and other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of Fenris Creations. Fenris Creations does not endorse, and is in no way affiliated with, Vigilant. Fenris Creations is in no way responsible for the content on or functioning of this software, nor can it be liable for any damage arising from its use.

Fenris Creations is the studio formerly known as CCP Games, renamed on 6 May 2026. Older EVE documentation, the ESI API reference, and much of the third-party tooling ecosystem still say "CCP" or "CCP hf." — they refer to the same company.

About

Self-hosted EVE Online companion dashboard — multi-character wallet, skills, industry, intel, and an interactive WebGL star map.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages