- Go 75.5%
- HTML 16.2%
- CSS 7.9%
- Dockerfile 0.4%
| config | ||
| handlers | ||
| middleware | ||
| protocols | ||
| proxy | ||
| static | ||
| templates | ||
| .env.example | ||
| .gitignore | ||
| CHANGELOG.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| main.go | ||
| README.md | ||
Smolnet Portal (Go)
A modern web proxy for the small internet — bringing Gemini, Gopher, Spartan, Finger, Nex, Scroll, and Text protocols to your browser over HTTP(S).
This is a high-performance Go reimplementation of Smolnet Portal (originally Python/Quart by Michael Lazar).
Features
Protocol Support
- ✅ Gemini — rich text with inline styling
- ✅ Gopher — classic menu browser with Gopher+ support
- ✅ Gophers — secure gopher over TLS
- ✅ Spartan — lightweight text protocol
- ✅ Finger — user info protocol
- ✅ Nex — directory listings
- ✅ Scroll — modern markup with metadata
- ✅ Text — plain text with smart URL linking
Content Processing
- Gemtext → HTML rendering (headers, links, lists, preformatted blocks, blockquotes, inline emoji)
- Gopher menu rendering with type icons and Gopher+ views/admin blocks
- Scroll markup support (
*bold*,_italic_,`code`) with metadata extraction - Nex directory listings with automatic link detection
- Plain text auto-linking for URLs, emails, and protocols
- Emoji favicons cached per domain
User Experience
- Streaming support for large responses (>1 MiB)
- Reader mode for distraction-free reading
- Theme switcher (light/dark/sepia) with system preference detection
- Font switcher (serif/sans-serif/monospace)
- TLS certificate inspection for Gemini/Scroll
- Inline markup with proper character preservation
- Responsive design for mobile and desktop
Security & Protection
- Rate limiting (10 req/s per IP, configurable)
- Cookie-based CAPTCHA for bot prevention
- Blocked hosts list with exact domain matching
- Configurable TLS verification for development/testing
- Secure port restrictions (default: ports 70, 77, 79, 300-301, 1900, 3000-3333, 5699, 7000-7099, 8070, 1960-2020)
Developer Features
- Single static binary — no dependencies, no database
- Configurable via environment variables — easy Docker/K8s deployment
- Rebrandable — customize app names and descriptions via environment variables
- Fail2ban integration via hidden trap link
- Docker support with multi-stage build
- Clean codebase with error logging
Installation
From Source
Requirements: Go 1.22+
# Clone the repository
git clone https://github.com/kalvin0x8d0/small-web-gateway.git
cd small-web-gateway
# Build the binary
go build -o smolnet-portal .
# Run it
PORT=8080 BASE_URL=http://localhost:8080 ./smolnet-portal
The proxy will be available at http://localhost:8080.
Using Docker
# Build and run
docker compose up --build
# Or with custom settings
docker run -e PORT=8080 -e BASE_URL=http://localhost:8080 \
-p 8080:8080 smolnet-portal:latest
Binary Release
Download pre-built binaries from releases.
# Extract and run
tar xzf smolnet-portal-linux-amd64.tar.gz
PORT=8080 ./smolnet-portal
Configuration
Environment Variables
| Variable | Default | Example | Description |
|---|---|---|---|
PORT |
8080 |
8080 |
HTTP listen port |
BASE_URL |
http://localhost:8080 |
https://proxy.example.com |
Public URL of the portal (for links) |
APP_NAME |
Smolnet Portal |
My Portal |
Application name (page title) |
APP_TITLE |
SMOLNET PORTAL |
MY PORTAL |
Application title (header) |
SITE_DESCRIPTION |
(see config) | Free proxy service |
Home page site description |
WELCOME_GREETING |
Welcome, voyager! |
Hello there! |
Home page welcome message |
CAPTCHA_TITLE |
Smolnet Captcha! |
Security Check |
Captcha page title |
CAPTCHA_ENABLED |
true |
false |
Enable cookie CAPTCHA |
BLOCKED_HOSTS |
vger.cloud,warpengineer.space,... |
site1.com,site2.com |
Blocked hostnames (comma-separated) |
ALLOWED_PORTS |
(see config) | 70,79,1965 |
Allowed ports (comma-separated) |
TLS_INSECURE |
false |
true |
Skip TLS certificate verification (for development) |
Docker Compose Example
version: '3.8'
services:
smolnet:
build: .
ports:
- "8080:8080"
environment:
PORT: "8080"
BASE_URL: "http://localhost:8080"
CAPTCHA_ENABLED: "true"
BLOCKED_HOSTS: "vger.cloud,warpengineer.space"
TLS_INSECURE: "false"
restart: unless-stopped
Advanced Configuration
Disable CAPTCHA for local development:
CAPTCHA_ENABLED=false ./smolnet-portal
Allow additional ports:
ALLOWED_PORTS=70,79,1965,9000,9001 ./smolnet-portal
Disable TLS verification (development only):
TLS_INSECURE=true ./smolnet-portal
Usage
Accessing Content
The proxy rewrites URLs to its own format:
http://localhost:8080/gemini/geminiprotocol.net/
http://localhost:8080/gopher/gopher.floodgap.com:70/
http://localhost:8080/spartan/spartan.mozz.us/
http://localhost:8080/text/www.example.com/robots.txt
Query Parameters
| Parameter | Purpose | Example |
|---|---|---|
?raw=1 |
View raw response (no HTML rendering) | /gemini/site.com/?raw=1 |
?crt=1 |
Display TLS certificate details | /gemini/site.com/?crt=1 |
?raw_crt=1 |
Download x509 certificate | /gemini/site.com/?raw_crt=1 |
?reader=1 |
Reader mode (minimal styling) | /gemini/site.com/?reader=1 |
?meta=1 |
Show Scroll metadata | /scroll/site.com/?meta=1 |
?vr=1 |
Virtual reality mode (gopher) | /gopher/site.com/?vr=1 |
?charset=utf-8 |
Override character encoding | /text/site.com/?charset=iso-8859-1 |
?lang=fr |
Set language preference | /scroll/site.com/?lang=fr |
Common URLs
Exploring Gemini:
http://localhost:8080/gemini/geminiprotocol.net/
http://localhost:8080/gemini/gmi.skyjake.fi/
http://localhost:8080/gemini/warmedal.se/
Browsing Gopher:
http://localhost:8080/gopher/gopher.floodgap.com:70/
http://localhost:8080/gopher/sdf.org:70/
Exploring Spartan:
http://localhost:8080/spartan/spartan.mozz.us/
Security Considerations
TLS/SSL
By default, the portal verifies TLS certificates. For development with self-signed certificates, use:
TLS_INSECURE=true ./smolnet-portal
⚠️ Never use TLS_INSECURE=true in production — it makes you vulnerable to MITM attacks.
Port Restrictions
The proxy only allows connections to specific ports to prevent scanning/abuse:
- Default allowed: 70, 77, 79, 300-301, 1900, 3000-3333, 5699, 7000-7099, 8070, 1960-2020
- Override with
ALLOWED_PORTS=70,79,1965
Rate Limiting
Built-in rate limiting (10 requests/second per IP) prevents DoS abuse. IPs with inactive connections are auto-cleaned every 5 minutes.
Blocked Hosts
Configure a list of blocked hosts to prevent access to private/local networks:
BLOCKED_HOSTS=vger.cloud,warpengineer.space,localhost,127.0.0.1 ./smolnet-portal
Blocking works with:
- Exact domain matching:
example.comblocksexample.com - Subdomain matching:
example.comblockssub.example.com
CAPTCHA Protection
A cookie-based CAPTCHA is enabled by default to protect against bot abuse. Disable with CAPTCHA_ENABLED=false.
Troubleshooting
Connection Refused
Issue: unable to establish connection with host
Causes:
- Host is unreachable or offline
- Port is not in the allowed list
- Firewall blocking the connection
Solution:
# Check if host is reachable
ping geminiprotocol.net
# Verify port is allowed
ALLOWED_PORTS=70,79,1965,3333 ./smolnet-portal
SSL Certificate Errors
Issue: x509: certificate signed by unknown authority
Causes:
- Self-signed certificate
- Invalid/expired certificate
- TLS verification enabled
Solution (development only):
TLS_INSECURE=true ./smolnet-portal
Host Blocked
Issue: this host has kindly requested that their content not be accessed via web proxy
Causes:
- Host is in
BLOCKED_HOSTSlist - Host is on the default blocklist (vger.cloud, etc.)
Solution:
Remove the host from BLOCKED_HOSTS if you manage it:
BLOCKED_HOSTS=other-site.com ./smolnet-portal
CAPTCHA Loop
Issue: Stuck on captcha screen
Causes:
- Cookies disabled in browser
- Cookie domain mismatch
BASE_URLincorrect
Solution:
- Enable cookies in browser
- Set correct
BASE_URL:BASE_URL=https://proxy.example.com ./smolnet-portal - Disable CAPTCHA for testing:
CAPTCHA_ENABLED=false ./smolnet-portal
Character Encoding Issues
Issue: Mojibake or corrupted text
Causes:
- Wrong character encoding detected
- Server not specifying charset
Solution:
# Override encoding
http://localhost:8080/text/site.com/?charset=iso-8859-1
http://localhost:8080/text/site.com/?charset=windows-1252
Contributing
Reporting Bugs
Found a bug? Please open an issue with:
- Clear description of the problem
- Steps to reproduce
- Expected vs. actual behavior
- Server logs (if relevant)
Submitting Changes
We welcome pull requests! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Write clean, well-commented code
- Test your changes:
go test ./... - Submit a pull request with description
Code Quality
- Run
go fmt ./...before committing - Run
go vet ./...to check for issues - Write tests for new features
- Keep commits atomic and descriptive
Project Structure
.
├── main.go # HTTP handler and server setup
├── config/ # Configuration loading
├── handlers/ # Protocol response conversion to HTML
├── protocols/ # Protocol clients (gemini, gopher, etc.)
├── proxy/ # URL parsing and utilities
├── middleware/ # HTTP middleware (rate limiting, etc.)
├── templates/ # HTML templates
├── static/ # CSS and static assets
└── docker-compose.yml # Docker deployment
Performance
- Single binary — ~20 MB executable
- Minimal memory — <50 MB at rest, scales with concurrent users
- Streaming — handles large responses without buffering
- Rate limiting — prevents resource exhaustion
- Caching — emoji favicons cached per domain
License
CC0 — Public Domain
This project is released into the public domain. You are free to use, modify, and distribute it without restriction.
Original Smolnet Portal by Michael Lazar under the Human Software License.
Acknowledgments
- Michael Lazar — Original Python/Quart implementation
- Gemini Protocol — Small internet pioneers
- Gopher Community — For keeping the internet weird and small
- Contributors — Bug fixes, features, and improvements
Further Reading
- Gemini Protocol — Modern, privacy-respecting web protocol
- Gopher Protocol — Retro internet classic
- Spartan Protocol — Lightweight web alternative
- Smolnet — Small internet philosophy
- Project Gemini — Alternative web vision
Made with ❤️ for the small internet