Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - prettyleaf/telegram-support-bot: A Telegram bot for managing support tickets using forum threads · GitHub
Skip to content

Repository files navigation

🎫 Support Ticket Bot

A Telegram bot for managing support tickets using forum threads. Each ticket is created as a separate topic in a Telegram forum (supergroup with topics enabled), allowing organized support conversations.

Features

  • Thread-based Tickets: Each ticket is a separate forum topic for organized conversations
  • Admin-only Access: Only configured admins can manage and respond to tickets
  • Ticket Management: Open, close, and reopen tickets with ease
  • Priority System: Set ticket priorities (low, normal, high, urgent)
  • User Ban System: Ban abusive users from creating tickets
  • Internal Notes: Add private notes visible only to admins
  • Media Support: Forward photos, documents, videos, voice messages, and more
  • Canned Responses: Create and send predefined responses for common questions
  • Language Selection: English and Russian user flows
  • Ticket Stats: View ticket activity summaries from admin commands
  • Automatic Ticket Welcome: Send a configurable first support reply on new tickets
  • Business Hours Notice: Optionally warn users when a ticket is opened outside support hours
  • Onboarding Captcha: Require a verification step on first bot launch, with generated or custom questions
  • HWID Device Flow: Detect HWID/device-limit questions, auto-escalate the ticket, and let users remove panel devices with a cooldown
  • Web Admin Panel: React + TypeScript admin site for ticket triage, replies, notes, canned responses, bans, and auth management

Deployment Image Workflow

The repository includes a GitHub Actions workflow at .github/workflows/docker-image-tar.yml that builds two Docker image tarballs:

  • support-ticket-bot-<VERSION>-linux-amd64.tar
  • support-ticket-bot-<VERSION>-linux-arm64.tar

Release flow

  1. Create and push a Git tag such as 1.2.3:
    git tag 1.2.3
    git push origin 1.2.3
  2. Wait for the Build Docker Image Tar workflow to finish.
  3. Download the tarball that matches the target server architecture from the workflow artifacts or the GitHub Release assets.
  4. Load the image on the target server:
    docker load -i support-ticket-bot-1.2.3-linux-amd64.tar
    For ARM64 servers, use:
    docker load -i support-ticket-bot-1.2.3-linux-arm64.tar
  5. Set BOT_VERSION=1.2.3 in .env.
  6. Start or update the stack:
    docker compose up -d

The Compose file bind-mounts local config.yaml into the container as /app/config.yaml, so config edits do not require rebuilding the image. After changing config.yaml, restart the bot container:

docker compose restart support-ticket-bot

The workflow creates a GitHub Release named <VERSION>, attaches the .tar file and checksum, and uses GitHub's standard Generate release notes output for the release description.

The loaded image tag will be support-ticket-bot:<VERSION>, and docker-compose.yml uses BOT_VERSION to select that exact image version.

Minimum .env example for the image version:

BOT_VERSION=1.2.3

SeaweedFS upload workflow

The repository also includes .github/workflows/release-to-seaweedfs.yml. It runs on every published GitHub Release, downloads the release .tar assets, and uploads them to SeaweedFS through its S3-compatible endpoint.

Required repository secrets:

  • SEAWEEDFS_S3_ENDPOINT
  • SEAWEEDFS_S3_BUCKET
  • SEAWEEDFS_S3_ACCESS_KEY_ID
  • SEAWEEDFS_S3_SECRET_ACCESS_KEY

Optional repository secrets:

  • SEAWEEDFS_S3_REGION (defaults to us-east-1 when empty)
  • SEAWEEDFS_S3_PREFIX (for example telegram-support-bot/releases)

Uploaded object path format:

s3://<bucket>/<prefix>/<version>/<filename>.tar

Web Admin Panel

The repository now includes a built-in admin panel under web/admin-ui and a Go HTTP server under internal/web.

For local source-based runs outside Docker, build the frontend once before starting the bot:

cd web/admin-ui
npm install
npm run build

What the panel covers

  • Dashboard summary for ticket activity
  • Ticket list with open/all/closed filters
  • Ticket detail view with replies, priority changes, close/reopen, notes, and HTML export
  • Canned responses management
  • Banned users management
  • Authentication and passkey management

Supported authentication methods

Only these methods are enabled in the codebase:

  • Password
  • Passkey
  • Telegram Login Widget
  • GitHub OAuth

No generic OAuth, Keycloak, Pocket ID, or Yandex auth is included.

Environment variables

WEB_ADMIN_ENABLED=trueWEB_ADMIN_LISTEN_ADDR=:8080WEB_ADMIN_PUBLIC_URL=https://support.example.com# Optional. Defaults to the path part of WEB_ADMIN_PUBLIC_URL, or / when there is none.# WEB_ADMIN_BASE_PATH=/WEB_ADMIN_SESSION_COOKIE_NAME=support_admin_sessionWEB_ADMIN_SESSION_TTL_HOURS=168WEB_AUTH_PASSWORD_ENABLED=trueWEB_AUTH_PASSWORD_USERS=admin:$2a$10$replace_with_bcrypt_hashWEB_AUTH_PASSKEY_ENABLED=trueWEB_AUTH_PASSKEY_DISPLAY_NAME=Support Ticket Bot AdminWEB_AUTH_GITHUB_ENABLED=trueWEB_AUTH_GITHUB_CLIENT_ID=your_github_client_idWEB_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secretWEB_AUTH_GITHUB_ALLOWED_EMAILS=ops@example.comWEB_AUTH_GITHUB_ALLOWED_LOGINS=your-github-loginWEB_AUTH_TELEGRAM_ENABLED=trueWEB_AUTH_TELEGRAM_ALLOWED_IDS=123456789,987654321VALKEY_ADDR=/run/support-ticket-valkey/valkey.sockVALKEY_PASSWORD=VALKEY_DB=0

Notes:

  • WEB_ADMIN_PUBLIC_URL should be the public admin URL that users actually open in the browser.
  • WEB_ADMIN_BASE_PATH is optional. If you omit it, the app uses the path from WEB_ADMIN_PUBLIC_URL; if the URL has no path, the admin UI is served from /.
  • Passkeys derive RP ID and allowed origin from WEB_ADMIN_PUBLIC_URL.
  • GitHub auth requires at least one allowlist entry in WEB_AUTH_GITHUB_ALLOWED_EMAILS or WEB_AUTH_GITHUB_ALLOWED_LOGINS.
  • If WEB_AUTH_TELEGRAM_ALLOWED_IDS is empty, ADMIN_IDS is used automatically.
  • Password auth expects bcrypt hashes in WEB_AUTH_PASSWORD_USERS.

Reverse proxy

The bot container listens on :8080 for the web admin. In Docker Compose the service is reachable on the shared Docker network as http://support-ticket-bot:8080.

Always proxy /api/admin/ to the bot container.

For the frontend path:

  • proxy / when WEB_ADMIN_PUBLIC_URL is on the domain root or WEB_ADMIN_BASE_PATH=/
  • proxy /admin/ when WEB_ADMIN_PUBLIC_URL uses /admin

If SWAG runs in a different Compose stack, attach it to the same Docker network first. This repository now creates that network with the fixed name support-ticket-bot, so SWAG can join it as an external network:

networks:
support-ticket-bot:
external: true

Then add that network to the SWAG service and use proxy_pass http://support-ticket-bot:8080;.

Example Nginx location blocks for root deployment:

location / {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Example Nginx location blocks for /admin deployment:

location /admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /api/admin/ {proxy_passhttp://support-ticket-bot:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}

Valkey

The Docker Compose stack now always starts a bundled Valkey container. It is used for:

  • web admin sessions
  • GitHub OAuth state
  • passkey registration/login ceremony state

Recommended VALKEY_ADDR inside Docker is the shared UNIX socket path:

VALKEY_ADDR=/run/support-ticket-valkey/valkey.sock

The bundled Valkey service also keeps TCP on support-ticket-valkey:6379, so existing configs continue to work. If VALKEY_ADDR is empty, the bot still falls back to an in-memory store.

Commands

User Commands (in private chat with bot)

CommandDescription
/startStart the bot and see welcome message
/newticketCreate a new support ticket
/myticketsView your tickets
/cancelCancel current operation
/languageChange your language

Admin Commands

CommandDescription
/helpShow admin help
/ticketsList all open tickets
/closeClose current ticket (in ticket thread)
/reopenReopen closed ticket (in ticket thread)
/priority [level]Set ticket priority (low/normal/high/urgent)
/infoView ticket details
/ban [user_id] [reason]Ban user from creating tickets
/unban [user_id]Unban user
/bannedList all banned users
/note [text]Add internal note (admin-only)
/notesView ticket notes
/stats [days]View ticket statistics
/cannedList canned responses
/addcanned [shortcut] [title] | [content]Add a canned response
/delcanned [shortcut]Delete a canned response
/[shortcut]Send a canned response inside a ticket thread

Configuration (config.yaml)

bot:
log_level: infotickets:
auto_close_hours: 0# Auto-close after X hours (0 = disabled)max_open_per_user: 3# Maximum open tickets per usernumber_prefix: "TKT"# Ticket number prefixpriority_enabled: truedefault_priority: normalcaptcha:
enabled: truetype: custom # math, button, text, customtimeout_seconds: 120questions:
- question: "How much is 2 + 2?"correct_answer: "4"options: ["3", "4", "5", "6"]
- question: "Tap the word Support"correct_answer: "Support"options: ["Ticket", "Support", "Panel", "Hello"]hwid:
enabled: truedelete_cooldown_hours: 24trigger_keywords:
- "hwid"
- "хвид"
- "удалить устройство"
- "лимит устройств"
- "delete device"
- "device limit"business_hours:
enabled: truetimezone: "Europe/Moscow"start: "10:00"end: "19:00"messages:
welcome: | 👋 Welcome to Support! Send me a message to create a ticket.ticket_created: | ✅ Ticket #{ticket_number} created!ticket_welcome: | 👋 <b>Hello!</b> Your ticket #{ticket_number} is now open. You can send any extra details in this chat at any time.ticket_welcome_ru: | 👋 <b>Здравствуйте!</b> Ваш тикет #{ticket_number} открыт. Вы можете отправить дополнительные детали в этом чате в любое время.ticket_out_of_hours: | 🌙 <b>We are currently outside business hours.</b> We received your ticket #{ticket_number} and will reply during working hours. <b>Support hours:</b> <code>{working_hours}</code>.ticket_out_of_hours_ru: | 🌙 <b>Сейчас нерабочее время.</b> Мы получили ваш тикет #{ticket_number} и ответим в рабочие часы. <b>Часы поддержки:</b> <code>{working_hours}</code>.ticket_closed: | 🔒 Ticket #{ticket_number} has been closed.

Optional .env values for panel integration:

REMNAWAVE_URL=https://panel.example.comREMNAWAVE_TOKEN=your_remnawave_token_hereREMNAWAVE_MODE=remoteREMNAWAVE_HEADERS=

Database Migrations

The bot uses golang-migrate for database schema management. Migrations run automatically on startup.

Migration Files

Located in internal/database/migrations/:

000001_initial_schema.up.sql # Creates tables
000001_initial_schema.down.sql # Drops tables (rollback)
000002_sync_tickets_id_sequence.up.sql
000002_sync_tickets_id_sequence.down.sql
000003_add_hwid_delete_tracking.up.sql
000003_add_hwid_delete_tracking.down.sql
000004_add_user_onboarding.up.sql
000004_add_user_onboarding.down.sql

Adding New Migrations

  1. Create a new migration file pair:

    000002_add_feature.up.sql
    000002_add_feature.down.sql
    
  2. Write your SQL changes in the .up.sql file

  3. Write the rollback in the .down.sql file

  4. Restart the bot - migrations run automatically

Manual Migration Commands

If you need to manage migrations manually:

# Install migrate CLI
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Run migrations up
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up
# Rollback one migration
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1
# Check current version
migrate -path internal/database/migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" version

About

A Telegram bot for managing support tickets using forum threads

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages