Telegram: @yv2t_bot
- Video download
- Audio download
- Playlist download
- Language selection
- Skip download param
- Random media
- Media crop
- Stats
- Exclude domains list
This guide assumes Kubernetes, Helm, and a working image registry. If you are deploying from this repository instead of using already-published images, build and push dev images first.
Required local tools:
kubectlhelmjust
Check local tools:
kubectl version --client
helm version
just --versionCluster components:
cert-manager- CloudNativePG
1.26+; use1.29+for new installs - Barman Cloud CNPG-I Plugin installed in the same namespace as the CloudNativePG operator
- Valkey operator (
valkey-operator) for the durable download queue
Install cert-manager:
helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.20.2 \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait
kubectl rollout status deployment/cert-manager -n cert-manager
kubectl rollout status deployment/cert-manager-webhook -n cert-manager
kubectl rollout status deployment/cert-manager-cainjector -n cert-managerInstall CloudNativePG:
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.0.yaml
kubectl rollout status deployment/cnpg-controller-manager -n cnpg-systemInstall the Barman Cloud CNPG-I Plugin:
kubectl get deployment -n cnpg-system cnpg-controller-manager \
-o jsonpath="{.spec.template.spec.containers[*].image}{'\n'}"
kubectl apply -f \
https://github.com/cloudnative-pg/plugin-barman-cloud/releases/download/v0.12.0/manifest.yaml
kubectl rollout status deployment/barman-cloud -n cnpg-systemInstall the Valkey operator (backs the durable download queue):
helm repo add valkey https://valkey.io/valkey-helm/
helm repo update
helm install valkey-operator valkey/valkey-operator \
-n valkey-operator-system --create-namespace
kubectl get pods -n valkey-operator-system -l app.kubernetes.io/instance=valkey-operatorFinal checks:
kubectl get crd certificates.cert-manager.io
kubectl get crd clusters.postgresql.cnpg.io
kubectl get crd objectstores.barmancloud.cnpg.io
kubectl get crd valkeyclusters.valkey.io
kubectl get deploy -n cert-manager
kubectl get deploy -n cnpg-system
kubectl get deploy -n valkey-operator-systemexport NAMESPACE=dev
kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -Use NAMESPACE=prod for production.
Create these once. These commands are safe to re-run because they render YAML client-side and apply it.
kubectl -n "${NAMESPACE}" create secret generic telegram-bot-api \
--from-literal=api_id='<telegram api id>' \
--from-literal=hash='<telegram api hash>' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n "${NAMESPACE}" create secret generic db \
--from-literal=username='admin' \
--from-literal=password='<db password>' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n "${NAMESPACE}" create secret generic db-superuser \
--from-literal=username='postgres' \
--from-literal=password='<db superuser password>' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n "${NAMESPACE}" create secret generic s3 \
--from-literal=access-key-id='rustfsadmin' \
--from-literal=secret-access-key='<at least 8 characters>' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n "${NAMESPACE}" create secret generic valkey \
--from-literal=password='<valkey password>' \
--dry-run=client -o yaml | kubectl apply -f -The s3 Secret is used by the internal RustFS backup store and CNPG backup plugin. The chart creates a single-node RustFS instance and bootstraps the backups bucket automatically.
Create local config files if they do not exist:
cp -n configs/config.example.toml configs/config.toml
cp -n configs/downloader.example.toml configs/downloader.toml
cp -n configs/cookie_assignment.example.toml configs/cookie_assignment.tomlEdit these files before installing:
configs/config.tomlconfigs/downloader.tomlconfigs/cookie_assignment.toml
Required config checks:
configs/config.tomlhas the real Telegram bot token.- Database credentials in
configs/config.tomlmatch thedbSecret. - Normal downloader token from
configs/config.toml[download].node_tokenis listed inconfigs/downloader.toml[auth].node_tokens. - Cookie-manager token matches in
configs/downloader.toml[auth].cookie_manager_tokenandconfigs/cookie_assignment.toml[download].cookie_manager_token. configs/config.toml[redis].hostmatches the Valkey service name (valkeywith the bundled operator; confirm withkubectl get svc -n "${NAMESPACE}"after theValkeyClusterreconciles).configs/config.toml[redis].userisadminand[redis].passwordmatches thevalkeySecret'spassword.- Default in-cluster service URLs are correct if all charts are installed into the same namespace.
Cookie files are optional. Put them here if you have them:
cookies/<domain>/<cookie-id>.txt
The sync helper also creates an empty cookie Secret when no cookie files exist.
Install order matters:
just helm-install-infra "${NAMESPACE}"
just k8s-update-bot-config "${NAMESPACE}"
just helm-install-bot "${NAMESPACE}"
just k8s-migration "${NAMESPACE}"
just k8s-update-downloader-config "${NAMESPACE}"
just helm-install-downloader "${NAMESPACE}"
just k8s-update-cookie-assignment-config "${NAMESPACE}"
just k8s-sync-cookie-assignment-cookies "${NAMESPACE}"
just helm-install-cookie-assignment "${NAMESPACE}"Notes:
infracreates the shared internal CA issuer.botcreates PostgreSQL, RustFS, Telegram Bot API, yt-toolkit, and the bot Deployment.k8s-migrationruns DB migrations after PostgreSQL exists.- The bot pod can restart until PostgreSQL is ready and migrations have run.
downloadercreates the headless downloader service and worker pods.cookie-assignmentdistributes cookies to downloader nodes and is safe with an empty cookie inventory.
Another bot can reuse the same downloader nodes without using the Telegram bot crate.
- Create a new bot crate, for example
bot_discord, and depend ondownloader_clientdirectly. - Create a separate chart for the new bot, usually by copying
charts/botonly as a starting point and removing Telegram-specific resources. - Give the new bot its own config Secret and client TLS certificate.
- Put the new bot's normal downloader token in its bot config, then add the same token to downloader config
[auth].node_tokens. - Do not give the new bot
cookie_manager_token; onlycookie_assignmentshould have that token. - Keep cookie distribution in
cookie_assignment; do not add cookie assignment logic to a bot. - PostgreSQL, RustFS, migrations, and upload cache are optional for another bot. A simple bot can use downloader nodes without any cache.
- If another bot has a cache, design that cache for that messenger. Do not blindly reuse the Telegram
file_idcache model.
kubectl get pods -n "${NAMESPACE}"
kubectl get certificates -n "${NAMESPACE}"
kubectl get clusters.postgresql.cnpg.io -n "${NAMESPACE}"
kubectl get backups.postgresql.cnpg.io -n "${NAMESPACE}"
kubectl get scheduledbackups.postgresql.cnpg.io -n "${NAMESPACE}"
kubectl get objectstores.barmancloud.cnpg.io -n "${NAMESPACE}"
kubectl get valkeyclusters.valkey.io -n "${NAMESPACE}"
kubectl get secret -n "${NAMESPACE}" telegram-bot-api db db-superuser s3 valkey bot-config downloader-config cookie-assignment-config cookie-assignment-cookiesLogs:
just k8s-logs-bot "${NAMESPACE}"
just k8s-logs-downloader "${NAMESPACE}"
just k8s-logs-cookie-assignment "${NAMESPACE}"Manual backup check:
kubectl -n "${NAMESPACE}" delete backup postgres-manual-backup --ignore-not-found
cat > /tmp/postgres-manual-backup.yaml <<'EOF'
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: postgres-manual-backup
spec:
cluster:
name: postgres
method: plugin
pluginConfiguration:
name: barman-cloud.cloudnative-pg.io
EOF
kubectl -n "${NAMESPACE}" create -f /tmp/postgres-manual-backup.yaml
kubectl -n "${NAMESPACE}" get backup postgres-manual-backup -wThis project does not require you to manually set Barman environment variables inside the PostgreSQL pod.
The bot chart applies backup configuration in three steps:
- The
s3Secret provides credentials:access-key-idsecret-access-key
charts/bot/templates/postgres-backup-object-store.yamlcreates abarmancloud.cnpg.io/v1ObjectStoreusing:s3.endpointURLs3.destinationPath- credentials from the
s3Secret
charts/bot/templates/postgres-cluster.yamlattaches that object store to thepostgrescluster throughspec.plugins, andcharts/bot/templates/postgres-scheduled-backup.yamlcreates the scheduled backup.
Current defaults from charts/bot/values.yaml:
- object store name:
postgres-backup-store - destination path:
s3://backups/ - endpoint URL:
http://s3:9000 - backup schedule:
0 0 3 * * *
To inspect the applied backup config:
kubectl -n "${NAMESPACE}" get objectstores.barmancloud.cnpg.io postgres-backup-store -o yaml
kubectl -n "${NAMESPACE}" get cluster postgres -o yaml
kubectl -n "${NAMESPACE}" get scheduledbackup postgres-backup -o yaml
kubectl -n "${NAMESPACE}" get secret s3 -o yamlIf you change backup credentials or endpoint settings:
- Update the
s3Secret. - Update Helm values if
endpointURL,destinationPath, or object-store naming changed. - Run:
just helm-upgrade-bot "${NAMESPACE}"Use shell exports only for manual S3 checks from your machine or a debug container, for example with mc or aws CLI:
export AWS_ACCESS_KEY_ID='<value from s3 secret access-key-id>'
export AWS_SECRET_ACCESS_KEY='<value from s3 secret secret-access-key>'
export AWS_ENDPOINT_URL='http://s3:9000'Those exports are not consumed by the PostgreSQL pod directly in this setup; they are only for your manual tooling.
In this setup, backups are used for recovery, not for direct browsing from PostgreSQL.
The normal restore flow is:
- Keep the original
postgrescluster untouched. - Create a new cluster, for example
postgres-restore. - Bootstrap that new cluster from the backup object store.
- Connect to the restored cluster and inspect or export data from it.
Important:
- Recovery is not in-place. Do not try to “restore over” the running
postgrescluster. - The backup object store keeps base backups and WAL archives.
- The restored cluster does not need to archive WALs back to the bucket unless you explicitly configure that.
Example restore manifest for this project:
cat > /tmp/postgres-restore.yaml <<'EOF'
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-restore
spec:
instances: 1
storage:
size: 3Gi
superuserSecret:
name: db-superuser
bootstrap:
recovery:
source: origin
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: postgres-backup-store
serverName: postgres
EOF
kubectl apply -n "${NAMESPACE}" -f /tmp/postgres-restore.yaml
kubectl get clusters.postgresql.cnpg.io -n "${NAMESPACE}"
kubectl get pods -n "${NAMESPACE}" -l cnpg.io/cluster=postgres-restore -wWhen the restored cluster is ready, connect to it:
export RESTORE_POD="$(kubectl get pod -n "${NAMESPACE}" \
-l cnpg.io/cluster=postgres-restore,role=primary \
-o jsonpath='{.items[0].metadata.name}')"
kubectl exec -it -n "${NAMESPACE}" "${RESTORE_POD}" -- psql -U postgres -d apiThen you can:
- inspect tables
- run queries
- export data with
pg_dump - compare restored data with the live cluster
Example pg_dump inside the restored pod:
kubectl exec -it -n "${NAMESPACE}" "${RESTORE_POD}" -- \
pg_dump -U postgres -d api > /tmp/postgres-restore.sqlIf you need point-in-time recovery instead of the latest state, add a recovery target:
bootstrap:
recovery:
source: origin
recoveryTarget:
targetTime: "2026-04-16T21:55:00Z"If you no longer need the restored cluster:
kubectl delete cluster postgres-restore -n "${NAMESPACE}"This is a different flow from Barman recovery.
Use this when you already have a dump file outside CNPG/Barman, for example from pg_dump.
Use a temporary cluster if possible. Do not import unknown dumps directly into the live postgres cluster.
Plain SQL dump:
kubectl exec -i -n "${NAMESPACE}" -c postgres "${RESTORE_POD}" -- \
psql -U postgres -d api < /path/to/dump.sqlGzipped SQL dump:
gunzip -c /path/to/dump.sql.gz | \
kubectl exec -i -n "${NAMESPACE}" -c postgres "${RESTORE_POD}" -- \
psql -U postgres -d apiCustom-format dump created with pg_dump -Fc:
cat /path/to/dump.dump | \
kubectl exec -i -n "${NAMESPACE}" -c postgres "${RESTORE_POD}" -- \
pg_restore -U postgres -d api --clean --if-existsNotes:
- plain
.sqldumps are restored withpsql - custom-format dumps are restored with
pg_restore
Upgrade charts:
just helm-upgrade-infra "${NAMESPACE}"
just helm-upgrade-bot "${NAMESPACE}"
just helm-upgrade-downloader "${NAMESPACE}"
just helm-upgrade-cookie-assignment "${NAMESPACE}"Refresh configs:
just k8s-update-bot-config "${NAMESPACE}"
just k8s-update-downloader-config "${NAMESPACE}"
just k8s-update-cookie-assignment-config "${NAMESPACE}"Refresh cookies:
just k8s-sync-cookie-assignment-cookies "${NAMESPACE}"
just k8s-rollout-cookie-assignment "${NAMESPACE}"Scale downloader nodes:
just scale-downloader "${NAMESPACE}" 3Run migrations:
just k8s-migration "${NAMESPACE}"Run a different migration command:
just k8s-migration "${NAMESPACE}" downUse this when deploying images built from your local checkout.
just docker-push-dev-bot
just docker-push-dev-downloader
just docker-push-dev-cookie-assignment
just docker-push-dev-migrationEach script builds with Docker buildx, pushes to the registry, uses remote cache, and defaults to dev-<git-short-sha>.
- Bot, downloader, and cookie-assignment scripts print the next
helm upgradecommand after a successful push. - The migration script builds from
deployment/Dockerfile.migration.devand prints the nextjust k8s-migrationcommand withIMAGE_REPO,IMAGE_TAG, andPULL_POLICY=Always.
Useful overrides:
NAMESPACEIMAGE_REPOIMAGE_TAGMIGRATION_COMMANDCACHE_REFPLATFORMDOCKERFILECONTEXT_DIRBUILDER_NAME
Example:
export NAMESPACE=dev
export IMAGE_TAG=dev-manual-1
IMAGE_TAG="${IMAGE_TAG}" just docker-push-dev-bot
IMAGE_TAG="${IMAGE_TAG}" just docker-push-dev-downloader
IMAGE_TAG="${IMAGE_TAG}" just docker-push-dev-cookie-assignment
IMAGE_TAG="${IMAGE_TAG}" just docker-push-dev-migrationIf only one component changed, rebuild and upgrade only that component.