2026-09-04 19:30:54 +02:00
|
|
|
|
# Operator runbook — seeding platform access
|
|
|
|
|
|
|
|
|
|
|
|
Some platform access cannot be provisioned from a specification. Telegram will
|
|
|
|
|
|
not let software create the *first* credential: an account needs a phone and a
|
|
|
|
|
|
device, and `api_id` / `api_hash` come from a web form behind a login. No API
|
|
|
|
|
|
exists for either, and none is coming, because their purpose is to establish that
|
|
|
|
|
|
a person is on the other end.
|
|
|
|
|
|
|
|
|
|
|
|
This runbook is what we do instead. It is not a lowering of the bar. **A manual
|
|
|
|
|
|
step is acceptable when it is guided, repeatable, and bounded — and unacceptable
|
|
|
|
|
|
when it is merely undocumented.** The test each step below has to pass is that
|
|
|
|
|
|
an operator who has never done it can follow it once, get the same result as
|
|
|
|
|
|
anyone else, and hand the outcome to a tool that takes over from there.
|
|
|
|
|
|
|
|
|
|
|
|
## The rule this runbook operates under
|
|
|
|
|
|
|
|
|
|
|
|
> Automate everything that has an interface. Where a step exists specifically to
|
|
|
|
|
|
> prove a human is present, guide it — and record why it resisted automation, so
|
|
|
|
|
|
> the judgement can be revisited rather than inherited.
|
|
|
|
|
|
|
|
|
|
|
|
Every manual step below carries a **Why not automated** line. That line is the
|
|
|
|
|
|
thing to re-read when the platform changes. Some of them will stop being true;
|
|
|
|
|
|
the sooner that is noticed, the smaller the runbook gets.
|
|
|
|
|
|
|
|
|
|
|
|
There is a real distinction here worth keeping. A KYC identity check resists
|
|
|
|
|
|
automation *by design* — automating it would defeat what it is for, and a
|
|
|
|
|
|
component weak enough to allow it is a defect, not an opportunity. A web form
|
|
|
|
|
|
that mints an API key resists automation only *incidentally*, because nobody
|
|
|
|
|
|
built the endpoint. The first stays manual on principle. The second is a
|
|
|
|
|
|
candidate the moment anything changes.
|
|
|
|
|
|
|
|
|
|
|
|
Telegram's steps are all the second kind. None of them is a fraud control; they
|
|
|
|
|
|
are just interfaces that were never built.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Before you start
|
|
|
|
|
|
|
|
|
|
|
|
Have these ready. Stopping halfway through to find one is how the session
|
|
|
|
|
|
becomes irreproducible.
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] A phone number that can receive SMS, **not** a personal one
|
|
|
|
|
|
- [ ] A device with Telegram installed, to complete the first login
|
2026-09-04 22:02:56 +02:00
|
|
|
|
- [ ] Access to OpenBao (`bao.coulomb.social`) with write on the campaign's subtree
|
2026-09-04 19:30:54 +02:00
|
|
|
|
- [ ] The campaign's presence spec merged (`pr-hall-of-helix/presence/telegram.yaml`)
|
|
|
|
|
|
- [ ] The avatar asset committed and referenced by the spec
|
|
|
|
|
|
|
|
|
|
|
|
Roughly 20 minutes, once.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
## Where the secrets go
|
|
|
|
|
|
|
|
|
|
|
|
The provisioner computes these paths; they are not a convention you may vary.
|
|
|
|
|
|
For campaign `hall-of-helix` on the default `secret` mount:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
secret/fluid-telegram/hall-of-helix/telegram/operator-app api_id, api_hash
|
|
|
|
|
|
secret/fluid-telegram/hall-of-helix/telegram/operator-session written by the tool
|
|
|
|
|
|
secret/fluid-telegram/hall-of-helix/telegram/bot-token written by the tool
|
|
|
|
|
|
secret/fluid-telegram/hall-of-helix/telegram/redaction-salt written by the tool, once
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Set these before any command below. `BAO_MOUNT` defaults to `secret`.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export BAO_ADDR=https://bao.coulomb.social
|
|
|
|
|
|
export BAO_TOKEN=<your token> # not committed, not echoed
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Only the first path is written by hand. The rest are the tool's, and writing
|
|
|
|
|
|
one yourself will be overwritten or -- for the salt -- silently honoured
|
|
|
|
|
|
forever.
|
|
|
|
|
|
|
2026-09-04 19:30:54 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Step 1 — Register the operator account
|
|
|
|
|
|
|
|
|
|
|
|
**Why not automated:** Telegram requires a phone number and an SMS round trip.
|
|
|
|
|
|
There is no registration API at any tier. This is the step whose whole purpose
|
|
|
|
|
|
is to prove a person is present.
|
|
|
|
|
|
|
|
|
|
|
|
1. Install Telegram on the device and register with the operator phone number.
|
|
|
|
|
|
2. Set a display name that reads as an operator account, not a person.
|
2026-09-04 22:02:56 +02:00
|
|
|
|
3. Enable two-factor authentication and store the password somewhere you can
|
|
|
|
|
|
retrieve it. The tool prompts for it during Step 3 and does not read it from
|
|
|
|
|
|
OpenBao, so this is for you, not for the provisioner.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
**Do not use a personal account.** If it is rate-limited or restricted — which
|
|
|
|
|
|
automation can cause — the loss should be a throwaway account, not someone's
|
|
|
|
|
|
messages.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
**Record:** the phone number where your team can find it. The tool prompts for
|
|
|
|
|
|
it rather than reading it, so it is not one of the paths above.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Step 2 — Obtain `api_id` and `api_hash`
|
|
|
|
|
|
|
|
|
|
|
|
**Why not automated:** issued by a web form at my.telegram.org behind a login
|
|
|
|
|
|
code. No API. This one is incidental rather than principled — if Telegram ever
|
|
|
|
|
|
exposes it, Step 2 disappears.
|
|
|
|
|
|
|
|
|
|
|
|
1. Go to <https://my.telegram.org>, log in with the operator number.
|
|
|
|
|
|
2. **API development tools** → create an application.
|
|
|
|
|
|
- App title: `helixforge-provisioner`
|
|
|
|
|
|
- Platform: Other
|
|
|
|
|
|
3. Copy `api_id` and `api_hash`.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
**Record:** both, in OpenBao. This is the one secret you write by hand, and
|
|
|
|
|
|
everything downstream reads it:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
bao kv put secret/fluid-telegram/hall-of-helix/telegram/operator-app \
|
|
|
|
|
|
api_id=<the number> api_hash=<the hash>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Verify the tool can see it before going on:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
bao kv get secret/fluid-telegram/hall-of-helix/telegram/operator-app
|
|
|
|
|
|
```
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
They are shown once per application. Losing them means creating another.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Step 3 — Mint the MTProto session
|
|
|
|
|
|
|
|
|
|
|
|
**Why not automated:** the login code arrives out of band, on the device. The
|
|
|
|
|
|
command is interactive by necessity — but *only this command*, and only once.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
go run ./cmd/provision session bootstrap --campaign hall-of-helix
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
It prompts for the phone number, the login code, and the 2FA password, then
|
|
|
|
|
|
writes the session string to OpenBao at
|
2026-09-04 22:02:56 +02:00
|
|
|
|
`secret/fluid-telegram/hall-of-helix/telegram/operator-session`.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
The session string is a **full-account credential** — it can do anything the
|
|
|
|
|
|
account can. It never leaves OpenBao, is never given to the adapter, and is used
|
|
|
|
|
|
only by the provisioner. Treat losing it as losing the account.
|
|
|
|
|
|
|
|
|
|
|
|
**Verify:**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
go run ./cmd/provision session check --campaign hall-of-helix # prints the account, no secrets
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Step 4 — Hand over to the provisioner
|
|
|
|
|
|
|
|
|
|
|
|
From here nothing is manual. The remaining work is `FT-WP-0002` T03–T06:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
go run ./cmd/provision plan --spec ../pr-hall-of-helix/presence/telegram.yaml # read the diff
|
|
|
|
|
|
go run ./cmd/provision apply --spec ../pr-hall-of-helix/presence/telegram.yaml # execute it
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
`apply` registers the bot through BotFather, sets its name, about text and
|
|
|
|
|
|
description, creates the test channel, adds the bot as administrator with
|
2026-09-04 19:30:54 +02:00
|
|
|
|
`post_messages` only, generates the redaction salt if it is absent, and writes
|
|
|
|
|
|
`presence/resolved/hall-of-helix.yaml`.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
Two things it will *not* do on this first run, both by design and both reported
|
|
|
|
|
|
in the plan. The **public channel is held** until a rendering has been checked in
|
|
|
|
|
|
the test channel. The **avatar is deferred**: `/setuserpic` needs a photo upload
|
|
|
|
|
|
that is not implemented, so set it by hand in @BotFather, or wait.
|
|
|
|
|
|
|
2026-09-04 19:30:54 +02:00
|
|
|
|
**Read the plan before approving it.** That is the human judgement this design
|
|
|
|
|
|
keeps — not clicking through BotFather, but deciding whether the diff is what
|
|
|
|
|
|
was intended.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Re-running this
|
|
|
|
|
|
|
|
|
|
|
|
Steps 1–2 are once per operator account, ever. Step 3 repeats only if the session
|
|
|
|
|
|
is revoked — by a password change, an explicit sign-out, or Telegram invalidating
|
|
|
|
|
|
it. When that happens, re-run Step 3 alone; nothing else is affected, because
|
|
|
|
|
|
resolved state and the bot token do not depend on the session that created them.
|
|
|
|
|
|
|
|
|
|
|
|
Step 4 is safe to repeat at any time. It is a converging reconciler, and a second
|
|
|
|
|
|
apply over an unchanged spec does nothing.
|
|
|
|
|
|
|
|
|
|
|
|
## When a step stops being necessary
|
|
|
|
|
|
|
|
|
|
|
|
Re-read the **Why not automated** lines whenever Telegram changes its API surface,
|
|
|
|
|
|
and delete the step if the reason has expired. A runbook nobody prunes becomes a
|
|
|
|
|
|
description of how things used to work, and the manual steps in it start looking
|
|
|
|
|
|
like requirements rather than gaps.
|