chore: sync port references and docs to 4000s infra

This commit is contained in:
asepharyana
2026-08-02 16:19:30 +07:00
parent ad7747bfd4
commit 62021397e0
21 changed files with 123 additions and 70 deletions
@@ -1,5 +1,10 @@
# Telegram Bot Uploader Backend Implementation Plan
> ⚠️ **LEGACY** — Dokumen historis (2026-05-17). Port & infrastruktur sudah berubah:
> produksi kini berjalan di port `4000` (Nix + systemd + Caddy, domain `upload.asepharyana.my.id`)
> dan database via PgBouncer pool `100.121.180.82:6432` (bukan port 5432, bukan localhost).
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Production-ready backend for Telegram file uploader with dual upload methods (bot + HTTP API), PostgreSQL storage, and redirect-based downloads.
@@ -75,9 +80,9 @@ schema.sql
```bash
BOT_TOKEN=isi_token_bot_telegram
STORAGE_CHANNEL_ID=-1001234567890
BASE_URL=https://tele.asepharyana.my.id
DATABASE_URL=postgresql://user:password@localhost:5432/telegram_uploader
PORT=3000
BASE_URL=https://upload.asepharyana.my.id
DATABASE_URL=postgresql://asephs:***@100.121.180.82:6432/uploader
PORT=4000
NODE_ENV=production
LOG_LEVEL=info
RATE_LIMIT_WINDOW_MS=60000
@@ -148,7 +153,7 @@ bun run start # Production mode
## FAQ
**URL permanen maksudnya apa?**
URL backend tetap permanen: `https://tele.asepharyana.my.id/f/{public_id}`
URL backend tetap permanen: `https://upload.asepharyana.my.id/f/{public_id}`
Ini berarti URL service Anda fix, bukan jaminan file Telegram abadi.
## Testing
@@ -250,7 +255,7 @@ export const config = {
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID, 10),
baseUrl: process.env.BASE_URL,
databaseUrl: process.env.DATABASE_URL,
port: parseInt(process.env.PORT, 10) || 3000,
port: parseInt(process.env.PORT, 10) || 4000,
nodeEnv: process.env.NODE_ENV || 'development',
logLevel: process.env.LOG_LEVEL || 'info',
rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000,
@@ -1009,14 +1014,14 @@ bun run dev
- [ ] **Step 2: Test health endpoint**
```bash
curl http://localhost:3000/health
curl http://localhost:4000/health
# Expected: {"status":"ok"}
```
- [ ] **Step 3: Upload test file via HTTP API (multipart)**
```bash
curl -X POST http://localhost:3000/api/upload \
curl -X POST http://localhost:4000/api/upload \
-F "file=@/path/to/testfile.txt" \
-F "fileName=test.txt"
```
@@ -1024,14 +1029,14 @@ curl -X POST http://localhost:3000/api/upload \
- [ ] **Step 4: Check file info endpoint**
```bash
curl http://localhost:3000/file/{public_id}/info
curl http://localhost:4000/file/{public_id}/info
# Expected: JSON with file metadata
```
- [ ] **Step 5: Download redirect**
```bash
curl -I http://localhost:3000/f/{public_id}
curl -I http://localhost:4000/f/{public_id}
# Expected: HTTP 302 with Location header to Telegram CDN
```
@@ -1047,7 +1052,7 @@ curl -I http://localhost:3000/f/{public_id}
- [ ] **Step 7: Test error handling (file too large)**
```bash
curl -X POST http://localhost:3000/api/upload \
curl -X POST http://localhost:4000/api/upload \
-F "file=@/dev/null" \
-H "Content-Length: 10000000000"
# Expected: HTTP 400 with error message
@@ -1057,7 +1062,7 @@ curl -X POST http://localhost:3000/api/upload \
```bash
# Send 31 requests within 1 minute
for i in {1..31}; do curl http://localhost:3000/f/{public_id} & done
for i in {1..31}; do curl http://localhost:4000/f/{public_id} & done
wait
# Expected: First 30 succeed, last one returns 429
```
@@ -1066,7 +1071,7 @@ wait
```bash
# In terminal 1: bun run dev
# In terminal 2: curl http://localhost:3000/health && sleep 0.1 && curl http://localhost:3000/health
# In terminal 2: curl http://localhost:4000/health && sleep 0.1 && curl http://localhost:4000/health
# Send SIGINT to server (Ctrl+C in terminal 1)
# Check if server stops cleanly, logs show shutdown sequence
```
@@ -1,5 +1,11 @@
# Docker, Traefik, and GitHub Actions Deployment Plan
> ⚠️ **LEGACY** — Dokumen historis (2026-05-18). Arsitektur Docker + Traefik +
> GitHub Actions sudah digantikan (2026-08-02) oleh Nix + systemd + Caddy di
> orangevps: port `4000`, domain `upload.asepharyana.my.id`, database via
> PgBouncer pool `100.121.180.82:6432` (bukan 5432/localhost).
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Containerize TeleUploader using Bun, configure Traefik labels for routing `upload.asepharyana.my.id`, and set up full GitHub Actions CI/CD to VPS `45.127.35.244`.
@@ -57,7 +63,7 @@ WORKDIR /usr/src/app
# Set production environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV PORT=4000
# Copy necessary files from builder and repo
COPY --from=builder /usr/src/app/dist/index.js ./dist/index.js
@@ -65,7 +71,7 @@ COPY --from=builder /usr/src/app/package.json ./package.json
COPY schema.sql ./schema.sql
# Expose server port
EXPOSE 3000
EXPOSE 4000
# Start server
CMD ["bun", "dist/index.js"]
@@ -104,7 +110,7 @@ services:
- STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
- BASE_URL=${BASE_URL}
- DATABASE_URL=${DATABASE_URL}
- PORT=3000
- PORT=4000
- NODE_ENV=production
- LOG_LEVEL=info
networks:
@@ -115,7 +121,7 @@ services:
- "traefik.http.routers.teleuploader.entrypoints=websecure"
- "traefik.http.routers.teleuploader.tls=true"
- "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
- "traefik.http.services.teleuploader.loadbalancer.server.port=3000"
- "traefik.http.services.teleuploader.loadbalancer.server.port=4000"
networks:
app-shared-net:
@@ -166,9 +172,9 @@ jobs:
env:
BOT_TOKEN: "mock_token"
STORAGE_CHANNEL_ID: "123456"
BASE_URL: "http://localhost:3000"
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/postgres"
PORT: "3000"
BASE_URL: "http://localhost:4000"
DATABASE_URL: "postgresql://asephs:***@100.121.180.82:6432/postgres"
PORT: "4000"
run: bun run test
build-and-push:
@@ -223,7 +229,7 @@ jobs:
- STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
- BASE_URL=${BASE_URL}
- DATABASE_URL=${DATABASE_URL}
- PORT=3000
- PORT=4000
- NODE_ENV=production
- LOG_LEVEL=info
networks:
@@ -234,7 +240,7 @@ jobs:
- "traefik.http.routers.teleuploader.entrypoints=websecure"
- "traefik.http.routers.teleuploader.tls=true"
- "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
- "traefik.http.services.teleuploader.loadbalancer.server.port=3000"
- "traefik.http.services.teleuploader.loadbalancer.server.port=4000"
networks:
app-shared-net:
@@ -248,7 +254,7 @@ jobs:
STORAGE_CHANNEL_ID=${{ secrets.STORAGE_CHANNEL_ID }}
BASE_URL=${{ secrets.BASE_URL }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
PORT=3000
PORT=4000
EOF
# Pull latest docker image
@@ -1,5 +1,11 @@
# S3-Compatible TeleUploader Implementation Plan
> ⚠️ **LEGACY** — Dokumen historis (2026-07-06). Port & infrastruktur sudah berubah:
> produksi kini berjalan di port `4000` (Nix + systemd + Caddy, domain
> `upload.asepharyana.my.id`), database via PgBouncer pool `100.121.180.82:6432`
> (bukan 5432/localhost). Contoh kode di bawah memakai `localhost:4000` untuk dev.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Transform TeleUploader into an S3-compatible storage server (Telegram-backed) with a web file manager UI.
@@ -2675,7 +2681,7 @@ describe('S3 Bucket Operations', () => {
process.env.S3_DEFAULT_REGION = 'us-east-1';
process.env.BOT_TOKEN = '123456:ABC-DEF';
process.env.STORAGE_CHANNEL_ID = '-1001234567890';
process.env.BASE_URL = 'http://localhost:3000';
process.env.BASE_URL = 'http://localhost:4000';
process.env.DATABASE_URL = 'postgresql://localhost/test';
});
@@ -2684,7 +2690,7 @@ describe('S3 Bucket Operations', () => {
});
it('should return 403 for unauthorized requests', async () => {
const req = new Request('http://localhost:3000/', {
const req = new Request('http://localhost:4000/', {
method: 'GET',
headers: { authorization: 'Invalid' },
});
@@ -2849,7 +2855,7 @@ describe('Web API v1', () => {
mockDbExecute.mockClear();
process.env.BOT_TOKEN = '123456:ABC-DEF';
process.env.STORAGE_CHANNEL_ID = '-1001234567890';
process.env.BASE_URL = 'http://localhost:3000';
process.env.BASE_URL = 'http://localhost:4000';
process.env.DATABASE_URL = 'postgresql://localhost/test';
});
@@ -2858,7 +2864,7 @@ describe('Web API v1', () => {
});
it('should list buckets via GET /api/v1/buckets', async () => {
const req = new Request('http://localhost:3000/api/v1/buckets');
const req = new Request('http://localhost:4000/api/v1/buckets');
const res = await handleWebApiV1(req);
expect(res.status).toBe(200);
const data = await res.json();
@@ -2867,7 +2873,7 @@ describe('Web API v1', () => {
});
it('should return 404 for unknown API path', async () => {
const req = new Request('http://localhost:3000/api/v1/unknown');
const req = new Request('http://localhost:4000/api/v1/unknown');
const res = await handleWebApiV1(req);
expect(res.status).toBe(404);
const data = await res.json();
@@ -2875,7 +2881,7 @@ describe('Web API v1', () => {
});
it('should return bucket object listing', async () => {
const req = new Request('http://localhost:3000/api/v1/buckets/test-bucket/objects?prefix=');
const req = new Request('http://localhost:4000/api/v1/buckets/test-bucket/objects?prefix=');
const res = await handleWebApiV1(req);
// Should return 200 even with empty results
expect(res.status).toBe(200);
@@ -2885,7 +2891,7 @@ describe('Web API v1', () => {
});
it('should reject invalid bucket name on create', async () => {
const req = new Request('http://localhost:3000/api/v1/buckets', {
const req = new Request('http://localhost:4000/api/v1/buckets', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'INVALID_NAME!' }),
@@ -1,5 +1,10 @@
# Telegram Bot Uploader Backend Design
> ⚠️ **LEGACY** — Dokumen historis (2026-05-17). Port & infrastruktur sudah berubah:
> produksi kini berjalan di port `4000` (Nix + systemd + Caddy, domain `upload.asepharyana.my.id`)
> dan database via PgBouncer pool `100.121.180.82:6432` (bukan port 5432, bukan localhost).
**Date:** 2026-05-17
**Status:** Approved
**Stack:** Bun, Telegraf, PostgreSQL, Drizzle ORM, Winston, nanoid
@@ -35,7 +40,7 @@ Production-ready backend untuk Telegram file uploader dengan dual upload methods
4. Bot extracts `telegram_file_id`, `telegram_file_unique_id`, `storage_message_id`
5. Bot generates `public_id` using nanoid
6. Bot saves metadata to PostgreSQL
7. Bot replies with download link: `https://tele.asepharyana.my.id/f/{public_id}`
7. Bot replies with download link: `https://upload.asepharyana.my.id/f/{public_id}`
#### Upload via HTTP API
1. Client POSTs to `/api/upload` with file (multipart or base64)
@@ -115,7 +120,7 @@ fileName: optional_filename.ext
"size_bytes": 1024000,
"file_type": "document",
"created_at": "2026-05-17T23:42:19Z",
"download_url": "https://tele.asepharyana.my.id/f/abc123xyz"
"download_url": "https://upload.asepharyana.my.id/f/abc123xyz"
}
```
@@ -212,9 +217,9 @@ schema.sql
```
BOT_TOKEN=<telegram_bot_token>
STORAGE_CHANNEL_ID=<private_channel_id>
BASE_URL=https://tele.asepharyana.my.id
DATABASE_URL=postgresql://user:password@localhost:5432/telegram_uploader
PORT=3000
BASE_URL=https://upload.asepharyana.my.id
DATABASE_URL=postgresql://asephs:***@100.121.180.82:6432/uploader
PORT=4000
NODE_ENV=production
LOG_LEVEL=info
RATE_LIMIT_WINDOW_MS=60000
@@ -7,6 +7,12 @@ metadata:
# Design: TeleUploader Deployment & CI/CD Setup
> ⚠️ **LEGACY** — Dokumen historis (2026-05-18) untuk arsitektur Docker + Traefik +
> GitHub Actions. Docker & Traefik sudah dihapus dari VPS produksi (2026-08-02):
> deploy sekarang Nix + systemd + Caddy di orangevps, port `4000`, domain
> `upload.asepharyana.my.id`, database via PgBouncer pool `100.121.180.82:6432`.
We are setting up production deployment for TeleUploader on VPS `45.127.35.244` behind Traefik utilizing GitHub Actions.
## 1. System Architecture
@@ -22,7 +28,7 @@ TeleUploader is a Bun-based service.
### `Dockerfile`
- Multi-stage build.
- **Stage 1 (Build)**: Install dependencies, copy source files, run Biome lint/format checks, compile TS build to `dist/index.js` using `bun build`.
- **Stage 2 (Run)**: Use minimal `oven/bun:1.1-slim` runtime. Copy `dist/index.js`, `schema.sql`, and `package.json`. Expose port `3000`.
- **Stage 2 (Run)**: Use minimal `oven/bun:1.1-slim` runtime. Copy `dist/index.js`, `schema.sql`, and `package.json`. Expose port `4000`.
### `docker-compose.yml`
```yaml
@@ -38,7 +44,7 @@ services:
- STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
- BASE_URL=${BASE_URL}
- DATABASE_URL=${DATABASE_URL}
- PORT=3000
- PORT=4000
- NODE_ENV=production
- LOG_LEVEL=info
networks:
@@ -49,7 +55,7 @@ services:
- "traefik.http.routers.teleuploader.entrypoints=websecure"
- "traefik.http.routers.teleuploader.tls=true"
- "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
- "traefik.http.services.teleuploader.loadbalancer.server.port=3000"
- "traefik.http.services.teleuploader.loadbalancer.server.port=4000"
networks:
app-shared-net: