Skip to content

aduggleby/octoporty

Repository files navigation

Octoporty

Octoporty

Self-hosted reverse proxy tunneling solution

Website GitHub License .NET

Octoporty is a self-hosted alternative to ngrok that lets you expose internal services through a public endpoint. Deploy the Gateway on a cloud server with a public IP, run the Agent inside your private network, and securely tunnel traffic to your internal services.

Table of Contents

Features

  • Self-Hosted - Full control over your infrastructure and data
  • WebSocket Tunnel - Efficient binary protocol with MessagePack serialization and Lz4 compression
  • WebSocket Proxy - End-to-end WebSocket forwarding for real-time applications (chat, live dashboards, hot reload)
  • Automatic HTTPS - Caddy integration provides automatic TLS certificates via Let's Encrypt
  • Web Management UI - React-based dashboard for managing port mappings
  • Multi-Domain Support - Route multiple domains to different internal services
  • Automatic Reconnection - Agent maintains persistent connection with exponential backoff
  • Gateway Self-Update - Update the Gateway from the Agent UI when version mismatch is detected
  • Customizable Landing Page - Edit the HTML shown when no mapping exists for a domain
  • Agent Logs - Real-time streaming of Agent process logs in the Web UI with historical retrieval
  • Request Logging - Audit trail for all tunneled requests
  • Rate Limiting - Built-in protection against brute force attacks
  • Startup Banner - Visual configuration display at startup with obfuscated secrets for easy verification
  • Import/Export - Export and import port mapping definitions as JSON for backup, migration, or sharing between Agents
  • SQLite Backup - Download a consistent SQLite database backup from the Web UI
  • Automatic Database Setup - Agent auto-applies migrations on startup, no manual database setup required
  • Docker Ready - Multi-arch container images (amd64/arm64) with minimal attack surface

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              INTERNET                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         CLOUD / PUBLIC SERVER                               │
│  ┌─────────────┐       ┌─────────────────────┐                              │
│  │    Caddy    │──────▶│  Octoporty Gateway  │                              │
│  │ (HTTPS/TLS) │       │   (WebSocket Hub)   │                              │
│  └─────────────┘       └─────────────────────┘                              │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                            WebSocket Tunnel
                         (MessagePack + Lz4)
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           PRIVATE NETWORK                                   │
│  ┌─────────────────────┐       ┌─────────────────────────────────────────┐  │
│  │   Octoporty Agent   │──────▶│          Internal Services              │  │
│  │  (Tunnel Client +   │       │  ┌─────────┐ ┌─────────┐ ┌─────────┐   │  │
│  │   Web UI @ :17201)  │       │  │ Web App │ │   API   │ │ Database│   │  │
│  └─────────────────────┘       │  │  :3000  │ │  :8080  │ │  :5432  │   │  │
│                                │  └─────────┘ └─────────┘ └─────────┘   │  │
│                                └─────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘

Components

Component Description
Octoporty.Gateway Cloud-deployed WebSocket server that receives external traffic and routes to connected Agents
Octoporty.Agent Runs inside private network, maintains tunnel to Gateway, forwards requests to internal services
Octoporty.Agent.Web React SPA for managing port mappings (embedded in Agent)
Octoporty.Shared Shared entities, contracts, options, and logging extensions

Tunnel Protocol

The tunnel uses WebSocket with MessagePack binary serialization and Lz4 compression for efficient data transfer.

Message Flow:

  1. Authentication - Agent connects and authenticates with pre-shared API key (includes WebSocket proxy capability negotiation)
  2. Configuration Sync - Agent sends its port mapping configuration to Gateway
  3. Heartbeat Loop - Maintains connection health with periodic pings
  4. Request/Response - Gateway forwards incoming HTTP requests through the tunnel
  5. WebSocket Proxy - Gateway detects WebSocket upgrade requests and relays frames bidirectionally through the tunnel to the internal service

Quick Start

The fastest way to get started is with Docker Compose:

# Clone the repository
git clone https://github.com/aduggleby/octoporty.git
cd octoporty

# Copy and configure environment
cp .env.example .env
# Edit .env with your settings

# Start the development environment
docker compose -f infrastructure/docker-compose.dev.yml up --build

Access the Agent web UI at http://localhost:17201

Installation

Docker (Recommended)

Gateway Deployment:

# docker-compose.gateway.yml
services:
  gateway:
    image: ghcr.io/aduggleby/octoporty-gateway:latest
    environment:
      - Gateway__ApiKey=your-secure-api-key-min-32-chars
      - Gateway__CaddyAdminUrl=http://caddy:2019
    ports:
      - "17200:17200"

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

volumes:
  caddy_data:

Agent Deployment:

# docker-compose.agent.yml
services:
  agent:
    image: ghcr.io/aduggleby/octoporty-agent:latest
    environment:
      - Agent__GatewayFqdn=your-gateway-domain.com
      - Agent__ApiKey=your-secure-api-key-min-32-chars
      - Agent__JwtSecret=your-jwt-secret-min-32-chars
      # Generate hash with: openssl passwd -6 "your-password"
      - Agent__Auth__PasswordHash=$6$rounds=5000$yoursalt$yourhash
    ports:
      - "17201:17201"
    volumes:
      - agent_data:/app/data

volumes:
  agent_data:

Manual Installation

Prerequisites:

  • .NET 10 SDK
  • Node.js 20+ (for frontend build)

Build from source:

# Clone repository
git clone https://github.com/aduggleby/octoporty.git
cd octoporty

# Build all .NET projects
dotnet build

# Build frontend (outputs to Agent's wwwroot)
cd src/Octoporty.Agent.Web
npm install
npm run build
cd ../..

# Run Gateway
dotnet run --project src/Octoporty.Gateway

# Run Agent (in separate terminal)
dotnet run --project src/Octoporty.Agent

Updating

Quick Update (Recommended)

Update to the latest version with a single command:

Gateway:

curl -fsSL https://octoporty.com/update-gateway.sh | bash

Agent:

curl -fsSL https://octoporty.com/update-agent.sh | bash

Manual Update

If you prefer to update manually:

Gateway:

cd /opt/octoporty/gateway

# Pull latest images
docker compose pull

# Restart services
docker compose down
docker compose up -d

# Verify the update
docker compose logs -f gateway

Agent:

cd /opt/octoporty/agent

# Pull latest images
docker compose pull

# Restart services
docker compose down
docker compose up -d

# Verify the update
docker compose logs -f agent

Checking Versions

# Check Gateway version
docker inspect ghcr.io/aduggleby/octoporty-gateway:latest --format='{{index .Config.Labels "org.opencontainers.image.version"}}'

# Check Agent version
docker inspect ghcr.io/aduggleby/octoporty-agent:latest --format='{{index .Config.Labels "org.opencontainers.image.version"}}'

Gateway Self-Update

When you update the Agent to a newer version, it can detect that the Gateway is running an older version. The Agent UI will display a notification banner with an "Update Gateway" button.

How it works:

  1. Update the Agent first (using the methods above)
  2. When the Agent connects to the Gateway, it compares versions
  3. If the Agent is newer, a yellow banner appears in the Agent UI
  4. Click "Update Gateway" to trigger a remote update
  5. A status modal appears showing real-time update progress, polling the Gateway every 5 seconds until the new version is confirmed
  6. The Gateway writes a signal file that the host watcher monitors
  7. Within 30 seconds, the Gateway is automatically pulled and restarted, and the modal auto-closes

Installing the Auto-Updater:

If your Gateway was installed before the auto-updater was included, you can add it to an existing installation:

curl -fsSL https://octoporty.com/install-updater.sh | sudo bash

This installs a systemd timer that checks for update signal files every 30 seconds. New Gateway installations include the auto-updater automatically.

Configuration:

Variable Description Default
Gateway__AllowRemoteUpdate Enable/disable remote update requests true
Gateway__UpdateSignalPath Path to the update signal file /data/update-signal

Security: Remote updates are only accepted over authenticated WebSocket connections (API key validated). The update signal file is only writable by the Gateway container, and the host watcher runs separately with access to Docker.

Configuration

Gateway Configuration

Variable Description Default
Gateway__ApiKey Pre-shared key for Agent authentication (min 32 chars) Required
Gateway__CaddyAdminUrl Caddy Admin API endpoint http://localhost:2019
Gateway__Port Gateway listening port 17200
Gateway__AllowRemoteUpdate Allow Agents to trigger Gateway self-updates true
Gateway__UpdateSignalPath Path for update signal file /data/update-signal
Gateway__RemoveRoutesOnTunnelUnavailable Remove Caddy routes when the tunnel is unavailable. Disabled by default to avoid Caddy reloads dropping long-lived connections (e.g., when the Agent tunnels through Caddy). false

Agent Configuration

Variable Description Default
Agent__GatewayFqdn Gateway domain (e.g., gateway.example.com). Recommended over GatewayUrl. -
Agent__GatewayUrl WebSocket URL to Gateway. If not set, derived from GatewayFqdn. Derived
Agent__ApiKey Pre-shared key matching Gateway Required
Agent__JwtSecret JWT signing key (min 32 chars) Required
Agent__Auth__PasswordHash SHA-512 crypt hash for Web UI login (generate with openssl passwd -6) Required
Agent__Port Agent web UI port 17201
Logging__FilePath Rolling log file path for Agent log streaming /var/log/octoporty/agent-.log
ConnectionStrings__DefaultConnection Database connection string SQLite at /app/data/octoporty.db

Port Assignments

Octoporty uses port range 17200-17299:

Port Service
17200 Gateway
17201 Agent Web UI
17202 Caddy Admin API
17280 Caddy HTTP
17243 Caddy HTTPS

Web Interface

The Agent includes an embedded React web application for managing port mappings.

Screenshots

Login Screen
Login Screen

Dashboard
Dashboard - Overview of tunnel status and gateway information

Mappings
Mappings - View and manage port mappings

Features

  • Dashboard - Overview of tunnel status and active mappings
  • Port Mappings - Create, edit, and delete domain-to-service mappings
  • Request Inspector - Debug tunnel routing by comparing Gateway vs Agent responses for any URL, with timing and header analysis
  • Settings - Customize the Gateway landing page with your own HTML and branding
  • Gateway Logs - Real-time streaming of Gateway logs with historical log retrieval and infinite scroll
  • Gateway Log Detail - Detailed log inspection view with search, level filtering, and full message payload display for debugging tunnel issues
  • Agent Logs - Real-time streaming of Agent process logs with level filtering, auto-scroll, and infinite scroll for historical entries
  • Import/Export - Export and import port mapping definitions as JSON, or download a full SQLite database backup
  • Caddy Configuration - View the current Caddy reverse proxy configuration for debugging and monitoring
  • Connection Logs - View connection history and status
  • Request Logs - Audit trail of all tunneled requests

Creating a Port Mapping

  1. Navigate to Mappings in the sidebar
  2. Click Create Mapping
  3. Enter the external domain (e.g., app.yourdomain.com)
  4. Enter the internal host and port (e.g., localhost:3000)
  5. Configure TLS options if needed
  6. Click Save

The Gateway will automatically configure Caddy to route traffic for the domain through the tunnel.

Development

Project Structure

octoporty/
├── src/
│   ├── Octoporty.Gateway/      # Gateway service
│   ├── Octoporty.Agent/        # Agent service + API
│   ├── Octoporty.Agent.Web/    # React frontend
│   └── Octoporty.Shared/       # Shared library
├── tests/
│   └── Octoporty.Tests.E2E/    # End-to-end tests
├── infrastructure/
│   ├── docker-compose.yml      # Production compose
│   ├── docker-compose.dev.yml  # Development compose
│   └── Caddyfile               # Caddy configuration
└── CLAUDE.md                   # AI assistant instructions

Running Tests

# Run all E2E tests
cd tests/Octoporty.Tests.E2E
dotnet test

# Run specific test category
dotnet test --filter "FullyQualifiedName~MappingsApi"
dotnet test --filter "FullyQualifiedName~ComprehensiveUi"

Technology Stack

Backend:

  • .NET 10
  • FastEndpoints (API framework)
  • Entity Framework Core (SQLite)
  • SignalR (WebSocket management)
  • MessagePack (binary serialization)
  • Serilog (structured logging)

Frontend:

  • React 19
  • TypeScript
  • Tailwind CSS 4
  • Vite (build tool)
  • Motion (animations)

Infrastructure:

  • Docker (multi-arch images for amd64/arm64)
  • Caddy (reverse proxy + auto HTTPS)
  • GitHub Container Registry

API Reference

Port Mappings API

Method Endpoint Description
GET /api/mappings List all port mappings
GET /api/mappings/{id} Get mapping by ID
POST /api/mappings Create new mapping
PUT /api/mappings/{id} Update mapping
DELETE /api/mappings/{id} Delete mapping

Settings API

Method Endpoint Description
GET /api/v1/settings/landing-page Get current landing page HTML and hash
PUT /api/v1/settings/landing-page Update landing page HTML (max 1MB)
DELETE /api/v1/settings/landing-page Reset to default Octoporty landing page

Gateway API

Method Endpoint Description
GET /api/v1/gateway/caddy-config Get current Caddy reverse proxy configuration from the Gateway

Agent Logs API

Method Endpoint Description
GET /api/v1/agent/logs Get Agent process logs with pagination (beforeId, count query params)

Import/Export API

Method Endpoint Description
GET /api/v1/import-export/export Export port mappings and landing page as JSON
POST /api/v1/import-export/import Import port mappings from JSON (merge-only: upserts by domain, does not delete)
GET /api/v1/import-export/sqlite Download a consistent SQLite database backup

Diagnostics API

Method Endpoint Description
POST /api/v1/test/diagnose Compare Gateway vs Agent responses for a URL to debug routing issues

Authentication

The Agent Web UI uses JWT authentication with HttpOnly cookies. Login via:

POST /api/v1/auth/login
Content-Type: application/json

{
  "password": "your-password"
}

The password is verified against the SHA-512 crypt hash configured in Agent__Auth__PasswordHash.

Security

Authentication

  • Agent-Gateway: Pre-shared API key with constant-time comparison to prevent timing attacks
  • Web UI: JWT with HttpOnly cookies, refresh tokens stored in memory
  • Rate Limiting: Login endpoint with exponential backoff lockout (1min, 5min, 15min, 1hr)

Network Security

  • All external traffic should go through Caddy with TLS
  • WebSocket tunnel uses secure WebSocket (WSS) in production
  • Internal services are never directly exposed to the internet

Best Practices

  1. Use strong, unique API keys (minimum 32 characters)
  2. Enable TLS for all external connections
  3. Regularly rotate credentials
  4. Monitor request logs for suspicious activity
  5. Keep all components updated

Troubleshooting

Agent won't connect to Gateway

  1. Verify Agent__GatewayUrl is correct and reachable
  2. Check that API keys match on both sides
  3. Ensure Gateway is running and healthy
  4. Check firewall rules allow WebSocket connections

Requests not reaching internal service

  1. Use the Request Inspector (under Gateway in the sidebar) to compare Gateway vs Agent responses
  2. Verify port mapping configuration
  3. Check internal service is running and accessible from Agent
  4. Review Agent logs for forwarding errors
  5. Ensure internal host is resolvable from Agent container

"Bad Gateway" with TLS frame errors

If you see an error like Cannot determine the frame size or corrupted frame, the mapping is likely set to HTTPS but the upstream service is speaking plain HTTP (or the port is wrong). Change the mapping's internal protocol to HTTP or fix the port.

Web UI login fails

  1. Verify password hash is set correctly (generate with openssl passwd -6 "your-password")
  2. Check for rate limiting lockout (try again after the lockout period)
  3. Ensure JWT secret is configured
  4. Clear browser cookies and try again

Data directory permission errors

If the Agent fails to start with a message about the data directory not being writable, the container cannot write to the mounted volume. The /app/data directory is created at runtime and inherits the container's UID.

For Docker Compose with bind mounts:

# Option 1: Run container as your host user (recommended)
# Add to docker-compose.yml: user: "1000:1000"

# Option 2: Change ownership to match your host user
sudo chown -R 1000:1000 /path/to/your/data

For TrueNAS SCALE:

  1. In the app configuration, go to Resources and Devices > Security Context
  2. Set User ID to 568 (the TrueNAS apps user)
  3. Ensure your dataset is owned by the apps user (568:568)

For other NAS platforms: Set the container to run as a user that has write access to the data directory, or change the directory ownership to match the container's runtime UID.

Startup Banner

When the Gateway or Agent starts, it displays a startup banner with the current configuration. This helps verify that environment variables are loaded correctly. Sensitive values (API keys, passwords, secrets) are obfuscated, showing only the first 2 and last 2 characters.

Example Agent output:

   ██████╗  ██████╗████████╗ ██████╗ ██████╗  ██████╗ ██████╗ ████████╗██╗   ██╗
  ██╔═══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝╚██╗ ██╔╝
  ██║   ██║██║        ██║   ██║   ██║██████╔╝██║   ██║██████╔╝   ██║    ╚████╔╝
  ██║   ██║██║        ██║   ██║   ██║██╔═══╝ ██║   ██║██╔══██╗   ██║     ╚██╔╝
  ╚██████╔╝╚██████╗   ██║   ╚██████╔╝██║     ╚██████╔╝██║  ██║   ██║      ██║
   ╚═════╝  ╚═════╝   ╚═╝    ╚═════╝ ╚═╝      ╚═════╝ ╚═╝  ╚═╝   ╚═╝      ╚═╝

  Agent v0.9.55
  ─────────────────────────────────────────────────────────────────────────
  GatewayUrl       : wss://gateway.example.com/tunnel
  ApiKey           : my****ey
  JwtSecret        : se****et
  PasswordHash     : $6****sh
  Environment      : Production
  Container UID:GID: 1000:1000

The Container UID:GID field displays the effective user and group IDs the process is running as (Linux only). This helps diagnose permission issues when the container can't write to mounted volumes.

Logs

# View Gateway logs
docker logs octoporty-gateway

# View Agent logs
docker logs octoporty-agent

# View Caddy logs
docker logs caddy

Contributing

Contributions are welcome! Please read the following guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Follow the code style guidelines in CLAUDE.md
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Code Style

  • Every file must have a header comment explaining its purpose
  • Document all branching logic with explanations
  • Use constant-time comparisons for security-sensitive operations
  • Follow existing patterns in the codebase

License

This project is licensed under the No'Saasy License (based on the O'Saasy License Agreement).

This means you can freely use, modify, and distribute the software, but you cannot offer it as a commercial SaaS product to third parties. Self-hosting for your own use is always permitted.

See LICENSE for the full license text.


Links


Made with care by Alex Duggleby

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors