Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 351 additions & 0 deletions docs/infrastructure/03-Guides/telegram-bot-local-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
---
title: Telegram Bot Local Setup
---

## Introduction

This workspace contains two services:
- `backend/` - Postgres backend
- `telegram/` - Telegram bot (requires the backend)

This guide walks through setting up both services locally for development.

## Requirements

- Docker (for Postgres, Redis and InfluxDB)
- `bun` installed for `backend/`
- `pnpm` installed for `telegram/`
- A Telegram bot token from BotFather
- Optional: `data-peek` or any Postgres client to inspect/manipulate the DB

## Start supporting services

### Postgres

Postgres is required for the backend. You can run it in a Docker container:

```powershell
docker run --name pn-backend-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=polinetwork_backend -p 5432:5432 -d postgres:15
```

### Redis + InfluxDB

> Note: You must modify the `docker-compose.yml` to set authentication for Redis and InfluxDB parameters you define in your `telegram/.env` file.

The Telegram repo already contains a `docker-compose.yml` for Redis and InfluxDB.

From the repo root:

```powershell
cd telegram
docker compose up -d
```

This will start:
- Redis on `6379`
- InfluxDB on `8086`

## Backend setup

### Create backend `.env`

Copy `backend/.env.example` to `backend/.env` and fill values.

Required values for local dev:

```env
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=postgres
DB_PASS=postgres
DB_NAME=polinetwork_backend
ENCRYPTION_KEY=<64+ hex chars>
BETTER_AUTH_SECRET=<40 hex chars>
AZURE_TENANT_ID=dummy
AZURE_CLIENT_ID=dummy
AZURE_CLIENT_SECRET=dummy
AZURE_EMAIL_SENDER=noreply@polinetwork.org
AZURE_BLOB_STORAGE_ACCOUNT=polinetworksa
AZURE_BLOB_STORAGE_CONTAINER=file-blobs
```

The Azure env values are currently required by `backend/src/env.ts` but you can use dummy values for local development if you do not intend to use Azure features.

#### Generate secrets

Generate the `ENCRYPTION_KEY` and `BETTER_AUTH_SECRET` values.

If you have `openssl` installed:

```powershell
openssl rand -hex 32
openssl rand -hex 20
```

Or with Python:

```powershell
python -c "import os, binascii; print(binascii.hexlify(os.urandom(32)).decode())"
python -c "import os, binascii; print(binascii.hexlify(os.urandom(20)).decode())"
```

### Install and run backend

```powershell
cd backend
bun install
bun db:migrate
bun dev
```

The backend should start on `http://localhost:3000`.

## Telegram bot setup

### Create telegram `.env`

Copy `telegram/.env.example` to `telegram/.env` and fill values.

Required values:

```env
BOT_TOKEN=<telegram bot token>

REDIS_HOST=127.0.0.1
REDIS_PORT=6379
BACKEND_URL=localhost:3000
REDIS_USERNAME=default
REDIS_PASSWORD=<your Redis password>
INFLUXDB_TOKEN=<32+ char token>
INFLUXDB_URL=http://localhost:8086
```

### Install and run bot

```powershell
cd telegram
pnpm install
pnpm run dev
```

The bot will connect to Redis and the backend. It uses `http://${BACKEND_URL}/api/trpc` internally.

## Role / permission setup

The bot uses the backend role system. Telegram users are stored in `tg.users`, but permissions are stored separately in `tg.permissions`.

If you want owner/admin access immediately, insert or update a row in `tg.permissions` for your Telegram user ID.

Example SQL:

```sql
INSERT INTO tg.permissions (user_id, roles, added_by)
VALUES (<YOUR_TELEGRAM_ID>, ARRAY['owner'], <YOUR_TELEGRAM_ID>)
ON CONFLICT (user_id) DO UPDATE
SET roles = ARRAY['owner'], added_by = <YOUR_TELEGRAM_ID>;
```

If you already interacted with the bot, your `tg.users` row should exist. If not, send a message to the bot so it creates your user row.

### Recommended manual data access

Use `data-peek` or any Postgres client with this DSN:

```text
postgresql://postgres:postgres@127.0.0.1:5432/polinetwork_backend
```

Inspect:
- `tg.permissions`
- `tg.users`
- other `tg.*` tables if needed

## Notes

- `telegram/` must be able to reach the backend at `localhost:3000`.
- `backend/` uses `USE_DEV_MAILER=true` by default in development, so Azure email sending is not required for local testing.
- If you use dummy Azure authentication values, the backend will still try to authenticate with Azure, which will fail. This is expected and non-fatal for local development. The backend will still run, and the bot can connect to it.
- The bot must be admin in the Telegram group/channel it is used in, otherwise it will not be able to perform moderation actions.

## Quick checklist

1. Docker Postgres running
2. Docker Redis running
3. `backend/.env` configured
4. `bun install` + `bun db:migrate` + `bun dev`
5. `telegram/.env` configured
6. `pnpm install` + `pnpm run dev`
7. `tg.permissions` row exists for your Telegram ID



## Optional automation script

You can automate the local startup for Postgres, Redis, InfluxDB, the backend, and the bot by saving the following shell script and running it from a root directory containing the `telegram` and `backend` directories.

```bash
#!/usr/bin/env bash
set -euo pipefail
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Detect OS environment
IS_WINDOWS=false
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
IS_WINDOWS=true
fi

# --- Pre-flight Checks ---
echo "Checking required system dependencies..."
for cmd in docker bun pnpm curl; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: Required command '$cmd' is not installed or not in your PATH." >&2
exit 1
fi
done

# --- Helper Functions ---
wait_for_port() {
local host="${1:-127.0.0.1}"
local port="${2}"
local timeout="${3:-60}"
local end=$((SECONDS + timeout))

while (( SECONDS < end )); do
if command -v nc &> /dev/null; then
if nc -z "$host" "$port" >/dev/null 2>&1; then return 0; fi
elif bash -c "cat < /dev/null > /dev/tcp/${host}/${port}" >/dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}

wait_for_http() {
local url="${1}"
local timeout="${2:-60}"
local end=$((SECONDS + timeout))

while (( SECONDS < end )); do
if curl -fsSL --max-time 3 "$url" >/dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}

kill_process_on_port() {
local port="${1}"

if [ "$IS_WINDOWS" = true ]; then
if command -v netstat.exe &> /dev/null; then
local win_pid
win_pid=$(netstat.exe -ano | grep "LISTENING" | grep ":$port " | awk '{print $5}' | head -n 1 || true)
if [ -n "$win_pid" ] && [ "$win_pid" -gt 0 ] 2>/dev/null; then
echo "Port $port occupied on Windows. Cleaning up PID $win_pid..."
taskkill.exe /F /PID "$win_pid" >/dev/null 2>&1 || true
fi
fi
else
if command -v lsof &> /dev/null; then
local pid
pid=$(lsof -t -i:"$port" || true)
if [ -n "$pid" ]; then
echo "Port $port occupied. Cleaning up process $pid..."
kill -9 "$pid" 2>/dev/null || true
fi
fi
fi
}

# --- Environment Cleanup ---
echo "Cleaning up local ports..."
kill_process_on_port 3000

Comment on lines +265 to +268

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid killing arbitrary listeners on port 3000.

This can terminate an unrelated local app that happens to be using the same port. For an optional setup script, that’s too destructive; restrict cleanup to the processes this script started, or prompt before force-killing anything.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/infrastructure/03-Guides/telegram-bot-local-setup.md` around lines 265 -
268, The kill_process_on_port function call with port 3000 will terminate any
process on that port, not just the ones started by this script, which is
destructive for an optional setup script. Modify the cleanup section to either
store the PID of the process started by this script (such as the telegram bot
service) when it launches and kill only that specific process by PID during
cleanup, or add a confirmation prompt before force-killing anything on port 3000
to prevent accidentally terminating unrelated applications.

# --- Infrastructure Setup ---
echo "Starting Postgres container..."
if ! docker ps -a --format '{{.Names}}' | grep -xq 'pn-backend-postgres'; then
docker run --name pn-backend-postgres \
-e POSTGRES_USER="${POSTGRES_USER:-postgres}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-postgres}" \
-e POSTGRES_DB="${POSTGRES_DB:-polinetwork_backend}" \
-p 5432:5432 \
-d postgres:15
else
docker start pn-backend-postgres >/dev/null
fi

echo "Starting Redis + InfluxDB via telegram/docker-compose.yml..."
pushd "$SCRIPT_ROOT/telegram" >/dev/null
docker compose up -d
popd >/dev/null

echo "Waiting for Docker services to be reachable..."
if ! wait_for_port 127.0.0.1 5432 60; then
echo "Warning: Postgres not responding on 127.0.0.1:5432" >&2
fi
if ! wait_for_port 127.0.0.1 6379 60; then
echo "Warning: Redis not responding on 127.0.0.1:6379" >&2
fi
if ! wait_for_http http://127.0.0.1:8086/health 60; then
echo "Warning: InfluxDB not responding on http://127.0.0.1:8086/health" >&2
fi
Comment on lines +287 to +296

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fail fast when a prerequisite service never becomes ready.

The script only logs warnings for Postgres/Redis/InfluxDB/backend timeouts and then keeps going into migrations or bot startup. That turns the real failure into a later, harder-to-diagnose crash. Exit non-zero here instead of continuing.

Also applies to: 328-331

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/infrastructure/03-Guides/telegram-bot-local-setup.md` around lines 287 -
296, The service readiness checks for Postgres (wait_for_port 127.0.0.1 5432),
Redis (wait_for_port 127.0.0.1 6379), and InfluxDB (wait_for_http
http://127.0.0.1:8086/health) currently only log warnings when services fail to
respond and allow the script to continue. Instead of continuing on failure,
modify these checks to exit the script with a non-zero status code when any
critical service fails to become ready within the timeout period. This will
ensure failures are caught early and reported at the point of failure rather
than causing cryptic errors later during migrations or bot startup. Also apply
this same fix to the similar service checks mentioned at lines 328-331.


echo "Waiting for Redis initialization..."
REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E 'redis_db' | head -n 1 || true)
if [ -z "$REDIS_CONTAINER" ]; then
REDIS_CONTAINER="redis_db"
fi

for i in {1..60}; do
if docker exec "$REDIS_CONTAINER" redis-cli -a {your_redis_password} ping >/dev/null 2>&1; then
break
Comment on lines +298 to +306

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fix the Redis readiness probe.

redis-cli is using a literal {your_redis_password} placeholder, so this check will never authenticate in the documented setup. It also assumes a redis_db container name that may not match the Compose service.

Suggested fix
-REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E 'redis_db' | head -n 1 || true)
-if [ -z "$REDIS_CONTAINER" ]; then
-  REDIS_CONTAINER="redis_db"
-fi
+REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E 'redis_db' | head -n 1 || true)
+if [ -z "$REDIS_CONTAINER" ]; then
+  echo "Warning: could not identify the Redis container" >&2
+fi
@@
-  if docker exec "$REDIS_CONTAINER" redis-cli -a {your_redis_password} ping >/dev/null 2>&1; then
+  if docker exec "$REDIS_CONTAINER" redis-cli -a "$REDIS_PASSWORD" ping >/dev/null 2>&1; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/infrastructure/03-Guides/telegram-bot-local-setup.md` around lines 298 -
306, The Redis readiness probe in the bash script snippet uses a literal
placeholder `{your_redis_password}` instead of an actual password variable,
which will cause authentication to fail. Replace the hardcoded placeholder
`{your_redis_password}` in the redis-cli command with an environment variable
like `$REDIS_PASSWORD` so that the actual password is used during
authentication. Additionally, document that users need to set this environment
variable before running the script, or consider using the Redis service name
from the Docker Compose configuration instead of assuming the container name is
`redis_db`.

fi
sleep 1
if [ "$i" -eq 60 ]; then
echo "Warning: Redis ping did not succeed after 60 seconds" >&2
fi
done

# --- Application Bootstrapping ---
echo "Preparing backend (install + migrate)..."
pushd "$SCRIPT_ROOT/backend" >/dev/null
bun install
bun db:migrate
popd >/dev/null

echo "Launching backend in background (logs: backend.log)..."

cd "$SCRIPT_ROOT/backend"
nohup bun dev > "$SCRIPT_ROOT/backend.log" 2>&1 </dev/null &
backend_pid=$!
cd "$SCRIPT_ROOT"

echo "Waiting for backend HTTP endpoint http://localhost:3000/ to respond..."
if ! wait_for_http http://localhost:3000/ 60; then
echo "Warning: Backend did not respond on http://localhost:3000 within timeout" >&2
fi

echo "Preparing bot dependencies..."
pushd "$SCRIPT_ROOT/telegram" >/dev/null
pnpm install
popd >/dev/null

echo "Launching bot in background (logs: bot.log)..."
cd "$SCRIPT_ROOT/telegram"
nohup pnpm run dev > "$SCRIPT_ROOT/bot.log" 2>&1 </dev/null &
bot_pid=$!
cd "$SCRIPT_ROOT"

echo "All done! Setup successfully initialized cross-platform."
echo "-------------------------------------------------------"
echo "Backend PID: $backend_pid (Logs: tail -f backend.log)"
echo "Bot PID: $bot_pid (Logs: tail -f bot.log)"
echo "-------------------------------------------------------"
```

> Note: This script requires a POSIX shell environment with `bash`, `curl`, and Docker available. On Windows, use WSL, Git Bash, or a Linux-compatible shell.
Loading