README
¶
Itchan Imageboard
A modern, high-performance imageboard built with Go, featuring a clean three-layer architecture, advanced authentication, and comprehensive rate limiting. Designed for privacy-conscious communities with domain-based board access control.
Table of Contents
- Features
- Architecture
- Tech Stack
- Getting Started
- Project Structure
- Backend Architecture
- Database Schema
- Configuration
- API Endpoints
- Frontend
- Testing
- Security Features
- Deployment
- Monitoring
- CI/CD
- Contributing
- License
Features
Core Functionality
- Boards & Threads: Create boards with custom names, organize discussions in threads
- Messages & Replies: Post messages with cross-thread reply support
- File Attachments: Upload images (JPEG, PNG, GIF) and videos (MP4, WebM, OGG) with automatic thumbnail generation
- Attachments-Only Messages: Post messages with only attachments (text is optional)
- Markdown Support: Rich text formatting using a custom markdown parser
- Thread Bumping: Automatic bump-to-top with configurable bump limits
- Pinned Threads: Pin important threads to the top of boards
- Pagination: Efficient board browsing with configurable threads per page
- Disable Media Mode: Cookie-based toggle to replace all images and videos with text placeholders
Authentication & Authorization
- User Registration: Email-based registration with confirmation codes
- Invite System: Registered users can generate invite codes to bring in friends without corporate email
- JWT Authentication: Secure, stateless authentication using JWT tokens
- Dual Auth Methods: Cookie-based (browsers) and Bearer token (API/mobile clients) support
- Admin Roles: Special privileges for board and content moderation
- User Blacklist: Admin-managed user blacklist with cached validation for banned users
- Board Permissions: Domain-based access control (restrict boards to specific email domains)
- CSRF Protection: Token-based CSRF protection for state-changing requests
Performance & Security
- Nginx Reverse Proxy: TLS termination, HTTP/2, connection limits, slowloris protection
- Two-Layer Rate Limiting: Nginx (per-IP, connection limits) + Go (per-user, token bucket)
- Media Sanitization: Automatic EXIF/metadata stripping from images and videos using ffmpeg
- Email Encryption: AES-256-GCM encryption of email addresses at the application level
- Database Partitioning: Automatic table partitioning by board for optimal performance
- Materialized Views: Fast board previews with configurable refresh intervals
- Security Headers: HSTS, CSP, X-Frame-Options, X-Content-Type-Options
- Gzip Compression: Automatic response compression
- CORS Support: Configured for secure cross-origin requests
- Graceful Shutdown: Proper cleanup and connection handling
Developer Experience
- Clean Architecture: Three-layer backend (Storage → Service → Handler)
- Comprehensive Testing: Unit and integration tests for all layers
- Docker Deployment: Full containerization with Docker Compose
- Type Safety: Strong domain types and validation
- Middleware Chain: Composable authentication, authorization, and rate limiting
Architecture
Web Browsers Mobile Apps API Clients
(Cookies) (Bearer) (Bearer)
│ │ │
└──────────────┼──────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Docker Compose │
├──────────────────────────────────────────────────────────────┤
│ Nginx (Reverse Proxy) │
│ TLS termination, rate limiting, HTTP/2 │
│ Ports 80/443 │
├──────────────┬──────────────────────┬──────────────────────┤
│ Frontend │ Backend API │ PostgreSQL 17.6 │
│ (Go HTML) │ (REST Service) │ (Partitioned DB) │
│ Port 8081 │ Port 8080 │ Port 5432 │
│ (internal) │ (internal) │ (internal) │
└──────────────┴──────────────────────┴──────────────────────┘
Backend Layers:
Handler ← HTTP routing, JSON I/O, validation
↓
Service ← Business logic, authentication, file processing
↓
Storage ← PostgreSQL operations, partitioning, queries
Shared Modules:
• domain/ - Domain types and models
• middleware/ - Auth, rate limiting, board access control
• config/ - Configuration management
• jwt/ - JWT token handling
• validation/ - Input validation and file handling
Tech Stack
- Backend: Go 1.24+ (frontend-agnostic JSON REST API)
- Frontend: Go templates (html/template) - replaceable with any client
- Database: PostgreSQL 17.6 with partitioning
- Reverse Proxy: Nginx (TLS termination, rate limiting, HTTP/2)
- Router: Chi (go-chi/chi)
- Auth: JWT (golang-jwt/jwt) with dual-mode support (Cookie + Bearer)
- TLS: Let's Encrypt (automatic renewal via certbot)
- Markdown: Custom parser
- Media Processing: ffmpeg (for metadata sanitization)
- Monitoring: Prometheus + Grafana (optional)
- Container: Docker & Docker Compose
- CI/CD: GitHub Actions
- Email: SMTP support for confirmations
Getting Started
Prerequisites
- Docker 20.10+ and Docker Compose 2.0+
- Go 1.24+ (for local development)
- PostgreSQL 17.6+ (if running without Docker)
- ffmpeg (for media sanitization - automatically included in Docker image)
Production Deployment
See SETUP.md for full production setup guide with HTTPS and nginx.
git clone https://github.com/itchan-dev/itchan.git
cd itchan
chmod +x scripts/*.sh
./scripts/setup.sh yourdomain.com your-email@example.com
# Configure email in .env (SMTP_SERVER, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD)
make deploy
Quick Start (Development)
For local development without nginx/HTTPS:
git clone https://github.com/itchan-dev/itchan.git
cd itchan
docker-compose up --build
# Frontend UI: http://localhost:8081
# Backend API: http://localhost:8080/v1
The database will be automatically initialized with the schema from backend/internal/storage/pg/migrations/init.sql.
Local Development
For development without Docker:
# 1. Start PostgreSQL (adjust connection settings in config/private.yaml)
# Make sure PostgreSQL is running on localhost:5432
# 2. Run database migrations
psql -U itchan -d itchan -f backend/internal/storage/pg/migrations/init.sql
# 3. Configure your environment
cp .env.example .env
# Edit .env with your database and email settings
make gen-configs
# 4. Start the backend API
cd backend
go run cmd/itchan-api/main.go -config_folder ../config
# 5. In a new terminal, start the frontend
cd frontend
go run cmd/frontend/main.go -config_folder ../config
Project Structure
itchan/
├── backend/ # Backend API service
│ ├── cmd/
│ │ ├── itchan-api/ # Main entry point
│ │ │ └── main.go
│ ├── internal/
│ │ ├── handler/ # HTTP handlers (REST endpoints)
│ │ │ ├── auth.go # Register, login, logout
│ │ │ ├── blacklist.go # User blacklist management
│ │ │ ├── board.go # Board CRUD operations
│ │ │ ├── handler.go # Handler dependencies
│ │ │ ├── health.go # Health and readiness probes
│ │ │ ├── helpers.go # Shared handler helpers
│ │ │ ├── invite.go # Invite code management
│ │ │ ├── message.go # Message posting and retrieval
│ │ │ ├── thread.go # Thread operations
│ │ │ └── user_activity.go # User activity tracking
│ │ ├── service/ # Business logic layer
│ │ │ ├── auth.go # Authentication logic (incl. invites)
│ │ │ ├── board.go # Board management
│ │ │ ├── gc.go # Orphaned media cleanup
│ │ │ ├── media_storage.go # File storage service
│ │ │ ├── message.go # Message processing
│ │ │ ├── thread.go # Thread management
│ │ │ ├── user_activity.go # User activity tracking
│ │ │ └── utils/ # Service utilities
│ │ │ └── sanitize.go # Media sanitization (EXIF/metadata stripping)
│ │ ├── storage/ # Data access layer
│ │ │ ├── pg/ # PostgreSQL implementation
│ │ │ │ ├── auth.go # User storage operations
│ │ │ │ ├── blacklist.go # User blacklist operations
│ │ │ │ ├── board.go # Board storage
│ │ │ │ ├── board_enrichment.go # Board data enrichment
│ │ │ │ ├── board_view.go # Materialized view management
│ │ │ │ ├── message.go # Message storage
│ │ │ │ ├── message_enrichment.go # Message data enrichment
│ │ │ │ ├── pg.go # DB connection & partitioning
│ │ │ │ ├── thread.go # Thread storage
│ │ │ │ ├── user_activity.go # User activity storage
│ │ │ │ ├── migrations/init.sql # Database schema
│ │ │ │ └── templates/ # SQL templates for partitioning & views
│ │ │ └── fs/ # File system storage
│ │ │ └── fs.go # File upload/download
│ │ ├── utils/ # Backend utilities
│ │ │ ├── utils.go # Helper functions
│ │ │ └── email/ # Email sending service
│ │ ├── router/ # Route configuration
│ │ │ └── router.go # All API routes and middleware
│ │ └── setup/ # Dependency injection
│ │ └── setup.go
│ └── Dockerfile
│
├── frontend/ # Frontend UI service
│ ├── cmd/frontend/ # Main entry point
│ │ └── main.go
│ ├── internal/
│ │ ├── handler/ # HTTP handlers for pages
│ │ │ ├── account.go # Account management page
│ │ │ ├── auth.go # Login/register pages
│ │ │ ├── blacklist.go # Blacklist management page
│ │ │ ├── board.go # Board view
│ │ │ ├── faq.go # FAQ page
│ │ │ ├── handler.go # Handler dependencies
│ │ │ ├── helpers.go # Shared handler helpers
│ │ │ ├── index.go # Homepage
│ │ │ ├── invites.go # Invite management page
│ │ │ ├── legal.go # Legal pages (terms, privacy, contacts)
│ │ │ ├── message.go # Message actions
│ │ │ ├── render.go # Template rendering
│ │ │ ├── settings.go # User settings
│ │ │ └── thread.go # Thread view
│ │ ├── apiclient/ # Backend API client
│ │ │ ├── apiclient.go
│ │ │ ├── auth.go
│ │ │ ├── blacklist.go
│ │ │ ├── board.go
│ │ │ ├── invites.go
│ │ │ ├── message.go
│ │ │ ├── thread.go
│ │ │ └── user_activity.go
│ │ ├── domain/ # Frontend domain models
│ │ │ ├── board.go
│ │ │ ├── common.go # Common template data
│ │ │ ├── message.go
│ │ │ ├── pages.go # Page-specific data structs
│ │ │ ├── partials.go # Partial-specific data structs
│ │ │ └── thread.go
│ │ ├── markdown/ # Markdown processing
│ │ │ ├── parser.go # Custom parser with block/inline rules
│ │ │ └── trie.go # Trie for fast marker matching
│ │ ├── middleware/ # Frontend middleware
│ │ │ ├── auth.go # Authentication forwarding
│ │ │ └── csrf.go # CSRF token handling
│ │ ├── router/ # Route configuration
│ │ │ └── router.go
│ │ └── setup/ # Dependency setup
│ │ └── setup.go
│ ├── templates/ # HTML templates
│ │ ├── base.html # Base layout
│ │ ├── index.html # Board list
│ │ ├── board.html # Board view
│ │ ├── thread.html # Thread view
│ │ ├── login.html # Login page
│ │ ├── register.html # Registration page
│ │ ├── register_invite.html # Invite-based registration
│ │ ├── check_confirmation_code.html # Email confirmation
│ │ ├── account.html # Account management
│ │ ├── admin.html # Admin panel
│ │ ├── invites.html # Invite management
│ │ ├── faq.html # FAQ and rules
│ │ ├── about.html # About page
│ │ ├── contacts.html # Contact information
│ │ ├── privacy.html # Privacy policy
│ │ ├── terms.html # Terms of service
│ │ └── partials.html # Reusable components
│ ├── static/ # Static assets
│ │ ├── css/style.css
│ │ ├── js/main.js
│ │ └── favicon.ico
│ └── Dockerfile
│
├── shared/ # Shared packages
│ ├── api/ # API request/response types
│ ├── blacklist/ # Blacklist caching
│ ├── config/ # Configuration management
│ ├── crypto/ # Email encryption (AES-256-GCM)
│ ├── csrf/ # CSRF token protection
│ ├── domain/ # Domain models
│ │ ├── attachments.go # File attachment models
│ │ ├── auth.go # User, credentials
│ │ ├── board.go # Board models
│ │ ├── helpers.go # Domain helper functions
│ │ ├── message.go # Message models
│ │ ├── thread.go # Thread models
│ │ └── types.go # Custom types and validation
│ ├── errors/ # Error types
│ ├── jwt/ # JWT utilities
│ ├── logger/ # Structured logging
│ ├── middleware/ # HTTP middleware
│ │ ├── auth.go # JWT authentication
│ │ ├── security_headers.go # Security headers (HSTS, CSP, etc.)
│ │ ├── board_access/ # Board permission check
│ │ ├── metrics/ # Prometheus metrics
│ │ └── ratelimiter/ # Rate limiting
│ ├── storage/ # Storage interfaces
│ ├── utils/ # Utility functions
│ │ ├── crypto.go # Hashing utilities
│ │ └── web.go # HTTP helpers
│ └── validation/ # Input validation
│ ├── attachment.go # File validation
│ ├── multipart.go # Multipart form handling
│ └── errors.go
│
├── nginx/ # Reverse proxy
│ ├── nginx.conf # Nginx configuration
│ ├── nginx.conf.explained # Line-by-line explanation
│ ├── certs/ # SSL certificates (generated)
│ └── certbot/ # ACME challenge files
│
├── monitoring/ # Prometheus + Grafana
│ ├── prometheus.yml # Prometheus configuration
│ └── grafana/ # Grafana dashboards
│
├── scripts/ # Setup and maintenance
│ ├── setup.sh # Full initial setup (configs + SSL + cron)
│ ├── gen-configs.sh # Generate configuration files
│ └── renew-ssl.sh # SSL certificate renewal (called by cron)
│
├── config/ # Configuration files
│ ├── public.yaml # Public configuration (shared with frontend)
│ └── private.yaml # Private configuration (backend only, generated)
│
├── tools/ # CLI utilities (run from project root)
│ ├── generate-encryption-key/ # Generate AES-256 encryption key
│ └── render-template/ # Docker image for Jinja2 config rendering
│
├── docker-compose.yml # Docker orchestration (production)
├── docker-compose.dev.yml # Docker orchestration (development)
├── docker-compose.monitoring.yml # Monitoring stack
├── Makefile # Build and deployment commands
├── SETUP.md # Production setup guide
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
├── CODE_OF_CONDUCT.md # Code of conduct
├── go.mod # Go module definition
├── go.sum # Go module checksums
└── README.md
Backend Architecture
Itchan follows a strict three-layer architecture for maintainability and testability:
1. Handler Layer (internal/handler/)
Responsibility: HTTP request/response handling
- Parse HTTP requests and extract parameters
- Validate input format and types
- Call appropriate service methods
- Convert service responses to HTTP responses
- Handle HTTP-specific errors (404, 401, etc.)
Example: handler/board.go
func (h *Handler) CreateBoard(w http.ResponseWriter, r *http.Request) {
// Parse request body
// Validate input
// Call service.Board.Create()
// Return JSON response
}
2. Service Layer (internal/service/)
Responsibility: Business logic and orchestration
- Enforce business rules (bump limits, thread counts, etc.)
- Coordinate between multiple storage operations
- Handle file uploads and processing
- Manage transactions
- Authentication and authorization logic
Example: service/thread.go
func (s *Thread) Create(ctx context.Context, data domain.ThreadCreationData) error {
// Check thread count limits
// Validate board exists
// Create thread in storage
// Trigger GC if needed
}
3. Storage Layer (internal/storage/pg/)
Responsibility: Data persistence
- Execute SQL queries
- Manage database connections
- Handle table partitioning
- Manage materialized views
- Map between database rows and domain models
Example: storage/pg/board.go
func (s *Storage) CreateBoard(ctx context.Context, board *domain.Board) error {
// Execute INSERT statement
// Create partition for new board
// Return database errors
}
Database Schema
The database uses PostgreSQL's table partitioning for scalability. Each board gets its own partition.
Key Tables
- users: User accounts with encrypted email and password hash
- user_blacklist: Blacklisted users with reason and admin reference (cached for JWT validation)
- confirmation_data: Email confirmation codes for registration
- invite_codes: User-generated invite codes for registration
- boards: Board metadata (name, short_name, activity timestamps)
- board_permissions: Domain-based access control (email domain allowlist per board)
- threads: Thread metadata (title, message count, bump time, pinned flag) - partitioned by board
- messages: Individual posts (text, author, timestamps, ordinal) - partitioned by board
- attachments: Links messages to uploaded files - partitioned by board
- files: File metadata with both original and sanitized filenames/MIME types (path, size, dimensions, thumbnail path)
- message_replies: Cross-reference table for reply relationships - partitioned by board
Materialized Views
- board_previews: Pre-computed board views with last N messages per thread
- Refreshed on configurable intervals (
board_preview_refresh_interval) - Dramatically improves board page load times
- Refreshed on configurable intervals (
Indexes
Optimized indexes for common queries:
- Thread lookup by board and bump time
- Message lookup by thread
- Reply lookup by receiver message
- Author lookup for moderation
Configuration
Configuration is split into two files:
config/public.yaml (Shared between backend and frontend)
jwt_ttl: 24h # JWT token lifetime
threads_per_page: 15 # Pagination
messages_per_thread_page: 1000 # Messages per thread page (0 = all)
max_thread_count: 100 # Max threads per board (null = unlimited)
n_last_msg: 3 # Messages shown in board preview
bump_limit: 500 # Messages before thread stops bumping
board_preview_refresh_internval: 30s # Materialized view refresh rate
board_activity_window: 3m # How far back to check for board activity
blacklist_cache_interval: 300 # Blacklist cache refresh interval (seconds)
# Auth
confirmation_code_ttl: 10m # Email confirmation code expiration
# Logging
log_level: info # debug, info, warn, error
log_format: text # text or json
# Security settings
secure_cookies: false # Set to true for HTTPS
csrf_enabled: true # Enable CSRF protection
# Text length limits (defaults shown, all optional)
board_name_max_len: 10
board_short_name_max_len: 3
thread_title_max_len: 50
message_text_max_len: 10000
message_text_min_len: 1
password_min_len: 8
max_replies_per_message: 50 # Max >>reply links per message
# File upload limits
max_attachments_per_message: 4
max_attachment_size_bytes: 10485760 # 10 MB per file
max_total_attachment_size: 20971520 # 20 MB total
allowed_image_mime_types:
- image/jpeg
- image/png
- image/gif
allowed_video_mime_types:
- video/mp4
- video/webm
- video/ogg
# Invite system
invite_enabled: false
invite_code_length: 12
invite_code_ttl: 720h # 30 days
max_invites_per_user: 5 # 0 = unlimited
min_account_age_for_invites: 720h # 30 days
# User activity page
user_messages_page_limit: 50 # Messages shown on account page
# Caching
static_cache_max_age: 240h # Static file cache (CSS, JS)
media_cache_max_age: 168h # User-uploaded media cache
# Media processing
media:
thumbnail_max_size: 225 # Max dimension (px) for thumbnails
thumbnail_display_op: 225 # Display size for OP thumbnails
thumbnail_display_reply: 150 # Display size for reply thumbnails
jpeg_quality_main: 85 # JPEG quality for main images (0-100)
jpeg_quality_thumbnail: 75 # JPEG quality for thumbnails (0-100)
# Registration restrictions
allowed_registration_domains: [] # Empty = allow all domains. Example: ["gmail.com", "company.com"]
config/private.yaml (Generated from templates/private.yaml.j2 + .env — never commit!)
jwt_key: "<your-secret-key>" # JWT signing key (generate random 512+ bit string)
encryption_key: "<your-encryption-key>" # AES-256-GCM key for email encryption (generate with: go run ./tools/generate-encryption-key/)
pg:
host: localhost
port: 5432
user: itchan
password: itchan
dbname: itchan
email:
smtp_server: smtp.gmail.com
smtp_port: 587 # 587 = STARTTLS, 465 = implicit TLS (Yandex)
username: your-email@gmail.com
password: your-app-password
sender_name: "Itchan Imageboard"
timeout: 10
# Referral source allowlist (optional)
# Only ?ref= values in this list are tracked and saved to the database.
# Empty or omitted = allow all sources (no filtering).
allowed_refs:
- twitter
- reddit
- telegram
API Endpoints
Authentication Methods
The API supports two authentication methods:
-
Cookie-based (automatic for browsers):
- Login sets an HTTP-only cookie with the JWT
- Subsequent requests include the cookie automatically
- Best for browser-based clients
-
Bearer Token (for API/mobile clients):
- Login returns
access_tokenin response body - Include token in requests:
Authorization: Bearer <token> - Best for mobile apps, CLI tools, third-party integrations
- Login returns
Authentication Endpoints
POST /v1/auth/register - Register new user (rate limited: 1/s per email & IP)
POST /v1/auth/check_confirmation_code - Verify email confirmation code (5 attempts per 10 min per email)
POST /v1/auth/login - Login with credentials (returns access_token)
POST /v1/auth/register_with_invite - Register with an invite code (rate limited: 1/s per IP)
POST /v1/auth/logout - Logout (clear cookies)
Login Response:
{
"message": "You logged in",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Boards (Authenticated)
GET /v1/boards - List all accessible boards
GET /v1/{board} - Get board with threads (paginated)
GET /v1/{board}/last_modified - Get board's last modification timestamp
Example with Bearer Token:
curl -H "Authorization: Bearer <your_token>" \
http://localhost:8080/v1/boards
Threads (Authenticated)
POST /v1/{board} - Create new thread (rate limited: 1/minute per user)
GET /v1/{board}/{thread} - Get thread with all messages
GET /v1/{board}/{thread}/last_modified - Get thread's last modification timestamp
Messages (Authenticated)
POST /v1/{board}/{thread} - Post message to thread (rate limited: 1/second per user)
GET /v1/{board}/{thread}/{message} - Get single message
Invites (Authenticated)
GET /v1/invites/ - Get user's invite codes
POST /v1/invites/ - Generate new invite code (rate limited: 1/minute per user)
DELETE /v1/invites/{codeHash} - Revoke an invite code
User (Authenticated)
GET /v1/users/me/activity - Get current user's activity data
GET /v1/public_config - Get public configuration
Admin (Admin only)
POST /v1/admin/boards - Create new board
DELETE /v1/admin/{board} - Delete board
DELETE /v1/admin/{board}/{thread} - Delete thread
POST /v1/admin/{board}/{thread}/pin - Toggle thread pinned status
DELETE /v1/admin/{board}/{thread}/{message} - Delete message
POST /v1/admin/users/{userId}/blacklist - Blacklist user (with optional reason)
DELETE /v1/admin/users/{userId}/blacklist - Remove user from blacklist
GET /v1/admin/blacklist - Get all blacklisted users with details
POST /v1/admin/blacklist/refresh - Manually refresh blacklist cache
Health & Monitoring (No auth)
GET /health - Liveness probe (200 if running)
GET /ready - Readiness probe (200 if DB connected)
GET /metrics - Prometheus metrics
Rate Limits
- Registration: 1/s per email, 1/s per IP, 100 global RPS
- Confirmation Code: 5 attempts per 10 minutes per email, 1/s per IP, 100 global RPS
- Login: 1/s per IP, 1000 global RPS
- Invite Registration: 1/s per IP, 100 global RPS
- Create Thread: 1 per minute per user
- Post Message: 1 per second per user
- Generate Invite: 1 per minute per user
- Get Board: 10 RPS per user
- General authenticated: 100 RPS per user
- Admin: No rate limits
Example Usage
Browser/Frontend (Cookie-based):
// Login - cookie set automatically
await fetch('http://localhost:8080/v1/auth/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', password: 'pass' })
});
// Subsequent requests - cookie sent automatically
await fetch('http://localhost:8080/v1/boards', { credentials: 'include' });
API/Mobile Clients (Bearer Token):
# Login and extract token
TOKEN=$(curl -X POST http://localhost:8080/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"pass"}' \
| jq -r '.access_token')
# Use token in requests
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/v1/boards
Python:
import requests
# Login
response = requests.post('http://localhost:8080/v1/auth/login',
json={'email': 'user@example.com', 'password': 'pass'})
token = response.json()['access_token']
# Use token
headers = {'Authorization': f'Bearer {token}'}
boards = requests.get('http://localhost:8080/v1/boards', headers=headers)
See BEARER_AUTH_EXAMPLE.md for more examples (React Native, Flutter, Node.js, etc.)
Frontend
The frontend is a server-rendered Go application using html/template.
Templates
- base.html: Common layout (header, footer, navigation)
- index.html: Board listing and creation form
- board.html: Thread list with pagination
- thread.html: Full thread view with all messages and reply form
- login.html / register.html / register_invite.html: Authentication pages
- check_confirmation_code.html: Email confirmation page
- account.html: Account management
- admin.html: Admin panel
- invites.html: Invite code management
- faq.html / about.html / contacts.html: Info pages
- privacy.html / terms.html: Legal pages
- partials.html: Reusable components (message rendering, forms)
Markdown Support
The frontend includes a lightweight custom markdown parser optimized for imageboard use:
- Code blocks (fenced with ```)
- Inline code, bold, italic, strikethrough
- Greentext (lines starting with >)
- Message links (>>threadId#msgId) with hover previews
Interactive Features
- Popup Reply System: Dynamic popup forms for quick replies with intelligent positioning
- Message Preview System: Hover over message links to see inline previews with caching (500 message cache)
- Preview Chains: Navigate through reply chains with automatic cleanup and smart timeout handling
- File Upload Manager: Real-time file preview with thumbnails, size validation, and individual file removal
- Hash-based Reply Links: Support for #reply-{id} URLs to auto-populate reply forms
- Disable Media Mode: Toggle in header to hide all images/videos, showing only filenames and metadata as text links (useful for slow connections, persists via cookie, works without JS)
Static Assets
- CSS: Minimal, responsive stylesheet
- JavaScript: Vanilla JS for dynamic reply forms, message previews, and file upload management
- Favicon: Custom imageboard icon
Testing
Comprehensive test coverage across all layers:
# Backend tests
cd backend
# Unit tests (service layer)
go test ./internal/service/...
# Integration tests (storage layer - requires running PostgreSQL)
go test ./internal/storage/pg/...
# Handler tests
go test ./internal/handler/...
# Frontend tests
cd ../frontend
go test ./internal/handler/...
# Run all tests with coverage
go test -v -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Test Structure
- Unit tests: Business logic validation (service layer)
- Integration tests: Database operations (storage layer with test DB)
- Handler tests: HTTP endpoint behavior (mocked services)
Integration tests use a separate test database and clean up after execution.
Security Features
Authentication & Authorization
- JWT-based authentication with configurable TTL
- Dual authentication support: Cookie-based (browsers) and Bearer token (API clients)
- Invite system: Users can generate invite codes for registration without corporate email
- Blacklist cache validation: Automatic JWT rejection for blacklisted users with cached validation
- Secure cookie storage with HttpOnly and Secure flags (when enabled)
- Password hashing using bcrypt
- Email encryption: AES-256-GCM encryption of email addresses at the application level
- CSRF protection: Token-based CSRF protection for state-changing requests
- Email confirmation required for registration
- Registration domain restrictions: Optional email domain allowlist for registration
- Admin role for moderation capabilities
Rate Limiting
- Multi-tier rate limiting: Per-user, per-IP, and global limits
- Token bucket algorithm with configurable refill rates
- Admin exemption: Admin users bypass rate limits
- Fingerprinting fallback: Uses request fingerprint if IP unavailable
Board Access Control
- Domain-based permissions: Restrict boards to specific email domains
- Middleware enforcement: Board access checked on every request
- Automatic filtering: Users only see boards they can access
Input Validation & Media Processing
- File type validation: Strict MIME type checking for uploads
- File size limits: Per-file and total attachment size limits
- Media sanitization: Automatic EXIF/metadata stripping from images (decode/encode) and videos (ffmpeg)
- Format normalization: GIF/WebP images converted to JPEG, PNG preserved
- Image dimension extraction: Metadata validation
- SQL injection prevention: Parameterized queries throughout
- XSS prevention: Template auto-escaping, markdown sanitization
Infrastructure
- Nginx reverse proxy: TLS termination, multi-tier rate limiting, connection limits, slowloris protection
- Let's Encrypt: Automatic SSL certificate provisioning and renewal
- Security headers: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- CORS configuration: Controlled cross-origin access
- Gzip compression: Automatic response compression (nginx)
- Graceful shutdown: Proper connection cleanup
- Database partitioning: Isolation between boards
Deployment
Development
make dev # or: docker compose up --build
Production
Full setup guide: SETUP.md
make deploy # Build and start all services (nginx, frontend, api, postgres)
docker compose ps # View running services
make logs # View all logs
make logs-api # View API logs
make logs-frontend # View frontend logs
Monitoring
Optional Prometheus + Grafana stack for metrics and dashboards.
Enable Monitoring
make deploy-monitoring
Access:
- Grafana: http://localhost:3000 (default: admin/admin)
- Prometheus: http://localhost:9090
Disable Monitoring
make deploy # Redeploy without monitoring compose file
docker compose stop prometheus grafana # Stop monitoring services
Available Metrics
http_requests_total{method, path, status}- Request counthttp_request_duration_seconds{method, path}- Latency histogramhttp_requests_in_flight- Concurrent requests gauge- Go runtime metrics (goroutines, memory, GC)
Health Endpoints
GET /health- Liveness probe (200 if running)GET /ready- Readiness probe (200 if DB connected)GET /metrics- Prometheus metrics (backend only)
CI/CD
Automated testing and deployment via GitHub Actions.
Workflow
- On Pull Request: Run tests
- On Push to main: Run tests → Deploy to VPS
Setup
Add these secrets in GitHub (Settings → Secrets → Actions):
VPS_HOST- Your server IP/hostnameVPS_USER- SSH usernameVPS_SSH_KEY- Private SSH key contentVPS_PROJECT_PATH- Path to project on VPS (e.g.,/home/user/itchan)
Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
go test ./...) - Format your code (
go fmt ./...) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Code Style
- Follow standard Go conventions (
gofmt,golint) - Write descriptive commit messages
- Add comments for complex logic
- Keep functions focused and testable
- Maintain the three-layer architecture separation
License
MIT License - see LICENSE for details.
Built with ❤️ using Go
Directories
¶
| Path | Synopsis |
|---|---|
|
backend
|
|
|
cmd/itchan-api
command
|
|
|
internal/storage/fs
in storage/localfs/localfs.go
|
in storage/localfs/localfs.go |
|
internal/storage/pg
Package pg implements the storage layer for the application using a PostgreSQL database.
|
Package pg implements the storage layer for the application using a PostgreSQL database. |
|
frontend
|
|
|
cmd/frontend
command
|
|
|
shared
|
|
|
middleware/metrics
Package metrics provides Prometheus HTTP metrics middleware.
|
Package metrics provides Prometheus HTTP metrics middleware. |
|
storage/pg
Package pg provides core PostgreSQL database primitives for storage layers.
|
Package pg provides core PostgreSQL database primitives for storage layers. |
|
tools
|
|
|
generate-encryption-key
command
|