MinIO S3 Storage
MinIO provides S3-compatible object storage for the Signal bot’s file sharing functionality. When users request file links via !files command, the bot generates presigned URLs that provide direct, time-limited access to files.
Note: RustFS was briefly evaluated (Dec 2025) for its lower memory footprint (~74 MiB vs 16 GiB), but had issues with presigned URL signature validation. MinIO remains the recommended solution.
Overview
Section titled “Overview”| Component | Value |
|---|---|
| Service | MinIO (S3-compatible) |
| Public URL | https://s3.irregular.chat |
| Console URL | https://s3-console.irregular.chat |
| Bucket | irregularchat |
| Link Expiration | 24 hours |
How It Works
Section titled “How It Works”- User searches for files with
!files <query> - Bot returns search results from local file index
- User replies with a file number to get download link
- Bot generates a MinIO presigned URL:
- Constructs S3 path from category + filename
- Signs URL with AWS4-HMAC-SHA256 algorithm
- Sets 24-hour expiration
- Uses public URL for signature so it matches external access
- User receives direct download link
Architecture
Section titled “Architecture”┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ Signal User │─────▶│ Signal Bot │─────▶│ MinIO Server ││ !files drone │ │ minio-client.ts │ │ irregularchat │└─────────────────┘ └──────────────────┘ │ bucket │ │ │ └─────────────────┘ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ Presigned URL │ │ │ │ https://s3... │◀─────────────┘ │ │ ?X-Amz-... │ │ └──────────────────┘ │ │ ◀─────────────────────────┘ Download link (15 min expiry)Docker Configuration
Section titled “Docker Configuration”Signal Bot compose.yml
Section titled “Signal Bot compose.yml”The signal-bot container connects to MinIO via the minio-network:
services: signal-bot: networks: - signal-bot-network - minio-network # For MinIO access
networks: minio-network: external: trueMinIO docker-compose.yml
Section titled “MinIO docker-compose.yml”Located at /home/minio-irregularchat/docker-compose.yml:
version: '3.8'
services: minio: image: minio/minio:latest container_name: minio-irregularchat restart: unless-stopped ports: - "127.0.0.1:9002:9000" # API (internal only) - "127.0.0.1:9003:9001" # Console (internal only) environment: MINIO_ROOT_USER_FILE: /run/secrets/access_key MINIO_ROOT_PASSWORD_FILE: /run/secrets/secret_key MINIO_SERVER_URL: https://s3.irregular.chat MINIO_BROWSER_REDIRECT_URL: https://s3-console.irregular.chat volumes: - /datadrive/minio-data:/data:rw - ./secrets/access_key.txt:/run/secrets/access_key:ro - ./secrets/secret_key.txt:/run/secrets/secret_key:ro command: server /data --console-address ":9001" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 10s retries: 3 networks: - minio-network
networks: minio-network: name: minio-networkEnvironment Variables
Section titled “Environment Variables”Add these to the signal-bot .env file:
# MinIO S3 ConfigurationMINIO_ENDPOINT=http://minio-irregularchat:9000MINIO_ACCESS_KEY=<your-access-key>MINIO_SECRET_KEY=<your-secret-key>MINIO_BUCKET=irregularchatMINIO_REGION=us-east-1MINIO_PUBLIC_URL=https://s3.irregular.chat| Variable | Description |
|---|---|
MINIO_ENDPOINT | Internal Docker network endpoint (used by bot) |
MINIO_ACCESS_KEY | S3-compatible access key |
MINIO_SECRET_KEY | S3-compatible secret key |
MINIO_BUCKET | Bucket containing files |
MINIO_REGION | AWS region (required for S3 signing) |
MINIO_PUBLIC_URL | Public URL for presigned links |
URL Shortening (Shlink)
Section titled “URL Shortening (Shlink)”MinIO presigned URLs can be very long (~300+ characters) which can cause issues in Signal messages:
- URLs get truncated by “Read More” in long messages
- URLs may get cut off when copying
- Long URLs are hard to share manually
The bot optionally integrates with Shlink (self-hosted URL shortener) to create short, memorable URLs:
| Component | Value |
|---|---|
| Long URL | https://s3.irregular.chat/irregularchat/...?X-Amz-... (~300 chars) |
| Short URL | https://irregular.chat/abc12 (~27 chars) |
| Expiration | Same as MinIO presigned URL (15 min) |
Shlink Configuration
Section titled “Shlink Configuration”Add these to the signal-bot .env file:
# Shlink URL Shortener (optional)SHLINK_API_URL=http://shlink-api:8080SHLINK_API_KEY=<your-shlink-api-key>SHLINK_PUBLIC_URL=https://irregular.chat| Variable | Description |
|---|---|
SHLINK_API_URL | Internal Shlink API endpoint (Docker network) |
SHLINK_API_KEY | Shlink API key for authentication |
SHLINK_PUBLIC_URL | Public domain for short URLs |
How It Works
Section titled “How It Works”- Bot generates MinIO presigned URL (long)
- If Shlink is configured, bot calls Shlink API to create short URL
- Short URL has same 15-minute expiration as presigned URL
- User receives short URL:
https://irregular.chat/xyz12 - Clicking short URL redirects to MinIO presigned URL
- After 15 minutes, both URLs expire
Without Shlink
Section titled “Without Shlink”If Shlink is not configured (no SHLINK_API_KEY), the bot falls back to providing the full MinIO presigned URL. All functionality still works, just with longer URLs.
File Path Mapping
Section titled “File Path Mapping”The bot maps local file paths to MinIO S3 keys:
| Local Path | S3 Key |
|---|---|
/app/irregularchat/UnmannedSystems/Documents/drone.pdf | UnmannedSystems/Documents/drone.pdf |
/app/irregularchat/Research/Reports/analysis.pdf | Research/Reports/analysis.pdf |
The minio-client.ts utility strips the base path and constructs the S3 key.
Presigned URL Format
Section titled “Presigned URL Format”Generated URLs follow AWS S3 presigned URL format:
https://s3.irregular.chat/irregularchat/UnmannedSystems/Documents/drone.pdf ?X-Amz-Algorithm=AWS4-HMAC-SHA256 &X-Amz-Credential=... &X-Amz-Date=20241209T120000Z &X-Amz-Expires=900 &X-Amz-SignedHeaders=host &X-Amz-Signature=...- Expires: 900 seconds (15 minutes)
- Algorithm: AWS4-HMAC-SHA256
- Region: us-east-1
Data Synchronization
Section titled “Data Synchronization”MinIO bucket is mounted at /datadrive/minio-data which mirrors the IrregularChat archive:
/datadrive/├── IrregularChat/Topics/ # Bot file archive (source)│ ├── UnmannedSystems/│ ├── Research/│ └── ...└── minio-data/ # MinIO bucket storage └── irregularchat/ ├── UnmannedSystems/ ├── Research/ └── ...Files are synced using rclone:
rclone sync /datadrive/IrregularChat/Topics /datadrive/minio-data/irregularchatCloudflare Tunnel
Section titled “Cloudflare Tunnel”MinIO is exposed via Cloudflare Tunnel (not direct port exposure):
- API:
s3.irregular.chat→localhost:9002 - Console:
s3-console.irregular.chat→localhost:9003
This provides:
- SSL/TLS termination
- DDoS protection
- No exposed ports
Fallback Behavior
Section titled “Fallback Behavior”If MinIO is not configured (missing env vars), the bot falls back to pCloud:
// In file-search.tsif (isMinioConfigured()) { // Use MinIO presigned URL return await getFileLink(s3Key);} else { // Fallback to pCloud return await getPCloudLink(filePath);}Troubleshooting
Section titled “Troubleshooting”Check MinIO Configuration
Section titled “Check MinIO Configuration”docker compose exec signal-bot node -e "const { isMinioConfigured, getMinioConfig } = require('./dist/utils/minio-client.js');console.log('Configured:', isMinioConfigured());console.log('Config:', JSON.stringify(getMinioConfig(), null, 2));"Test Presigned URL Generation
Section titled “Test Presigned URL Generation”docker compose exec signal-bot node -e "const { getFileLink } = require('./dist/utils/minio-client.js');getFileLink('UnmannedSystems/Documents/test.pdf').then(console.log);"Check MinIO Health
Section titled “Check MinIO Health”curl -f http://localhost:9002/minio/health/liveView MinIO Logs
Section titled “View MinIO Logs”docker logs minio-irregularchat --tail 50Maintenance
Section titled “Maintenance”Restart MinIO
Section titled “Restart MinIO”cd /home/minio-irregularchatdocker compose restartUpdate MinIO
Section titled “Update MinIO”cd /home/minio-irregularchatdocker compose pulldocker compose up -dVerify Restart Policy
Section titled “Verify Restart Policy”Both MinIO and Signal Bot have restart: unless-stopped, ensuring automatic restart after server reboot.
Related Documentation
Section titled “Related Documentation”- File Management - Complete file search documentation
- All Commands - Bot command reference
- Self-Hosting Guide - Deployment instructions