# Automated / git-managed deployments

This document covers what you need to do when your Node-RED flows live in source
control (or are otherwise managed by automation) and your bot's token is supposed
to flow in from an environment variable or a secrets store rather than being typed
into the editor by hand.

It is also the answer to issue [#432](https://github.com/windkh/node-red-contrib-telegrambot/issues/432).

## Why this needs special handling

The bot's `token` field is declared to Node-RED as a **credential**, not a regular
config property. Node-RED treats credentials specially:

- It strips them out of `flows.json` on save.
- It stores their encrypted form in a separate `flows_cred.json` file.
- The encryption key is `credentialSecret` from `settings.js`. If
  `credentialSecret` isn't pinned in `settings.js`, Node-RED generates one and
  stores it in `.config.runtime.json`.

So if your automated deployment commits `flows.json` and uses a fresh
`credentialSecret` on every container start, the token will appear to "vanish" —
you'll see the bot abort with `Aborting: Token of <botname> is not set`.

The plugin's `{env.get("X")}` / `{flow.get("X")}` / `{global.get("X")}`
expressions are evaluated **at runtime, inside the credential resolution path**.
They work — but only once the encrypted credential has been re-read from
`flows_cred.json`. The expression has to survive the credential-serialisation
step first.

## Pattern 1 — pin `credentialSecret` and commit `flows_cred.json`

The most robust pattern. Pick a deterministic secret per environment and tell
Node-RED to use it via `settings.js`:

```js
// settings.js
module.exports = {
    credentialSecret: process.env.NODE_RED_CREDENTIAL_SECRET || '<a-strong-fallback>',
    // ...
};
```

Then commit both `flows.json` **and** `flows_cred.json` to your repo. The token
field can be a literal value or a `{env.get("...")}` expression — either
survives, because Node-RED re-decrypts `flows_cred.json` with the same secret on
every boot.

If you want to keep the token out of the encrypted file too, put the
`{env.get("TG_TOKEN")}` expression in the token field before committing. The
plugin will resolve it at runtime against whatever `TG_TOKEN` the container
inherits from its environment.

## Pattern 2 — write `flows_cred.json` at boot

If you don't want to commit any credential material, generate
`flows_cred.json` from your secrets store as the container starts. A small
entrypoint script that reads from Vault / SOPS / Doppler / AWS Secrets Manager /
... and writes the file to `$HOME/.node-red/flows_cred.json` before launching
Node-RED works fine. `credentialSecret` still needs to match what was used to
encrypt it.

## Pattern 3 — `userDir` and `flowFile` overrides

The `userDir` and `flowFile` settings let you point Node-RED at any directory.
A common pattern is to keep `flows.json` in `/etc/node-red/flows.json`
(read-only, baked into the image) and have Node-RED write `flows_cred.json` to
a writable volume.

## What `{env.get(...)}` does and doesn't fix

Once the token expression makes it past Node-RED's credential serialisation,
the plugin's expression evaluator (see
[ADR 0004](doc/architecture/adr/0004-safe-expression-evaluator.md)) handles
the supported forms:

```
{env.get("TG_TOKEN")}
{flow.get("token")}
{global.get("token")}
{context.get("token")}
{context.flow.get("token")}
{context.global.get("token")}
```

`usernames` and `chatids` use the same syntax. As of V17.3.1 a raw string
result from `env.get` / `flow.get` / `global.get` is split on commas — so
`CHATIDS=123,456` resolves to `[123, 456]`.

Anything outside that grammar (function calls, statement chains, prototype
walks, `require`, `eval`, etc.) evaluates to `undefined` — see
[ADR 0004](doc/architecture/adr/0004-safe-expression-evaluator.md) for the
exact whitelist.

## Common pitfalls

- **`credentialSecret` is not set in `settings.js`.** Node-RED generates a random
  one on first start and writes it to `.config.runtime.json`. Subsequent
  container restarts that lose that file will be unable to decrypt the existing
  `flows_cred.json`. The bot will then abort with "token is not set".
- **`credentialSecret` is rotated.** Same effect — old `flows_cred.json` can no
  longer be decrypted. Re-enter the token via the editor (or delete
  `flows_cred.json` and let Node-RED re-create it on save).
- **`flows.json` is regenerated by a flow-export step.** Make sure your export
  pipeline preserves the credential references (it normally just strips the
  values, which is correct), and doesn't accidentally write back the bot config
  node with `credentials: {}` cleared.
- **Multiple bot config nodes share the same token.** The plugin's
  `botsByToken` registry aborts the second one with
  "Token of <botname> is already in use by ...". Use one config node per token
  and reference it from multiple runtime nodes.
