Secrets and credentials
This page covers the difference between the secrets that start the installation and the credentials you connect inside it.
Before you begin
- Know how your installation runs, because that sets where the secrets file is.
opensslon yourPATH, to make a key.- For provider credentials, the Tower open at Integrations.
Four bootstrap secrets stay in the environment
NoticeOS needs four secrets before it can read its own store. They cannot be stored in the product, because the product is not running yet.
| Secret | What it does |
|---|---|
CREDENTIALS_KEY | The key every stored provider credential is encrypted under. Make one with openssl rand -base64 32. |
OPERATOR_TOKEN | The token the runner and your own terminal commands present to the installation's write routes. |
ASSET_TOKENS | One shared token per site, checked when a site sends its report and presented when the installation pulls one. |
DATABASE_URL | The address of the installation's own Postgres database, as the application login. |
They are stored in one file, workers/ingest/.dev.secrets.json, which is never committed. It is in the installation folder's state/ (/state/workers/ingest/.dev.secrets.json in the app container), and pnpm os:setup generates it.
Edit it as formatted JSON. After a change, run pnpm os:restart so it is read again. The app compiles the file to a generated .dev.vars beside it at every start. DATABASE_URL never reaches the app as a binding; the runner reads it at start and passes it to the server's environment only.
Never put a provider credential here first
The environment is for the four secrets in the table. A Google key, a DataForSEO login or a webhook URL belongs on the Integrations page, not in this file.
What never leaks
Secrets are removed from logs before they are written to disk and again when pnpm os:logs reads them. Status output never shows an address, a token or a raw database error. Test fixtures use invented values.
Connect a provider credential
Every provider credential is connected in the product.
- Open Integrations (
/integrations). - Select the provider and Connect. The panel has a field for each value that provider needs and shows where to find it.
- Select Test connection. It tests the credential with one real call and shows a live sample.
- To remove it later, select Disconnect.
Each credential is stored AES-GCM encrypted in the database under CREDENTIALS_KEY. Saving a new value inserts the next version and removes the previous one in a single transaction. Every collector reads the store first.
The providers available today include Google Analytics and Search Console, Bing Webmaster, DataForSEO, Microsoft Clarity, PostHog, Mediavine, Discord, calendar feeds and Cloudflare.
Move credentials out of the environment
Older installations kept provider credentials in the secrets file. That still works: a provider with no stored credential falls back to its environment value, and its card on Integrations shows a Legacy env chip. System health shows in one line how many connections still use a legacy credential.
- On Integrations, on any Legacy env card, select Import from this machine. The whole secrets file is imported.
There is no import command; use the button. The move is optional. Not moving costs portability: a fresh installation would need those values copied by hand.
Check every credential
In the checkout, run the check:
shpnpm creds:check # every configured credential, one real probe each pnpm creds:check --lane dataforseo # one provider only pnpm creds:check --origin http://127.0.0.1:6000 # a Tower on another port
Each row shows whether the credential came from the store or the environment, and shows a live data sample or the provider's actual error and the likely fix. It never prints a secret value.
Two providers are probed only when you name them explicitly, as in the second example: a Clarity probe spends one of ten daily calls, and a Discord probe posts a real message.
Rotate the encryption key
Rotation re-encrypts every stored credential under a new key, one connection at a time, with every credential readable throughout. It runs on the machine that hosts the installation.
In a terminal, generate the new key:
shopenssl rand -base64 32In
workers/ingest/.dev.secrets.json, setCREDENTIALS_KEY_PREVIOUSto the currentCREDENTIALS_KEY, andCREDENTIALS_KEYto the new value.Restart so both keys are loaded:
pnpm os:restart.Run the sweep:
shpnpm creds:rotate-keyRemove
CREDENTIALS_KEY_PREVIOUSfrom the file and restart once more.
While both keys are present, every read tries the current key and then the previous one, so a collector that fires mid-sweep still works.
Verify
pnpm creds:checkshows every row with its source (store or environment) and a live data sample.- No card on Integrations shows a Legacy env chip, and System health no longer counts legacy credentials.
pnpm creds:rotate-keyprints counts and provider names, nothing else, and exits zero.
If it didn't work
creds:checkprints that it is running the env-only check: the installation is stopped, so store-held credentials cannot be tested. Start it, or pass--originwith the right port.- A card still shows Legacy env: the credential is still stored in the environment file. Select Import from this machine.
creds:rotate-keyexits non-zero: a row could not be re-encrypted. KeepCREDENTIALS_KEY_PREVIOUSin place and run the sweep again before removing it. See Troubleshooting.
The line between bootstrap secrets and integration credentials is written once, in doc 06, Operations & self-observability. The credential commands are at https://github.com/notice-cx/NoticeOS/blob/main/scripts/README.md#credentials-credscheck.