ultratears
Unofficial native Windows port of Tears of the Kingdom 1.4.3 using static recompilation. Builds locally from the player's own extracted game dump; no game files included.
翻譯準備中
EveSynapse is a modern, comprehensive, yet lightweight and scalable, Go rewrite of the 2013-era all-in-one EVE online tool EveSynapse originally created by gr1m5/natemsz, element, and j0ker.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗EveSynapse is a lightweight and fast, yet comprehensive and powerful all-in-one EVE Online companion (character sheets, market, fitting, intel, industry, etc) rebuilt in Go on CCP/Fenris Creation's ESI API with EVE SSO login.
The modernization is developed by natemsz (Nate / IGN: Burzrujat), rewriting the original Python-based closed-source app module by module in Go. Not all modules have been finished but development is very active. Built in the hopes it might be useful to the players of this game that holds a special place in my heart and other third party developers for their own projects.
EveSynapse is based on the 2013 project originally developed by natemsz, element, and j0ker (Rest in peace Matt. See you on the other side of the Eve Gate). Without them, the original project would not have been possible and this rewrite would not exist. While they have not contributed to the modernization, their work was critically important to both it's development and to my skills as a developer today.
Special thanks to Sahir and ado for helping me fix my own mistakes and answering questions, and to Marti for putting up with me and my constant requests to test out features.
This project is dedicated to EVE Online, the game and community that I have loved for over two decades. To its pilots and its developers, past, present, and future; and to all those who we have lost over the years o7.
While there is a hosted version (https://evesynapse.app) you are welcome to install this on your local machine or as a corporation/alliance service (see below on how to restrict sign-ups). If you have suggestions, concerns, questions or complaints, feel free to shoot us an email.
Contact: [email protected] (the team), [email protected] (Nate, or in-game 'Burzrujat'), [email protected] (Miko). Bugs, issues and security reports go to [email protected] (see SECURITY.md).
This is a human-built project built for other humans, and a labor of love.
evesynapse; see go.mod)github.com/go-chi/chi/v5) — HTTP routerpgxstoredatabase/sql code over the pgx
stdlib driver; a pgxpool backs the session store)login.eveonline.com)embed.FS — server-rendered pagescmd/evesynapse/ — release entrypoint: thin wiring (config → app →
HTTP server). It does not import the devtools package, so the
release binary contains no dev-login code; DEV_LOGIN=1 has no
effect on it.
cmd/evesynapse-dev/ — dev entrypoint: identical wiring plus the
dev-only routes (/dev-login). Local testing only — never deploy
this binary.
cmd/releasesign/ — the maintainer's tool for release signing:
makes the release key, signs the update manifests in the release
job, checks a signature by hand. Never deployed either
cmd/residue-corpus/ — builds labeled training examples for the
intel residue reader from production exports (the residue calls
and the reports they fed), with labels taken only from verified
outcomes. A maintainer tool; never deployed
cmd/mkicons/ — draws the raster icons from the logo's SVG files in
internal/app/static/: logo.svg (the mark, shown in the page
header), icon.svg (the app icon) and favicon.svg (the simplified
small one). To change the logo, replace those and run
go run ./cmd/mkicons: it rewrites favicon.ico, the four PNG app
icons and the notification badge beside them
internal/app/ — the application: config + .env loader
(core_config.go), EVE SSO auth/sessions/JWT verification (auth.go),
account linking and the sign-up policy (auth_links.go, auth_signup.go),
token refresh and encryption at rest (auth_refresh.go,
auth_tokencrypt.go), cookie, header and cross-site protections
(core_httpsec.go), the application struct and router (core_app.go),
page handlers and view models
(core_pages.go, char_assets.go, char_skills.go, corp_overview.go,
market.go, sync.go, char_pages.go, char_fittings.go,
char_killmails.go, intel.go), the background worker (worker.go
runs the cycle; each *_worker.go file is the fetching behind one
group of pages, and worker_name_harvest.go collects the names a cycle
has to resolve), what pages and the worker both read
(core_snapshots.go, market_book.go), the
SDE static-data importer (), static assets and their
caching (), the health check (), and the
two maintenance modes that need the application (:
and )
It is one Go package, and a file's name starts with the part of the
app it belongs to, so a directory listing groups them. A test file
sits beside the code it tests with the same prefix.
cp .env.example .env # fill in EVE_CLIENT_ID / EVE_CLIENT_SECRET
make run # or: go build -o bin/evesynapse ./cmd/evesynapse && ./bin/evesynapse
The release build needs a PostgreSQL 16 database to point
DATABASE_URL at; for a fresh local database the setup script
below provisions one, or create role+database yourself and put
the URL in .env. A fresh database gets the full schema from
the embedded baseline on first boot.
make run runs the release build (cmd/evesynapse). If you fork
this project, that's the build you get by default — it has no
dev-login route at all. make build produces bin/evesynapse,
make build-arm64 cross-compiles bin/evesynapse-arm64. For local
development there is also a dev build (make build-dev, binary
bin/evesynapse-dev, entrypoint cmd/evesynapse-dev) that
additionally registers /dev-login when started with DEV_LOGIN=1;
see "Dev login" below. Never deploy the dev build.
For local testing there is a simpler option that does all of
the above in one command: from a checkout,
sudo bash deploy/setup.sh --build-dev installs Go and
PostgreSQL when they are missing, provisions the database,
writes a dev .env (with DEV_LOGIN=1), builds the dev
server and runs it. Local testing only.
The app auto-loads ./.env at startup (keys already set in the real
environment win). Then open http://localhost:8080:
/ — home; signed out it shows the public landing page (what the
app does, sign-up, a demo with sample characters to look around
with, and the public tools), signed in it shows your home: a
customizable overview of widgets over every linked character
(the briefing, your pilots at a glance, net worth, market,
industry and more), in the layout the account chooses for itself
/map/ — the map of New Eden: systems and their connections,
jump routes, your characters' locations, and the intel your
channels carry laid over it (requires login)
/character/ — character live state for one character: online
status and login history, location, active ship, jump fatigue,
active implants, home/jump clones with their implants (requires
login; esi-location.*, esi-clones.*, esi-characters.read_fatigue.v1)
/characters/ — every character linked to the account in one
list, with the way into each one's pages (requires login)
/fittings/ — the Ship Workbench: the fitting simulator. Build
a fit on any hull and the dogma engine works out its statistics
(requires login)
/fittings/saved/ — My Loadouts: your saved fittings, from the
game and from the workbench (requires login;
esi-fittings.read_fittings.v1)
/fittings/public/ — the Public Loadouts Browser: fits other
players have published, with ratings
/killmails/ — the 50 most recent kills/losses with KILL/LOSS
badges, final-blow attacker, involved count and an estimated
destroyed+dropped value (requires login;
esi-killmails.read_killmails.v1; details warmed by the worker)
/mail/ — EVE mail: read and search your characters' mail and
compose from the page (sending takes esi-mail.send_mail.v1;
requires login)
/calendar/ — the calendar: upcoming events for your characters
and corporations, and the op calendar (requires login)
/contacts/ — contacts and their standings across your
characters (requires login)
/corporations/ — public corporation overviews for the user's
characters' corporations (name, CEO, alliance, tax, home station;
public ESI, cached per CCP's Expires header)
/corporations/members/ — member roster with, when member
tracking is available, join date, last login, current ship and
location (roster: no role; tracking requires the
role in-game)
Everything comes from the environment (after ./.env fills any
gaps; real environment variables win over the file):
| Variable | Required | Default | Purpose |
|---|---|---|---|
EVE_CLIENT_ID | yes | — | EVE SSO application client ID |
EVE_CLIENT_SECRET | yes | — | EVE SSO application client secret |
EVE_CALLBACK_URL | yes | http://localhost:8080/auth/callback | OAuth2 redirect URI; must match the callback registered at developers.eveonline.com character-for-character |
EVE_SSO_REVOKE_URL | no | CCP's endpoint | Token revocation endpoint (RFC 7009); only changed to point the unlink flow's "revoke at EVE" call somewhere else, for example in tests |
DATABASE_URL | yes | postgres://evesynapse@localhost:5432/evesynapse?sslmode=disable | PostgreSQL connection URL |
ADDR | no | :8080 | HTTP listen address |
TOKEN_ENCRYPTION_KEY | no | — | Encrypts the EVE tokens stored in the database (see "Token encryption") |
TOKEN_KEY_PROVIDER | no | derived | Where the token key comes from: env (the key above; the default when it is set), http (fetched from a key service at startup), oci (unwrapped by OCI KMS at startup), or none (tokens stored unencrypted; the default when no key is set). See "Keeping the key off the machine" |
TOKEN_KEY_WRAPPED, TOKEN_KEY_UNWRAP_URL, TOKEN_KEY_UNWRAP_CREDENTIAL | with http | — | The wrapped token key, the key service's unwrap endpoint, and the credential the app presents to it (http provider) |
TOKEN_KEY_OCI_KEY_ID, TOKEN_KEY_OCI_CRYPTO_ENDPOINT | with oci | — | The OCI Vault key that wrapped the token key (in TOKEN_KEY_WRAPPED) and the vault's cryptographic endpoint (oci provider) |
EVE_CLIENT_SECRET_OCI_SECRET_ID, DISCORD_CLIENT_SECRET_OCI_SECRET_ID, DISCORD_BOT_TOKEN_OCI_SECRET_ID, |
You need a Linux machine (ARM64 or x86-64) and an EVE app registration for sign-in ("EVE SSO flow" below). Then:
Get the program. Easiest is the prebuilt binary from the
project's releases, published on
Forgejo,
GitHub and
GitLab:
evesynapse-arm64 for ARM machines (most ARM cloud instances),
evesynapse-amd64 for Intel/AMD ones. Or build it yourself
("Build from source" below).
Run the setup script from a checkout of this repo:
sudo bash setup.sh
Run on its own like this, it downloads the latest build for
your machine, checks the
release's signature and verifies the build against the checksum
in its signed manifest ("Release signing" below; on a machine
whose OpenSSL is older than 3.0 it can check only the checksum,
and says so), creates the evesynapse user and
/opt/evesynapse, installs and starts
PostgreSQL if it's missing and creates the app's database
(role evesynapse, database evesynapse, with a generated
password written into /opt/evesynapse/.env as DATABASE_URL),
installs the systemd service (ordered after
postgresql.service), and links evesynapse into /usr/bin so you
can run it without typing the full path. It never overwrites
an existing .env (it only appends a DATABASE_URL that
isn't there yet).
It also sets PostgreSQL's memory for the machine it is on. PostgreSQL ships with 128 MB of cache whatever the machine has, so on anything with 2 GB of RAM or more the script raises three settings, sized from the RAM it finds:
| Setting | Set to | Example: 4 GB | 8 GB | 24 GB |
|---|---|---|---|---|
shared_buffers | an eighth of the RAM, at most 2 GB | 475 MB | 975 MB | 2 GB |
effective_cache_size | two thirds of the RAM (a planning hint; it reserves nothing) | 2.5 GB | 5.2 GB | 15.9 GB |
shared_preload_libraries | pg_prewarm, which reloads the cache after a restart |
Updating afterwards is one command — see "Updating" below.
Signing in or linking another character opens a short chooser (/auth/link) instead of asking EVE for every permission at once:
requires closure turns the answers into the features to switch on.A scope is declared required when a user who chose that feature would be surprised to find the capability missing (it is what the feature is); it stays an enhancement only when it is specialist data a user goes looking for (audit logs, flavor records, niche statistics).
Playstyle answers grant the permissions their features cannot work without and the optional ones that light them up, all shown ticked on the reveal; unticking any of them before completing is the player's choice. The server works the scope list out from the declarations (the page sends feature ids, never scopes), and a link only ever asks for a subset of eveScopes, the list the EVE developer portal allows. The account remembers its last choice, so the next character opens on the reveal, preselected. Linking more onto a character asks for what it already granted too, so a grant never narrows. /auth/eve with no choice is the full link, as before. ESI cannot narrow a granted token, so there is no removal flow; a narrower grant is a fresh link at the narrower set.
Out of the box, anyone who can reach the site and sign in with
EVE gets an account, and the worker then keeps every character
they link in sync. To limit an instance to the people it is meant
for, list them in .env (comma-separated EVE IDs; any mix):
EVE_ALLOWED_CHARACTER_IDS=90000001,90000002
EVE_ALLOWED_CORPORATION_IDS=98000001
EVE_ALLOWED_ALLIANCE_IDS=99000001
A new account then needs a character that is listed itself or
whose corporation or alliance is (checked against EVE's public
API at sign-in). Characters in EVE_ADMIN_CHARACTER_IDS are
always allowed. Everyone else is turned away at sign-in with a
message, and no account is created.
The lists decide who may join:
A list that is set but cannot be read (a typo, a stray separator) stops the app at startup rather than silently allowing everyone.
EveSynapse tells you when something happens to your characters:
a skill finishes, new mail, a new in-game calendar event, a new op
planned for one of your corporations, a reminder before an op you
signed up to starts (10 minutes to 2 hours ahead, your choice), a new
killmail, planetary
extractors stopping, an industry job finishing, and watch list
alerts. They show under the bell in the top bar with no setup at
all. Under bell → Settings (/notifications/settings) each kind
can be switched off altogether, or for some of your characters: the
list beside a kind has a tick box per character and can be searched
by name. A character linked later starts with everything on.
Browser push delivers the same notifications while no EveSynapse tab is open, including to a phone with the site installed to its home screen. It is off until the server has a key pair, and each browser has to be switched on by hand. Nothing asks for permission on its own: not opening the site, and not installing it as an app.
The site can be installed to a phone's home screen or a computer's desktop, where it opens in its own window and gets its own notification icon.
android/ is an Android app that opens the site full screen: a
Trusted Web Activity. It has no code of its own, and it runs in the
phone's browser (Chrome, or another that supports it), so sign-ins,
push and everything else behave as they do there. It exists for people
who would rather install an app than "Add to Home screen", and so the
site can be put on Google Play.
The pipeline's android job builds it. Run it by hand from the
project's pipelines page and download evesynapse-android from
the job's artifacts:
.apk (to install directly)
and .aab (for Google Play).To set up the signing key, once, in Git Bash or any shell with OpenSSL
(pick your own password; it is typed twice below). The base64 line's
output goes into the project's CI secrets as
ANDROID_KEYSTORE_B64, and the password as
ANDROID_KEYSTORE_PASSWORD:
MSYS_NO_PATHCONV=1 openssl req -x509 -newkey rsa:2048 -sha256 -days 10000 -nodes -subj "/CN=EveSynapse" -keyout android-key.pem -out android-cert.pem
openssl pkcs12 -export -name evesynapse -inkey android-key.pem -in android-cert.pem -out android.p12
base64 -w0 android.p12
openssl x509 -in android-cert.pem -noout -fingerprint -sha256
Keep android.p12 and its password somewhere safe and out of the
repository: an app can only be updated by a build signed with the same
key. The last command prints the fingerprint. Put it, and the package
name, in the site's .env and restart:
ANDROID_APP_PACKAGE=app.evesynapse.twa
ANDROID_APP_FINGERPRINTS=AB:CD:…
https://<your site>/.well-known/assetlinks.json then names the app,
and the app opens without the address bar. If Google Play signs the
app for you (Play App Signing), add the fingerprint Play Console shows
under "App integrity" as well, separated by a comma.
A site other than evesynapse.app builds its own app: run the workflow with its host and its own package name.
On the server, make a key pair:
evesynapse -push-keys
It prints three lines starting VAPID_PUBLIC_KEY=,
VAPID_PRIVATE_KEY= and VAPID_SUBJECT=. Nothing is saved
anywhere; the lines are only printed.
Paste those three lines into the server's .env
(/opt/evesynapse/.env on an install made by the setup
script), and change VAPID_SUBJECT to an address you can be
reached at, such as mailto:[email protected].
Restart:
sudo systemctl restart evesynapse
Keep the private key secret, and do not replace the pair later: every browser that switched push on would have to do it again. The site has to be served over https (see "HTTPS").
On an iPhone or iPad, first add EveSynapse to the Home Screen (Share → Add to Home Screen) and do these steps in the app that opens from there; Safari does not offer push to an ordinary tab.
| What you see | What it means |
|---|---|
| "Browser notifications are not set up on this server." | The server did not find a usable key pair. Check the three lines are in the .env the service reads and that it was restarted. A pair that is set but wrong is reported in the log as browser push is off |
| "This browser cannot receive push notifications." | The browser has no push support. On iOS, open the installed app, not a Safari tab |
| "…the address is not a known browser push service (it is at …)" | The browser subscribed with a push service this server does not send to. The server only sends to the services browsers are known to use (Google's, Mozilla's, Microsoft's and Apple's), so that nobody can make it send requests to an address of their choosing. The message names the host: report it, with the browser, so it can be added |
| "Notifications from this site are blocked in this browser." | Permission was refused earlier. Allow notifications for the site in the browser's site settings, then reload the page |
| "The test could not be sent: no browser took the test message" | The push service refused the message. The reason is in the server log (journalctl -u evesynapse), on a line starting push: |
| The test says sent, but nothing appears | The operating system is hiding it: check its notification settings for the browser, and Do Not Disturb / Focus |
Optional. EveSynapse can connect a user's Discord account to their EveSynapse account, send their notifications to them on Discord, and, through one bot that can be added to any number of servers, give roles there and post new ops in a channel. It only ever sends to Discord; it reads nothing from any channel.
What the bot does in a server is not part of this install's settings. It is set on the site, per server, by the directors of the corporation or alliance the server belongs to.
At https://discord.com/developers/applications, create an application. If Discord asks what it is for, "Build a Bot" is the one; the Social SDK is not used.
On General Information, copy the Application ID. That is the client id. (The Public Key on that page is not used.)
Under OAuth2, press Reset Secret and copy the Client
Secret, and add this redirect, with your site's address:
https://your.site/discord/callback
Under Bot, press Reset Token and copy the token. Switch on the Server Members Intent if any server is to use removals (below): it is what lets the bot read a server's member list. No other privileged intent is needed. Leave Public Bot on if other corporations and alliances are to add it to their servers.
Add to the server's .env (/opt/evesynapse/.env) and restart
(sudo systemctl restart evesynapse):
DISCORD_CLIENT_ID=<the Application ID>
DISCORD_CLIENT_SECRET=...
DISCORD_BOT_TOKEN=...
Treat the secret and the token like passwords. With only the first two set, "Connect Discord" works and there is no bot.
Who may: an account with a character that holds the Director role in the corporation (a CEO does), for that corporation's servers; and a Director of the alliance's executor corporation, for the alliance's. That character's link to EVE has to be working.
On the site: bell → Settings → Discord → Set up your corporation's or alliance's Discord server, then Add the bot to a server.
Discord asks which server and shows what the bot may do there: give roles, send messages, add members, and kick members (used only if removals are switched on for that server). Discord only lets someone who can manage that server add a bot to it.
In that Discord server's Roles list, drag the bot's own role above the roles it is to give. Discord lets a bot manage only the roles below its own.
Back on the site, write that server's role rules: "give these people this role", as many as there are roles to give. A member gets every role they qualify for. "These people" can be:
Then, if wanted, pick the channel new ops are posted in. Roles and channels are picked from that server's own.
Adding members automatically. Someone with a character in the corporation (or alliance) who has connected Discord is put in its server by the bot, with their roles, without needing an invite: on the worker's next pass after they qualify, whether that came from connecting Discord, linking the character, the character joining the corporation, or the server being set up. Only people who belong are added; a rule that gives a role to "everyone connected" adds nobody. Each person agrees to it on Discord when they connect ("join servers for you"). Untick the box on the server's settings to switch it off. A bot that was added before this existed lacks the permission: add it to the server again from the site (its settings are kept).
Removals (off unless a server's directors switch it on). With it on, the bot removes from the server everyone who is owed no role by the server's rules: someone whose character left, whose account or Discord connection went, and also anyone who never connected EveSynapse. People are kicked, not banned, a few at a time, and can come back with an invite. Never removed: bots, the server's owner, anyone in their first 24 hours in the server, and holders of roles the directors tick as protected (guests, diplomats). A server with no rules removes nobody, and removals cannot be switched on for one. Press Show who would be removed first: it lists them and changes nothing. Each removal is written to EveSynapse's log and to the server's own audit log.
Groups (/groups/ on the site) are lists of characters that a
corporation's or alliance's directors keep by hand: a special interest
group, the fleet commanders, a logistics wing. A director makes a
group, ticks the characters in it, and adds a rule giving the group a
role; the bot does the rest. Only characters linked to EveSynapse that
are in the corporation (or the alliance) can be put in one. A member
who leaves the corporation stays on the list but loses the role until
they are back. Deleting a group removes the rules that used it.
Which channels a role can open is set on the same page, under Channels: pick a channel (or a category, or a voice channel) and tick the roles that may open it. The bot then keeps it private to those roles: it lets itself in, lets each ticked role in, and hides the channel from everyone else. Saving a channel again replaces its list. Saving it with nothing ticked stops the bot managing it; the channel stays hidden until someone opens it in Discord. The bot only touches channels listed there, and it has to be able to see a channel to change it. With the role rules this is the whole chain: what someone is in EVE gives them a role, and the role opens the channels.
Ops in an alliance's server. A corporation's ops are posted in its own servers. They are posted in its alliance's server only after a Director of that corporation ticks the box for it on the same page.
Removing a server first takes back the roles the bot gave there; press Remove again a few minutes later to finish, and the bot leaves.
On bell → Settings, under Discord: Connect Discord and approve on Discord's page, which asks for two things: to see their Discord name, and to add them to servers. Within a few minutes they are in their corporation's server and have their roles in every server that uses EveSynapse; Check my roles now does it at once, for someone who has just joined a server. To get notifications there, tick "Send my notifications to me on Discord" and press Send a test message; direct messages only arrive for someone who shares a server with the bot and allows messages from its members. Disconnect Discord removes the link and takes back the roles EveSynapse gave.
A role is given to a Discord account when the EveSynapse account connected to it has a character, with a link to EVE that still works, for which the last sync saw what the rule asks: in the corporation, its CEO, holding the in-game role, in the group. It is taken away when that stops being true, and each role on its own: losing Director takes the directors' role and leaves the members' one.
TOKEN_ENCRYPTION_KEY like EVE tokens. That token can
read their Discord name and add them to servers the bot is in, and
nothing else. Someone who connected before this, or who removes
EveSynapse under Discord's Authorized Apps, is asked on the settings
page to connect again.EveSynapse itself speaks plain HTTP. On anything but your own machine, put a reverse proxy that terminates TLS in front of it, so sign-in cookies and character data never cross the network unencrypted. With Caddy, which obtains and renews the certificate itself, the whole configuration is:
eve.example.org {
reverse_proxy 127.0.0.1:8080
}
Then, in /opt/evesynapse/.env:
ADDR=127.0.0.1:8080 # only the proxy can reach the app
EVE_CALLBACK_URL=https://eve.example.org/auth/callback # and the same URL at developers.eveonline.com
The app reads its public address from EVE_CALLBACK_URL. When
that is an https:// address it marks the session cookie
Secure and sends Strict-Transport-Security; when it is plain
http:// on anything other than localhost it logs a warning at
startup.
Every response also carries X-Content-Type-Options,
X-Frame-Options, Referrer-Policy and a
Content-Security-Policy, and state-changing requests coming
from another site are refused.
Needs Go (the version in go.mod). The fonts and wallpaper
travel through the repo in encoded form (under ci-assets/), so
decode them into place first, then build:
make assets # decode the fonts and wallpaper
make build # bin/evesynapse for this machine
make build-arm64 # bin/evesynapse-arm64
make build-amd64 # bin/evesynapse-amd64
make test # the test suite (starts its own embedded PostgreSQL)
make check # what CI checks: gofmt, go vet, tests under the race detector
Every optional feature (the build planner, the fitting tool, the market pages, the corporation pages, Discord, the shared cache, ...) is a module, and a build contains exactly the modules internal/app/modules_enabled.go imports. The default file lists them all. To build a smaller server, let the build tool write that file for your selection; whatever a chosen module requires comes along, and the tool prints the ESI scopes the selection will ask players for:
make modules # every module, what it requires and the scopes it declares
go run ./cmd/modbuild -modules fitting,doctrines # print the file and the scopes; nothing is written
go run ./cmd/modbuild -modules fitting,doctrines -write
go build -o bin/evesynapse ./cmd/evesynapse
go run ./cmd/modbuild -all -write # back to the default, everything
-core-only is the empty selection (go build -tags core_only compiles none regardless of the file). CI checks that the default file is the all-modules selection and that each module builds on its own with what it requires.
Install your build with the setup script's --build mode,
which does the build above and the install in one go:
sudo bash deploy/setup.sh --build
Or install a build you already made:
sudo EVESYNAPSE_BINARY=$PWD/bin/evesynapse bash deploy/setup.sh
Builds are published automatically: every push to main runs the
test suite in CI and, when internal/app/version.txt names a
version that has no release yet, publishes the release on the
project's Forgejo, GitHub and GitLab with builds for ARM64 and
AMD64 plus, for each build, a small manifest (the version and
the build's SHA-256 checksum) and that manifest's signature.
Each published release is then mirrored to the project's static
mirror (a Cloudflare R2 bucket served from
https://cdn.synap6.io): the version's files under
evesynapse/v<version>/, the channel's binaries under latest/
(or latest-dev/), and the channel's signed pointer manifests
last; the mirror job needs the R2_ACCOUNT_ID,
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY and R2_BUCKET
CI/CD variables, and skips without them.
That mirror is also the archive of versions: every published
version's files are kept under
https://cdn.synap6.io/evesynapse/, one folder per version
(evesynapse/v<version>/, for example
evesynapse/v0.7.2.1/).
The updater reads releases from the sources
EVESYNAPSE_UPDATE_PROVIDER lists, and checks them in the
order listed: no source is preferred beyond that order. The
newest signed release across the sources wins, a tie goes to
the earlier-listed source, and the manifest, its signature and
the build all come from that one source's release, verified by
the same signatures and checksums whichever source serves
them. An update run reads out in the package manager's idiom:
each source it checks, at its endpoint, with the version it
found there, the signing key's signer and fingerprint once
the signature verifies, then the download's total size, the
Get line naming the source it downloads from, and progress as
it downloads:
github https://api.github.com/repos/natemsz/evesynapse v0.7.0.9
gitlab https://gitlab.com/api/v4/projects/natemsz%2Fevesynapse v0.7.0.9
forgejo https://natems.dev/api/v1/repos/EveSynapse/evesynapse no usable release
mirror https://cdn.synap6.io v0.7.0.9
A new version is available: v0.7.0.9.
Signature verified: [email protected] / natemsz, SHA256:KkV2PM3ej2aUVOkjfuCTzhibjMcydkO886VchqyxE+k
Total download size: 54.0 MiB
Get: evesynapse-amd64 from github [54.0 MiB]
42% [evesynapse-amd64 22.7/54.0 MiB] 6.8 MB/s
Download verified against its checksum.
With the setting unset, the updater walks a built-in fallback list (github, gitlab, forgejo) and asks the mirror only when no forge produced a signed release.
A release is one edit: internal/app/version.txt. Nothing else in
the repository repeats the version, and the tests read it from that
file, so there are no version numbers in tests to keep in step.
Versions are four plain numbers with no leading zeros,
stable.major.feature.fix: 0.4.1.4, then 0.4.1.5 for a fix or a
small addition, 0.4.2.0 for a feature, 0.5.0.0 for a major
version. The first number stays 0 until there is a feature-complete
stable release. A development build adds a suffix (0.4.2.0-dev).
Releases up to 0.4.01.003 padded the last two parts with zeros. The
updater reads every part as a number and compares part by part, so
the padding makes no difference to it: 0.4.01.003 is 0.4.1.3, and
installs on it update to 0.4.1.4. A test fails if version.txt is
ever set to something those installs would take for an older version.
To update a running install, run the updater. With no flag it automatically picks the build that matches the machine it's running on:
sudo /opt/evesynapse/evesynapse -update # right build for this machine
sudo /opt/evesynapse/evesynapse -update -arm64 # ARM build explicitly
sudo /opt/evesynapse/evesynapse -update -amd64 # Intel/AMD build (-x86 and -x64 also work)
The updater first checks the latest release's signature, and refuses a release that is not signed with the project's release key ("Release signing" below). Then it asks the release what version it carries and compares it with its own. If they're the same, it just says so and stops. If the release is newer, it downloads that build, verifies it against the checksum in the signed manifest and checks it's built for the right kind of computer, swaps it into place, restarts the running server onto it, and reports the new version number.
There's also a manual form that installs a specific build: from an address, with the checksum published for it, or from a local file:
sudo /opt/evesynapse/evesynapse -update <url> <sha256>
sudo /opt/evesynapse/evesynapse -update <file> [sha256]
The manual form checks what you give it and no signature: it is your own word for that build.
Builds from the evesynapse-dev branch carry a version ending in
-dev and are published as prereleases, which a plain -update
never installs. To follow them on a test box:
sudo /opt/evesynapse/evesynapse -update -dev
A plain -update from a development build goes back to the
latest release once that release's number catches up.
Every release is signed, and the updater installs nothing that is not.
latest-<arch>.json:
the version, the kind of computer, and the build's SHA-256. Its
signature is published beside it as latest-<arch>.json.sig.
Because the checksum is inside what is signed, the signature
covers every byte of the build.RELEASE_SIGNING_KEY CI secret, which the
release job signs with. Its public half is
internal/releasesig/trusted_keys.pem, compiled into every build.To check a release by hand, with nothing but OpenSSL 3:
base64 -d latest-arm64.json.sig > sig.bin
openssl pkeyutl -verify -pubin -inkey internal/releasesig/trusted_keys.pem \
-rawin -in latest-arm64.json -sigfile sig.bin
sha256sum evesynapse-arm64 # must be the "sha256" in latest-arm64.json
(go run ./cmd/releasesign verify latest-arm64.json makes the first
check exactly as the updater does. OpenSSL reads only the first key
in trusted_keys.pem.)
Making the key. Once, from the repository root:
go run ./cmd/releasesign keygen -out ~/evesynapse-release-key.pem
It makes the key pair, writes the private half to that file (the
key is never printed), and only when that has worked adds the
public half to trusted_keys.pem. Commit that file. The variable
store masks single-line values only, so the key goes in encoded:
base64 -w0 ~/evesynapse-release-key.pem
Paste that one line into the project's CI secrets (on the
mainline's Forgejo: the repository's Settings > Actions >
Secrets) as RELEASE_SIGNING_KEY, and keep the file somewhere
offline. releasesign reads the key
in either form. That copy is one more place the key
can leak from, and without one, losing the variable means the
manual update described under "If the key is lost".
The release job fails, and publishes nothing, when the variable
is missing or is not the other half of a key in trusted_keys.pem.
Replacing the key. An install only trusts the keys of the build it is running, so the new key has to reach installs in a release signed with the old one:
go run ./cmd/releasesign keygen -add -out ~/new-release-key.pem
adds the new public key beside the old, and writes the new private
half outside the repository. Commit trusted_keys.pem and publish
a release. It is still signed with the old key, and its updater
trusts both.RELEASE_SIGNING_KEY
in the project's CI/CD variables, then delete that file or move
it somewhere offline.trusted_keys.pem.An install that skipped the release from step 1 needs the manual update below. If you kept a copy of the old private half you can avoid even that: put both keys in the variable for a while (one PEM block after the other) and every release carries both signatures, which an install knowing either key accepts. If the private half may have leaked, replace it the same way without waiting.
If the key is lost (the variable deleted, and no copy kept), make
a new one (delete the old block from trusted_keys.pem, then
keygen as above) and publish a release. Existing installs cannot
verify it, because the only key they trust is the lost one. Each
needs one manual update, with the address and checksum from the
release page, and updates normally from then on:
sudo evesynapse -update https://gitlab.com/api/v4/projects/natemsz%2Fevesynapse/packages/generic/evesynapse/v<version>/evesynapse-arm64 <sha256>
By default the updater checks the mainline project's releases.
To have it check your fork instead, set this in
/opt/evesynapse/.env (the setup script writes it for you when
you install from a fork):
EVESYNAPSE_UPDATE_PROJECT=yourname/evesynapse
For your fork to publish releases the same way, the pipelines
are already in the repo: the mainline publishes through the
Forgejo release workflow (.forgejo/workflows/release.yml), and
.gitlab-ci.yml carries the same release contract for a
GitLab-hosted fork: your pushes get tested, built, and released
the same way, with the fork's own RELEASE_SIGNING_KEY variable
(see "Release signing").
A fork signs its releases with its own key. In the fork, delete the
mainline key's block from internal/releasesig/trusted_keys.pem,
run the keygen command under "Release signing", and commit the
file; until the fork has a key its release job fails rather than
publish unsigned builds. It follows that the setting above only
moves an install that is already running a build of the fork: a
mainline build refuses the fork's releases, since they are not
signed with the key it trusts. Install the fork's build first (its
setup script does that), and it updates from the fork from then on.
evesynapse run the web app
evesynapse -version print the version and exit
evesynapse -update update to the latest release (right build for this machine)
evesynapse -update -arm64 update, fetching the ARM build
evesynapse -update -amd64 update, fetching the Intel/AMD build (-x86, -x64 also work)
evesynapse -update -dev update to the newest development build
evesynapse -update <url> <sha256> install a specific build from an address (checksum required)
evesynapse -update <file> [sha256] install a build from a local file
evesynapse -refresh mark all cached data stale (run while the app is stopped)
evesynapse -push-keys print a new key pair for browser push, as lines for .env
evesynapse -h show this list
Register the app at https://developers.eveonline.com with the
callback URL from EVE_CALLBACK_URL, and put the issued client ID
and secret in .env.
Login: GET /auth/eve stores a random state in the session
and redirects to login.eveonline.com requesting the scopes the
chosen features need (see "Choosing what to link"; a full link
requests all 61 scopes in eveScopes, in
internal/app/auth.go): the read scopes
from the OAuth2 catalog in CCP's ESI OpenAPI document, plus four
that are not read-only, each for one feature:
| Scope | What the app does with it |
|---|---|
esi-fittings.write_fittings.v1 | "Save to EVE" in the fitting editor |
esi-mail.send_mail.v1 | sending mail from the compose page |
esi-mail.organize_mail.v1 | marking a mail as read |
esi-planets.manage_planets.v1 | reading colonies; CCP publishes no read scope for planetary industry, and the app only ever reads |
Everything else that can change something in the game (contacts, fleets, calendar responses, waypoints, opening in-game windows) is not requested. Characters linked before a scope was added keep the scopes they granted until they sign in again; the feature that needs the missing scope says so.
Callback: GET /auth/callback verifies the state
(constant-time, single-use), exchanges the authorization code for
tokens, then verifies the access-token JWT: RS256 signature against
CCP's JWKS (found via the OAuth discovery document), iss =
login.eveonline.com. Character ID/name come from the sub/name
claims.
Persist: first login creates a row in users; the character is
upserted into characters with the access/refresh tokens, expiry,
and granted scopes. Re-login (or "Link another character" while
signed in) attaches characters to the same account. The session
token is rotated at sign-in.
Character pull: the signed-in home page shows the character sheet — identity (name, portrait, corporation, birthday, security status), ISK wallet balance, total/unallocated SP, the currently training skill, and the 25 heaviest skills with names resolved via (cached in-process and in the table).
Tokens, authorization codes and the client secret are never logged; request logs contain paths only, no query strings.
EVE SSO access tokens live ~20 minutes. validAccessToken
(internal/app/auth_refresh.go) returns the stored token while it has more
seconds left; otherwise it refreshes against CCP and persists the new
access token, the rotated refresh token, and the new expiry.
Refreshes are serialized process-wide and the character row is
re-read first, so a rotated refresh token is never replayed.
ESI responses for skills, skill queue, wallet and assets are cached as raw
JSON in character_snapshots, keyed by
(character, kind) with the response's Expires header stored as
cached_until (5-minute fallback when ESI sends none). The module
sweep caches the character live-state endpoints the same way
(location, ship, online, clones, implants, fittings, fatigue and the
recent-killmails list). Killmail details are different: they are
immutable, so the worker warms them once per killmail into the
killmail_details store (at most 10
per character per cycle) and pages read the store only. Corporation
datasets (module sweep cluster 2) ride the
same snapshot table as corp_* kinds, fetched with each viewing
character's token against that character's corporation — so pages
follow the selected character's corp, and character/corporation ID
collisions can never cross-contaminate. Role-gated corporation
endpoints answer 403 when the character lacks the in-game role;
the worker records that in snapshot_fetch_state (state
role_missing plus the role label), backs the kind off for six
hours instead of retrying every minute, and never writes a
snapshot for a refused kind — the subpages and the Sync page
render the recorded state ("Needs the Director role in-game")
until a fetch succeeds. Corp asset singleton names come from
POST /corporations/{id}/assets/names/ (bounded to 1,000 items
per cycle, stored in item_names; CCP's "None" placeholder for
unnamed items is skipped). Assets are
paginated: every page is fetched and stored as one merged JSON array. Pages serve
fresh snapshots without calling ESI; on fetch failure a stale
snapshot is served instead of an error. ESI's error-limit statuses
(420/429) are treated as a hard back-off signal.
Refreshes are conditional where they can be. A dataset that comes
in one response is stored with the ETag ESI sent, and the next
refresh offers it back (If-None-Match). When nothing has changed
ESI answers 304 Not Modified with no body: the stored payload
stays as it is and only its cache window is renewed. Datasets
spread over several pages (assets, contracts, blueprints, …) have
an ETag per page and none for the whole, so they are downloaded in
full each time. evesynapse -refresh clears the stored ETags, so
everything really is downloaded again.
Name resolution is split in two tiers. Page renders resolve type,
group and place names from local data only — the in-process maps,
the SDE static-data tables (below), and the type_names fallback
table — a render never waits on ESI; anything still missing shows
as Type #<id> (or an "Ungrouped" skill section) until the
worker's warm-up pass fills it in. The interactive Market lookup
is the one exception: it may make a single name fetch for an item
nobody has cached yet.
The Intel cluster (module sweep cluster 4) is public ESI data —
wars, incursions, faction
warfare and Tranquility status — so it doesn't ride the
per-character snapshot table. The worker keeps it in a small
global store instead: global_snapshots (one raw payload per
dataset, same Expires/cached_until contract) plus a
war_details store for GET /wars/{war_id}/ payloads, warmed
newest-first at most 50 per cycle and refreshed while a war is
active (finished wars are immutable). War-party corporation and
alliance names and incursion constellation names warm into
in-process caches through the worker cycle's shared lookup
budget; faction names come from the stored factions list itself.
The Intel pages and the Home page's Tranquility line render from
the store only — and the worker pass runs even with no characters
linked, since none of it needs a token.
Item, skill, group, station and system names come primarily from a
local copy of CCP's static data export (the SDE), stored in the
sde_* tables: types,
groups, categories, NPC stations, solar systems and regions. The
ESI drip-feed caches remain only as fallback for anything the SDE
lacks — notably player-structure names, which aren't in the dump.
https://www.fuzzwork.co.uk/dump/latest/csv/ (plain .csv;
.csv.bz2 names and bzip2 content are also handled). Override
with EVE_SDE_BASE_URL to use a mirror.Every 60 seconds the worker walks all linked characters: it ensures
each access token is usable (refreshing when needed) and re-fetches
any snapshot whose cached_until has passed — a first pass runs at
boot. It also warms killmail details behind each character's recent
list and contract item lists behind the contracts list (both
bounded per cycle) so those pages never wait on ESI. The same
cycle refreshes the public Intel store (server status, war list
and details, incursions, faction-warfare systems/stats, factions)
and the public names behind the Intel pages, spending from the
same per-cycle lookup budget.
It then warms the name caches from the fresh snapshots (type
names and type→group links, group names, station/system names, and
character names for killmail victims and final-blow attackers) with
a small concurrent pool, capped per cycle; a huge account simply
converges over a few cycles. Characters are warmed first when they
were just linked via SSO or flagged on the Sync page. On a 420/429
the cycle stops and waits for the next tick. Logs stay quiet: one
summary line per cycle only when something was refreshed or failed,
plus a heartbeat every 10 minutes. The current status (last run,
summary, cumulative names resolved) shows on the Admin and Sync
pages. When a Trade ideas model is configured (a MODEL_API_KEY, or
the Cloudflare credentials as the fallback), the same worker
also refreshes the Trade ideas page (Market > Trade ideas): it feeds
the spread scanner's Forge rows (at most 60, same columns as the
scanner page) to the model and
stores the answer, at most twice a day and spending no ESI budget.
The call runs beside the worker cycle, not in it; a failed refresh
keeps the previous answer and backs off before asking again.
Self-hosted instances can share public ESI data with each other — market history, type names, corporation and alliance records, and player structure names — so the whole swarm asks ESI for each record once instead of once per instance. Nothing private ever leaves the instance: no character data, no tokens, no accounts. The design is section 3 of How EveSynapse syncs.
It is off unless SHARED_CACHE says open (or on) or
allowlist (see "Configuration"). open shares with every
instance holding the secret; allowlist additionally waits for
each instance to be approved on the Admin page. New instances find
the swarm through the SHARED_CACHE_SEEDS directory nodes,
then membership spreads peer to peer about once a day
(SHARED_CACHE_GOSSIP_HOURS). Every round ends with one
heartbeat per configured seed, so the directory keeps a fresh
record of each instance; every instance also accepts those
heartbeats itself (POST /swarm/seed/register), so any
instance works as a seed for the rest.
Fetch capacity is spent swarm-first. A dedicated warming node
(SHARED_CACHE_WARM) says which kinds it warms
(GET /swarm/warms) and takes the pull requests it cannot answer
as demand: the key joins its own queue and its worker fetches it
on its own ESI budget. An instance that a dedicated node covers
leaves a miss of that kind to the swarm for 15 minutes before
spending its own call, so its budget is the last resort; a kind
no dedicated node covers is fetched as before. Pages never wait
on any of this: a miss renders its not-yet-known state and fills
in when the record lands.
Trust is cryptographic and reputational. Every shared entry is signed by the instance that fetched it (Ed25519); the signature and shape are verified before anything is stored. Verification against ESI is sampled, not exhaustive: new signers are checked every time, then the check rate falls with trust (half at half trust, five percent near the top), drawn unpredictably per entry. A failed check counts against its signer with weight that doubles per consecutive failure, and five in a row excludes on velocity alone; old bad marks fade a little every gossip round, and passing checks rebuild trust, so a fixed peer earns its way back and the operator's re-admit is the way home. Ten checks under half trust excludes the signer on its own. Every applied entry records the peer that served it, the instance that signed it and the signed fetch time; when a signer is excluded, the worker takes back what it supplied that this instance has not fetched for itself since, and fetches it from ESI instead. The urgent drain asks peers before spending ESI, and a key with a fresh staged entry skips its refetch while the spot decision lands. The operator can always add an instance by address, approve, exclude, re-admit, or remove one on the Admin page. The first key seen for an instance is the one kept: a later sighting under another key is refused and marked on that instance's row, and an address moves only when the instance itself says so under its pinned key. Removing an instance is the only way its key changes (the next sighting pins the new one). Every member signs its own address with its own key and the time it signed; that tuple travels through every member list, relayed unchanged, so an address is always its owner's word, and of two tuples the later-issued wins. A seed is a member like any other, with its own instance ID and key, and there can be several. Only peers a pull has lately staged data from are asked for data; any other peer gets one probe, then is dialed for membership alone until a gossip round lets it be probed again. A seed serves its member list — itself plus every peer it knows — so one address teaches a new instance the whole swarm. The Sync page's "Swarm Cache" card shows entries applied, ESI calls saved, spot checks for and against, and each instance's trust.
The member list itself is private to the swarm. Fetching it
needs the shared secret, and the list is sealed under a key
derived from that secret on top — a response that leaks
anywhere else stays opaque, readable by members alone. Pages
show gossip-learned members under stable aliases
(adjective.noun.number) instead of their addresses; only
instances added by hand and the configured seeds show plainly.
Every log line carries a level:
LOG_LEVEL sets the least severe kind that is written. The
default, info, writes everything, as before; warn leaves only
the lines that may need attention. LOG_FORMAT=json writes one
JSON object per line (time, level, msg) for a log collector;
the default is the plain line:
2026/10/07 12:00:00 WARN worker: ESI error limit hit refreshing intel; backing off until next cycle
Under systemd, journalctl -u evesynapse -g ' (WARN|ERROR) ' shows
only those lines without changing what is logged.
EveSynapse is built to stay lightweight, lean, fast, and efficient at scale — without compromising features, power, or security. Two rules govern how data moves:
Data changes on a known schedule (market sweeps complete every 30 minutes; character snapshots refresh on their own cadence), so a page that re-derives everything per load is doing repeat work for an identical answer. Under one user that is a CPU spike; under a hundred it is an outage. When in doubt, push the work into the worker and store the result.
The UI echoes the 2013 EveSynapse theme — the original planet/nebula
wallpaper, its teal-blue accents (#326b8c) changed to the molten ember
of EVE's Dominion expansion (2009), a year that holds special meaning
to us as it is the year our characters were born, and translucent dark
nav and header panels over the art — rebuilt with a
single dependency-free stylesheet (internal/app/static/style.css): no Bootstrap,
no jQuery, it's not 2013 anymore :), no external fonts, responsive down to phone widths.
The dev build (cmd/evesynapse-dev, make build-dev) registers
/dev-login when started with DEV_LOGIN=1: it flips the session to
signed-in without EVE SSO so the signed-in pages can be exercised
locally (it is never an admin: the dev session has no account). The
handler lives in internal/devtools and is only wired into the dev
entrypoint — the release binary (cmd/evesynapse) doesn't contain
the code, and DEV_LOGIN=1 has no effect on it. /dev-login hands
a session to anyone who asks: never enable it on a deployment
anyone else can reach, and never deploy the dev build. The dev
server logs a loud warning at boot when it is on.
The app runs on PostgreSQL 16 (the live deployment is 16.15;
tests self-provision an embedded Postgres 16, so CI needs no
database service). Every query and the hand-rolled importer ride
a database/sql handle over the pgx/v5 stdlib driver; a pgxpool
exists only to back the scs pgxstore session store (sessions
live in the sessions table the baseline creates).
The schema is a series of numbered steps in
internal/store/schema_pg/: 001_baseline.sql is the whole schema
as of the move to Postgres (BIGINT/DOUBLE PRECISION keep the
generated Go models' int64/float64 types), and each later file is
one change. At startup store.Open applies whichever steps a database
is missing, in order. Each step runs in a single transaction
together with its row in the schema_migrations table, so a step
lands completely or not at all, and that table is the record of
what has been applied. A database from before the table existed is
adopted on first start: its existing steps are recorded, not run
again.
Times are timestamptz columns and time.Time (or sql.NullTime
where "never" is a possible answer) in the code; a connection
always hands them back in UTC, whatever zone the machine is in.
The baseline kept them as RFC 3339 text, and steps 009–011 convert
those columns, carrying every stored value over as the same
instant. Those three steps rewrite the tables they touch, so the
first start after upgrading past them takes as long as copying
those tables once; the snapshot table is by far the largest.
To change the schema, add the next numbered file, then embed it and
add one line to schemaSteps, both in internal/store/schema.go.
Regenerate query code after editing internal/db/query/ with:
make gen # sqlc generate
Each linked character's EVE access and refresh tokens are stored
in the characters table. A refresh token is a standing
credential, so anyone holding a copy of the database could read
that character's data and use the write scopes the app requests
(sending mail, saving fittings).
Set TOKEN_ENCRYPTION_KEY to any random value of at least 32
characters and both tokens are stored encrypted (AES-256-GCM),
each bound to its character and column:
openssl rand -hex 32 # put the output in .env as TOKEN_ENCRYPTION_KEY=...
The setup script generates a key for new installs. add the line and restart: tokens already stored are encrypted in place at that start.
Two things to know before switching it on:
.env, not in the database, so
back the two up together. If the key is changed or removed the
app refuses to start rather than run with tokens it cannot
read; the error explains how to clear the stored tokens if the
key is truly lost, after which every character signs in once
more.enc:v1:, a single SHA-256 of the key) still open, and
the first start of this build re-seals them as enc:v2: (a
proper key derivation, HKDF). A build from before that change
cannot read enc:v2: values, so take a database backup
before upgrading if you might roll back.Without a key, tokens are stored unencrypted as before and the app logs a warning at startup.
The key in .env protects the tokens against a stolen copy of
the database — but it sits on the same machine, so a copy of the
machine's files (database and .env together) opens everything.
For any instance reachable from the web, the stronger arrangement
is a key service: the key is wrapped by a KMS, and the app fetches
it, unwrapped, at startup and keeps it in memory only. Set:
TOKEN_KEY_PROVIDER=http
TOKEN_KEY_WRAPPED=... # the key, wrapped by the key service
TOKEN_KEY_UNWRAP_URL=... # its unwrap endpoint
TOKEN_KEY_UNWRAP_CREDENTIAL=... # the credential the app presents
TOKEN_KEY_PROVIDER is env (the TOKEN_ENCRYPTION_KEY above;
the default when it is set), http, oci, or none (no key). Any HTTPS
endpoint speaking the two-call protocol — POST {"wrapped": …} →
{"secret": …} to unwrap, POST {"secret": …} → {"wrapped": …}
to wrap — can serve, so nothing ties this to one vendor's KMS. A
complete reference service ships as a Cloudflare Worker you deploy
to your own account, free, in a few minutes:
natemsz/cloudflare-kms.
On Oracle Cloud there is a second shape, TOKEN_KEY_PROVIDER=oci:
the key is wrapped by a master key in an OCI Vault — create it
with HSM protection, so the key cannot be exported — and the app
unwraps it at startup with a KMS Decrypt call authenticated by
the instance's own identity (an instance principal). Nothing
cloud-shaped is stored on the machine at all: no credential, no
key, only the ciphertext and the key's address. OCI authorizes
the call through IAM: put the instance in a dynamic group (its
matching rule is instance.id = '<the instance OCID>') and give
that group this policy statement:
Allow dynamic-group <group> to use keys in tenancy.
Keep it in that plain form: a statement narrowed with a
where target.key.id = ... condition looks tighter but is not
honored on the vault's cryptographic endpoint — calls against it
are refused — and scoping by compartment only helps if the key
itself (not just its vault) lives in that compartment. Set:
TOKEN_KEY_PROVIDER=oci
TOKEN_KEY_WRAPPED=... # the key, wrapped by the vault key
TOKEN_KEY_OCI_KEY_ID=... # the vault key's OCID
TOKEN_KEY_OCI_CRYPTO_ENDPOINT=... # the vault's cryptographic endpoint
Produce TOKEN_KEY_WRAPPED once, on the instance, while
TOKEN_ENCRYPTION_KEY is still set: evesynapse -wrap-token-key
prints it (the wrap itself is an instance-principal call, so it
only works there). Then remove TOKEN_ENCRYPTION_KEY and
restart. The oci provider starts and unlocks exactly like
http: serving at once, retrying in the background, installing
the key only after it opens the stored tokens.
With http the app starts and serves at once; the unwrap is
retried in the background until it succeeds, and until then stored
tokens report their key missing while everything else — the public
pages included — keeps working. A fetched key is only used after
it has been shown to open the stored tokens. Losing the key
service's wrapping key is the same as losing
TOKEN_ENCRYPTION_KEY: the stored tokens cannot be recovered and
every character signs in once more — so back the key service's
configuration up with the rest.
The four working credentials — EVE_CLIENT_SECRET,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and MODEL_API_KEY —
can come off the machine the same way. With the oci provider,
put each in OCI Secrets (in the same vault; the dynamic group
needs a read secret-bundles policy statement alongside the key
one) and name its OCID in <name>_OCI_SECRET_ID: the app reads
it at startup with the instance's identity and holds it in memory
only. With the http provider, wrap each once with the key
service's wrap call and store the ciphertext in <name>_WRAPPED;
it is unwrapped through the same endpoint as the token key. Each
credential moves independently: while a fetch is still to land,
the plain value (when one is set) serves, so add the external
source, watch the startup log name the credential as loaded, and
then take the plain value out. A feature whose credential has
not landed yet stays off and recovers when it does.
SHARED_CACHE_SECRET is the deliberate exception: it stays in
.env, because it is a proof shared with other swarm operators
by design, and moving it would buy nothing.
An install already running on TOKEN_ENCRYPTION_KEY moves over
without touching the stored tokens — the key itself never
changes, only how it is delivered:
.env up.oci, run
evesynapse -wrap-token-key on the instance (with the oci
settings below in place; the command prints only the
ciphertext). With http, post the key to your key service's
wrap endpoint once..env together: the provider, the
wrapped blob, and that provider's settings — while keeping
TOKEN_ENCRYPTION_KEY for the moment. Never leave a
half-written provider in .env across a restart: an app that
is told to use a provider whose settings are incomplete
refuses to start, by design.token encryption key loaded via the <provider> provider; stored tokens are unlocked.TOKEN_ENCRYPTION_KEY from .env and restart again;
the same line must appear. The plaintext key now exists
nowhere on the machine..env and
restart.Back the database up with pg_dump (and restore with psql):
pg_dump evesynapse > evesynapse-$(date +%F).sql
psql evesynapse < evesynapse-2026-10-05.sql # into a fresh database
The generated SQL file plus /opt/evesynapse/.env is a complete
backup of an install.
sde.gocore_static.gocore_health.gocore_maintenance.go-version-refresh| Prefix | What is in it |
|---|---|
core_ | the application itself: start-up, config, routing, page rendering, security headers, static files, health, maintenance |
auth | EVE sign-in, sign-up policy, linking characters, token refresh and encryption |
ui_ | navigation state and the tests that pin layout and styling |
module_ | which modules an account has, by the scopes it granted |
home_ | the home page: widgets, briefing, net worth |
char_ | pages about one character: sheet, skills, assets, fittings, killmails, industry |
economy_ | wallet, orders, contracts |
planets_ | planetary industry |
comms_ | mail, calendar, contacts |
profit_ | the profit pages: per-item trading and per-blueprint industry profit and loss, and their charts |
map_ | the star map: systems, routes, character and intel layers |
corp_ | corporation pages and special-interest groups |
ops_ | the op calendar and attendance |
market_ | market pages, scanners, price guide, restock |
items_ | the item database |
intel_ | wars, incursions, pilots, corporations, alliances, places |
structures_ | structures |
search_ | search |
sde | the static-data importer |
sync | the Sync page |
notify_ | notifications and browser push |
discord_ | the Discord bot |
worker_ | the background worker: the cycle, tiers, lanes, timing, name warm-up |
A *_worker.go file under another prefix is the fetching behind
that group of pages.
internal/app/templates/ — embedded html/templates (base.html
layout)
internal/app/static/ — embedded assets: the 2013 wallpaper
(bg.jpg) and the dependency-free stylesheet (style.css), served
at /static/
internal/store/ — the database: opens Postgres and brings the
schema up to date at startup (store.go, schema.go). The schema
itself is the numbered steps in internal/store/schema_pg/, which
are also sqlc's input
internal/fit/ — the fitting simulator's stat engine: the dogma
arithmetic that turns a ship, its modules, skills and charges into
the fit's statistics. Pure over the static data it loads; the
fitting pages in internal/app are its only caller
internal/skillplan/ — skill-plan arithmetic: SP per level,
training speed, ordering targets with their prerequisites, the
remap advisor. Pure; the skill pages supply the skill graph
cmd/modbuild/, internal/app/modulecatalog/ — the module build tool and the list of every module it chooses from
internal/app/swarm/ — the Swarm Cache module: the signed,
spot-checked sharing of public data between instances (see
"Swarm Cache" below)
internal/app/scanner/, tradefinder/, leaderboard/, restock/, appraise/ — the market page modules, each a page over stored rows read through module.Deps
internal/app/intel/ — the Intel module: the killboard, the d-scan
and intel parsers' pages, intel channels, the synthesized feed and
workbench, pilot dossiers, and the public wars, incursions and
faction-warfare pages. The parsers and synthesis live in core and
the intelanalyze/intelchat packages; the module is their pages
internal/app/tradeideas/ — the Trade Ideas module: the page and
the stored model answers the worker refreshes
internal/app/piplanner/ — the PI Planner module: colony chain
layouts over the SDE, beside the core colonies pages
internal/htmlsanitize/ — makes text written by other players safe to show inside a page (mail, pilot bios, descriptions)
internal/app/wallet/, mail/, calendar/, contacts/, orders/ (module id market), contracts/ — the pilot economy and comms pages
internal/balancechart/ — the balance-over-time chart builder shared by the wallet pages, the home net-worth card and the corporation wallets
internal/app/corpdata/, srp/, doctrines/, groups/ — the corporation modules: member, wallet, order, asset and structure pages; ship replacement; doctrines and corporate loadouts; groups
internal/fitview/ — the fit foundation: the fit document, EFT, slot families and the simulation view, read by core and the fitting modules
internal/app/fitting/ — the Fitting module: workbench, saved loadouts, import/export, save to the game, shopping list
internal/app/publicfits/ — the Public Fits module: browser, fit pages, ratings and the jobs that keep them stored-only; the flag and storage stay core
internal/app/skillplans/ — the Skill Plans module: plan pages, plan-from-fit and plan editing over module.Deps, built on internal/skillplan
internal/app/buildplanner/ — the Build Planner module: its page, JSON payload and recipe source over the SDE, built on module.Deps
internal/humanize/, internal/tags/ — small pure wording and tag helpers shared by core and modules
internal/buildplan/ — the industry build planner's engine: the
tree of everything a product needs, the manufacturing formulae,
what is already held, and the price of the rest. Pure
internal/markethistory/ — figures and the SVG chart computed
from a type's stored daily price history. Pure
internal/selfupdate/ — evesynapse -update: the release channel,
checking a release's signature, downloading, verifying and swapping
in a new build, and handing over to the running server. It is told
the running version and knows nothing else about the application
internal/releasesig/ — the signature on a release: how an update
manifest is signed and how the updater checks one, plus
trusted_keys.pem, the public keys compiled into every build
("Release signing" below)
internal/pidfile/ — the server's pidfile: written at start-up,
read by -update to restart the server and by -refresh to refuse
while it is running (pidfile/pidfiletest is test support)
internal/dotenv/ — the .env loader the server and the updater
both use
internal/pgtest/ — test-only embedded-Postgres provisioning
(a fresh database per test; go test ./... needs no external
database). A package whose tests use it needs a TestMain that
calls pgtest.TestMain, which stops the server when the tests
finish; without one pgtest refuses to start a server
internal/esi/ — the ESI client: HTTP layer, per-character
snapshot cache, and the two-tier type/group/place name resolution
(network tier + cache-only render tier). Never imports
internal/app; access tokens are injected via a callback.
internal/devtools/ — dev-only routes (/dev-login), imported
solely by cmd/evesynapse-dev
internal/db/query/ — hand-written queries (sqlc input)
internal/db/sqlc/ — sqlc-generated code (do not edit)
/corporations/wallets/ — the seven wallet divisions with
balances, plus the selected division's recent journal and
transactions (requires the Accountant or Junior
Accountant role in-game)
/corporations/orders/ — open corporation orders with item,
location and region names, price, volume remaining and expiry
(requires the Accountant or Trader role in-game)
/corporations/assets/ — corporation assets grouped by
location, with player-given singleton names (named ships,
renamed containers) resolved by the worker via the assets/names
endpoint (requires the Director role in-game)
/corporations/structures/ — Upwell structures: type, system,
state, fuel expiry, reinforce timers and service states
(requires the Station Manager role in-game)
/corporations/killmails/ — the corporation's recent kills and
losses, sharing the killmail store and page rendering with
/killmails/ (a victim in the corp is a LOSS; requires the
Director role in-game)
All corporation subpages follow the selected character's
corporation (switch characters with ?character=, like Assets),
render from worker-warmed snapshots only, and show a plain
"needs the role" state when ESI refused the dataset for want of
an in-game role — see the caching section below.
/doctrines/ — the corporation's doctrines: the fleet
compositions it flies (requires a character in the corporation)
/doctrines/fits/ — Corporate Loadouts: the fittings behind
the doctrines (requires login)
/srp/ — Ship Replacement (SRP): replacement requests and
their handling (requires login)
/groups/ — Groups: the lists of characters a corporation's
or alliance's directors keep by hand, used by the Discord role
rules and more (see "Discord"; requires login)
/corporations/settings/ — the corporation's own settings:
its Discord servers and related housekeeping (requires login)
/auth/eve — starts EVE SSO login (also "Link another character")
/auth/callback — OAuth2 callback (see SSO flow below)
/auth/logout — destroys the session (POST; the sidebar's Sign out button)
/admin/ — the admin page: an analytics tab of read-only site
statistics and charts, an accounts tab (accounts and the
characters linked to them: totals, the newest accounts, and one
account looked up by any of its characters, a search box that
suggests as you type, with each character's sign-in state and
what it granted), and a notices tab. Nothing about data
freshness: that is the Sync tab (requires an admin account; see
EVE_ADMIN_CHARACTER_IDS)
/assets/ — asset browser: every stack grouped by location for the
signed-in user's characters (requires login; esi-assets.read_assets.v1)
/wallet/ — the character wallet: balance, journal and
transactions, with the balance over time as a chart (requires
login; esi-wallet.read_character_wallet.v1)
/orders/ — your characters' market orders, open and finished
(requires login)
/contracts/ — your characters' contracts and what is inside
them (requires login)
/contracts/search/ — search public contracts across New Eden
(requires login)
/profit/ — profit: per-item trading profit and loss on a pooled
FIFO cost basis (sales tax paired, broker fees tied, and typed
gaps where a cost is not known), per-blueprint industry profit
three ways (realized, at the prices of their time, and rerun at
today's prices), LP earnings, and the chart set: running total by
character, fees with their share of sales, and cost of goods sold
(requires login)
/market/ — market browser: item search (local name cache + exact
ESI resolution), guide prices, and best/top orders for The Forge,
Domain, Sinq Laison, Heimatar and Metropolis (requires login; all
data is public ESI, order books capped at 20 pages)
/market/appraise/ — appraisal: paste a plain item list, an asset
or cargo copy, or an EFT fit (the ship, its fitted modules and
charges, and the drones and cargo the fit lists), and every line is
priced from EveSynapse's own stored prices with its basis and age
shown. Blueprint copies written as Name (BPC, ME n, TE n, n runs)
price from the stored median for that copy, never the original, and
a copy with too few listings on record is left unpriced. Mutated
(abyssal) items appraise too: a line carrying the item's in-game
link prices from that item's stored rolls estimate, and a line
without the link, or with no estimate on record, prices its base
item and says so (no login required; signed-in appraisals are saved
and shareable by link)
/market/scanner/ — the Spread Scanner: buy and sell spreads
from EveSynapse's own swept order books, ranked and stored at
sweep time (requires login)
/market/trends/ — Market Trends: price and volume movement
from the stored daily history (requires login)
/market/lp/ — the LP Store browser: every loyalty point
store's offers, what each costs in LP, ISK and required items,
and what the reward is worth (requires login)
/market/baskets/ — Market Baskets: saved groups of items,
watched and priced together (requires login)
/market/ideas/ — Trade Ideas: trade opportunities written by
a model from the spread scanner's rows (needs a model
configured: MODEL_API_KEY or the Cloudflare settings under
Configuration; otherwise the page shows its empty state)
/market/tradefinder/ — the Tradefinder: routes between
stations with the profit worked out from stored order books
(requires login)
/market/leaderboard/ — Top Stations: stations ranked by open
orders and the ISK on their books (requires login)
/market/restock/ — the Restock Planner: what a market or a
list needs bought to be whole again, and what it costs
(requires login)
/items/ — the Item Browser: the item database, every type
with its group, attributes and market summary
/skills/ — full skill sheet: total/unallocated SP, level-V count,
the complete training queue, and every known skill grouped by
category with per-group SP subtotals (requires login;
esi-skills.read_skills.v1 + esi-skills.read_skillqueue.v1)
/skills/plans — Skill Plans: build a training plan (or take
one from a fit) with prerequisites ordered for you and the
training time worked out (requires login)
/industry/ — Blueprints: your blueprint library, originals
and copies with their research (requires login)
/industry/jobs/ — My Jobs: your industry jobs, running and
delivered (requires login)
/industry/mining/ — the Mining Ledger: what your characters
mined, by day and by item (requires login)
/planner/ — the Build Planner: pick a product and see the
tree of everything it needs, what you already hold, and the
price of the rest (requires login)
/planets/ — My Colonies: your planetary colonies and what
they extract and produce (requires login)
/industry/pi/ — the PI Planner: lay out a colony chain on
paper before committing to it in the game (requires login)
/admin/sync/ — the Admin page's Sync tab: the worker and the
data: the last cycle in figures (time, fetches against the
allowance, characters by tier, ESI's rate-limit budgets), what
was warmed ahead and how pages are served, the shared cache's
check outcomes, the shared public data, the SDE static-data
block (row counts, import state, update check), and one
character's snapshot freshness and type-name coverage with a
re-warm button. The character is the reader's own until another
is looked up by name or id; no page prints every character. It
auto-refreshes so an import can be watched as it lands (requires
an admin account); /sync/ redirects here
/intel/wars/ — current wars from the public war list and
worker-warmed war details: aggressor/defender names, state,
kill records, open-for-allies/mutual badges, and a flag on wars
involving one of your characters' corporations (requires login;
all data is public ESI)
/intel/incursions/ — active Sansha incursions: constellation,
staging system, state, influence bar, boss presence and the
affected systems (requires login; public ESI)
/intel/fw/ — faction warfare: per-faction summary cards
(systems held, pilots, kills and victory points) and the most
contested front-line systems with occupier/owner factions
(requires login; public ESI)
/intel/kills/ — the Killboard: kills and losses across New
Eden from the stored killmail data (requires login)
/intel/scan/ — the D-Scan Parser: paste a directional scan
(or local, probe or overview text) and get the interpreted
reading: what is there and on grid, whose ships they likely
are, and the composition at a glance (requires login)
/intel/channels/ — Intel Channels: the channels you run or
belong to. Reports posted to a channel are parsed and
synthesized as they land (requires login)
/intel/feed/ — the Intel Feed: the synthesized reports from
every channel you can read, newest first, each element linking
deeper (to the pilot, the system on the map, related scans)
(requires login)
/intel/workbench/ — the Intel Workbench: one pilot or gang
under the microscope: the synthesized headline, their
activity, sightings and threads (requires login)
/license/ — the project's license and legal notices
/healthz — ok (200) when the database answers and the background
worker is running; 503 with one line per problem otherwise
/dev-login — dev-only fake sign-in, registered only when
DEV_LOGIN=1 (see below)
MODEL_API_KEY_OCI_SECRET_IDwith oci |
| — |
The OCI Secrets OCID each working credential is read from at startup instead of sitting in .env (oci provider). The plain variable can stay until the fetched value is seen to land, then come out |
EVE_CLIENT_SECRET_WRAPPED, DISCORD_CLIENT_SECRET_WRAPPED, DISCORD_BOT_TOKEN_WRAPPED, MODEL_API_KEY_WRAPPED | with http | — | Each working credential wrapped by the key service, unwrapped at startup through the same endpoint as the token key (http provider) |
EVE_ADMIN_CHARACTER_IDS | no | — | Comma-separated EVE character IDs whose accounts may open the Admin and Sync pages. Any character linked to an account makes that whole account an admin's. Empty = nobody. The status belongs to whoever owned the character when it was first seen as an admin: if the character is sold or transferred, its new owner does not get it. To give it to a new owner on purpose, remove the id, restart, add it back and restart |
EVE_ALLOWED_CHARACTER_IDS, EVE_ALLOWED_CORPORATION_IDS, EVE_ALLOWED_ALLIANCE_IDS | no | — | Limit who may create an account (see "Who can sign up"). All empty = anyone who can sign in with EVE |
EVESYNAPSE_UPDATE_PROVIDER | no | github, gitlab, forgejo, mirror | The sources the updater reads releases from: one or more of github, gitlab, forgejo, mirror (the project's static mirror), comma-separated. The updater checks them in the order listed and prefers no source beyond that order: the newest signed release wins, and a tie goes to the earlier-listed source. Unset, it walks the default shown here: the three forges, then the mirror |
EVESYNAPSE_UPDATE_PROJECT | no | natemsz/evesynapse | GitLab project (group/project) the updater checks; EVESYNAPSE_UPDATE_REPO, set by older installs, is read as the same project's path |
EVESYNAPSE_UPDATE_API_URL | no | the provider's public API | The API address of the chosen provider the updater lists releases from; set it only for a self-hosted GitLab or a GitHub Enterprise host |
EVESYNAPSE_UPDATE_MIRROR_URL | no | https://cdn.synap6.io | The mirror provider's root |
EVE_SDE_BASE_URL | no | Fuzzwork's dump | Base URL of the SDE CSV dump the importer downloads |
ESI_CONTACT | no | — | How CCP can reach whoever runs this instance (an email address, a Discord handle, a character name). Sent in the User-Agent of every ESI request, as CCP asks of third-party apps |
STATUS_PAGE_ORIGIN | no | — | The status page's origin (https://status.example.com): the one site whose pages may read /status.json, the public summary of how the database, worker, notifications, ESI, market sweeps and push are doing (a state and an age each, nothing more) |
LOG_LEVEL | no | info | Least severe kind of log line written: debug, info, warn or error (see "Logging") |
LOG_FORMAT | no | text | text for the classic line, json for one object per line |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY | no | — | The key pair that turns on browser push notifications (see "Notifications and browser push"). Make one with evesynapse -push-keys. Unset, notifications show in the top bar only |
VAPID_SUBJECT | no | the site's address | A contact address (mailto: or https:) the browsers' push services may use to reach the operator |
ANDROID_APP_PACKAGE, ANDROID_APP_FINGERPRINTS | no | — | Name the site's Android app (see "The Android app"): its package, and the SHA-256 fingerprints of the certificates it is signed with, comma separated. Unset, the site names no app |
OPS_MANAGER_ROLES | no | Director | The in-game corporation roles whose holders may create, change and cancel ops on the calendar, comma separated and spelled as ESI spells them (Director,Personnel_Manager) |
NOTIFY_POLL_SECONDS | no | 30 | How often, in seconds, an open page checks whether its notifications icon has changed, so new notifications show without a reload. 0 turns the checks off; other values are kept between 5 and 3600. Takes effect on restart |
WORKER_TIERS | no | on | How often a character's data is refreshed follows its account: as often as ESI allows for the character someone has open, every 5 minutes (position every 2) for that account's other characters, every 5 to 15 minutes for an account seen in the last day, every 30 minutes (position every 6 hours) for one not seen for a day, and every 2 hours for one not seen for a week that has no browser subscribed to push and no Discord account linked. off refreshes everything as often as ESI allows for everyone, which uses up the worker's allowance at around fifteen characters |
WORKER_FETCHES_PER_CYCLE | no | 120 per lane (480) | How many ESI fetches the worker's character pass may make in one one-minute cycle, for the whole server (kept between 20 and 2000). Raise it when the Sync page shows the allowance used up and characters put off. One lane gets through about 200 fetches in a minute, so more than about 200 per lane makes a cycle run past its minute |
FORESIGHT | no | on | Warming on game events: when a character docks at a trade hub, an industry job is about to finish, an order is undercut, a skill queue runs out or a war is declared on a corporation, the data the account is likely to open next is fetched ahead. off switches it off. The Sync page's "Warmed ahead" table shows what it cost and how often it was right |
FORESIGHT_FETCHES_PER_CYCLE | no | 30 | The most ESI calls a cycle may spend on that (0 to 200), out of what the rest of the cycle leaves of its allowance |
SHARED_CACHE | no | off | Sharing public data with other instances (see "Swarm Cache"): open (or on) shares with every instance holding the secret, allowlist also waits for each instance to be approved on the Admin page. Anything else stays off |
SHARED_CACHE_SECRET | no | — | The swarm's membership proof: at least 32 random characters (openssl rand -hex 32). Enabling sharing without one refuses to start. Never shown on any page |
SHARED_CACHE_SEEDS | no | — | Bootstrap and fallback directory nodes (comma-separated https URLs; http only on loopback): the instances a new or lonely one asks for the membership list. Afterwards membership spreads peer to peer |
SHARED_CACHE_GOSSIP_HOURS | no | 18 | How often, in hours, membership is re-gossiped (12 to 168). Takes effect on restart |
SHARED_CACHE_WARM | no | — | Run as a dedicated warming node for these shared kinds (all, or a comma list of corporation_record, alliance_record, type_name, market_history, structure_name): the instance fetches the swarm's misses of them on its own ESI budget, and other instances spend theirs on those kinds only as the last resort. Takes effect on restart |
WORKER_LANES | no | 4 | How many characters the worker refreshes at the same time (1 to 16). Each character is still fetched one request after another, so ESI's per-character rate limits are unaffected; 1 is the worker as it was, one character at a time. More lanes mean more database connections in use at once: with more than 8, raise DB_MAX_CONNS to about two and a half per lane (40 for 16 lanes), which still fits PostgreSQL's default of 100. The Sync page's "Fetches a cycle" beside "Characters on average" shows what the characters actually cost, which is what to size this and the allowance by |
DB_MAX_CONNS | no | 20 | How many database connections the app may hold open (5 to 500). The session store keeps a few more of its own (at most 4, or one per CPU core if that is more). Raise it together with PostgreSQL's max_connections (100 unless changed), which must stay above the two added together; more than a few dozen is rarely useful on one machine |
DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET | no | — | A Discord application's id and secret: turns on "Connect Discord" (see "Discord") |
DISCORD_BOT_TOKEN | no | — | That application's bot: lets it be added to Discord servers, where it gives roles and sends messages. What it does in each server is set on the site by that server's directors, not here |
MODEL_API_KEY | no | — | Turns on the Trade ideas page (Market > Trade ideas) and its optional home module with the hosted model (Meta's Muse Wonder): the worker asks it for trade opportunities in The Forge from the spread scanner's rows, at most twice a day. Keep the key secret |
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_AUTH_TOKEN | no | — | Cloudflare Workers AI credentials: the Trade ideas fallback when no MODEL_API_KEY is set, asking the Gemma model instead, on the same twice-a-day cadence. With neither configured, the page shows its empty state. Keep the token secret |
INTEL_TAGGER | no | on | The intel classifier: a small model built into the server that helps the intel reader tell where one pilot's name ends and the next begins. It runs on this machine and sends nothing anywhere. off reads intel without it |
INTEL_TAGGER_WEIGHTS | no | the built-in model | A model file to use for the intel classifier instead of the built-in one |
DEV_LOGIN | no | — | Dev build only: 1 registers the /dev-login route |
A setting is changed only while it is still as PostgreSQL shipped
it and nobody has set it with ALTER SYSTEM, so a value you chose
is never replaced and running the script again changes nothing. A
machine with under 2 GB is left alone, and so is a database on
another machine. On a first install PostgreSQL is restarted to
apply them; if it does not come back, the script takes them out
again and starts it as it was. With EveSynapse already running the
script does not restart anything: it prints the command, because
the restart drops the site's connections for a few seconds.
EVESYNAPSE_TUNE_POSTGRES=0 skips the whole step:
sudo EVESYNAPSE_TUNE_POSTGRES=0 bash deploy/setup.sh
The program, /opt/evesynapse and .env belong to root; the
service account can read them but not change them, and the
unit runs it sandboxed with /run/evesynapse as its only
writable directory. Updates run as root, so the account the
server runs as must not be able to replace what root runs.
If you installed before this layout, run the script once more
to move to it. Installing from a fork or from a build you
made yourself works too:
Why
/usr/bin? Updating always runs undersudo, andsudosearches its own locked-down PATH — which does not include/usr/local/binon RHEL-family systems. A link in/usr/local/binworks in your shell but is invisible tosudo evesynapse …, so the setup script puts the link in/usr/bin(and retires an older/usr/local/binlink if it finds one).
sudo EVESYNAPSE_UPDATE_REPO=you/evesynapse bash deploy/setup.sh
sudo EVESYNAPSE_BINARY=/path/to/evesynapse bash deploy/setup.sh
Run the fork form from a checkout of the fork: the script checks the release against the release key in the checkout it is run from, and a fork's releases are signed with the fork's own key.
Production from source: in a checkout,
sudo bash deploy/setup.sh --build installs the build tools
it needs, provisions the same way, but builds the program
from the checkout instead of downloading a release, and
installs that build.
Local testing only: in a checkout,
sudo bash deploy/setup.sh --build-dev provisions a local
database and a dev .env, builds the dev server and runs
it (see "Run" above). Never deploy the dev build.
Fill in /opt/evesynapse/.env with your EVE app's client
ID, secret, and callback URL, then start it:
sudo systemctl start evesynapse
Open the address you configured and sign in with EVE.
GET /universe/types/{id}/type_names