feat: HAProxy IPv6, ACME fixes, deploy command, docs

HAProxy:
- Add IPv6 dual-stack binding (*:port,[::]:port)
- Exclude ACME challenges from HTTPS redirects
- Fix certificate path detection for multiple locations

Service Registry:
- Fix certificate expiry check paths (HAProxy, ACME, Let's Encrypt)
- BusyBox-compatible date parsing

local-build.sh:
- Add deploy command for automated package deployment
- Sync packages to router feed with index generation

Documentation:
- Add README for luci-app-haproxy
- Add README for luci-app-hexojs
- Add README for luci-app-metablogizer
- Add README for luci-app-mitmproxy
- Add README for luci-app-tor-shield

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-28 10:33:51 +01:00
parent 6b69b8de65
commit 8e7a5b1bb9
15 changed files with 4155 additions and 164 deletions

View File

@ -0,0 +1,449 @@
# ⚡ HAProxy Manager - Reverse Proxy Dashboard
Enterprise-grade reverse proxy management with automatic SSL certificates, vhost configuration, and backend health monitoring.
## ✨ Features
| Feature | Description |
|---------|-------------|
| 🌐 **Vhost Management** | Create and manage virtual hosts |
| 🔒 **ACME SSL** | Automatic Let's Encrypt certificates |
| ⚖️ **Load Balancing** | Round-robin, least-conn, source |
| 🏥 **Health Checks** | Backend server monitoring |
| 📊 **Statistics** | Real-time traffic dashboard |
| 🔧 **Config Generator** | Auto-generate HAProxy config |
| 🐳 **LXC Container** | Runs isolated in container |
## 🚀 Quick Start
### Create a Vhost
1. Go to **Services → HAProxy → Vhosts**
2. Click **+ Add Vhost**
3. Fill in:
- **Domain**: `app.example.com`
- **Backend**: Select or create
- **SSL**: ✅ Enable
- **ACME**: ✅ Auto-certificate
4. Click **Save & Apply**
### Architecture
```
┌─────────────────────────────────┐
Internet │ HAProxy Container │
│ │ ┌─────────────────────────┐ │
▼ │ │ Frontend │ │
┌─────────┐ │ │ :80 → :443 redirect │ │
│ Port 80 │──────►│ │ :443 → SSL terminate │ │
│Port 443 │ │ └──────────┬──────────────┘ │
└─────────┘ │ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Backends │ │
│ │ app.example.com →:8080 │ │
│ │ api.example.com →:3000 │ │
│ │ blog.example.com→:4000 │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
```
## 📊 Dashboard
```
┌──────────────────────────────────────────────────────┐
│ ⚡ HAProxy 🟢 Running │
├──────────────────────────────────────────────────────┤
│ │
│ 📊 Statistics │
│ ├─ 🌐 Vhosts: 5 active │
│ ├─ ⚙️ Backends: 8 configured │
│ ├─ 🔒 Certificates: 5 valid │
│ └─ 📈 Requests: 12.5K/min │
│ │
│ 🏥 Backend Health │
│ ┌────────────┬────────┬────────┬─────────┐ │
│ │ Backend │ Status │ Server │ Latency │ │
│ ├────────────┼────────┼────────┼─────────┤ │
│ │ webapp │ 🟢 UP │ 2/2 │ 12ms │ │
│ │ api │ 🟢 UP │ 1/1 │ 8ms │ │
│ │ blog │ 🟡 DEG │ 1/2 │ 45ms │ │
│ └────────────┴────────┴────────┴─────────┘ │
│ │
└──────────────────────────────────────────────────────┘
```
## 🌐 Vhost Configuration
### Create Vhost
```bash
ubus call luci.haproxy create_vhost '{
"domain": "app.example.com",
"backend": "webapp",
"ssl": 1,
"ssl_redirect": 1,
"acme": 1,
"enabled": 1
}'
```
### Vhost Options
| Option | Default | Description |
|--------|---------|-------------|
| `domain` | - | Domain name (required) |
| `backend` | - | Backend name to route to |
| `ssl` | 1 | Enable SSL/TLS |
| `ssl_redirect` | 1 | Redirect HTTP to HTTPS |
| `acme` | 1 | Auto-request Let's Encrypt cert |
| `enabled` | 1 | Vhost active |
### List Vhosts
```bash
ubus call luci.haproxy list_vhosts
# Response:
{
"vhosts": [{
"id": "app_example_com",
"domain": "app.example.com",
"backend": "webapp",
"ssl": true,
"ssl_redirect": true,
"acme": true,
"enabled": true,
"cert_status": "valid",
"cert_expiry": "2025-03-15"
}]
}
```
## ⚙️ Backend Configuration
### Create Backend
```bash
ubus call luci.haproxy create_backend '{
"name": "webapp",
"mode": "http",
"balance": "roundrobin"
}'
```
### Add Server to Backend
```bash
ubus call luci.haproxy create_server '{
"backend": "webapp",
"name": "srv1",
"address": "192.168.255.10",
"port": 8080,
"weight": 100,
"check": 1
}'
```
### Backend Modes
| Mode | Description |
|------|-------------|
| `http` | Layer 7 HTTP proxy |
| `tcp` | Layer 4 TCP proxy |
### Load Balancing
| Algorithm | Description |
|-----------|-------------|
| `roundrobin` | Rotate through servers |
| `leastconn` | Least active connections |
| `source` | Sticky by client IP |
| `uri` | Sticky by URI hash |
## 🔒 SSL Certificates
### ACME Auto-Certificates
When `acme: 1` is set:
1. HAProxy serves ACME challenge on port 80
2. Let's Encrypt validates domain ownership
3. Certificate stored in `/srv/haproxy/certs/`
4. Auto-renewal before expiry
### Manual Certificate
```bash
# Upload certificate
ubus call luci.haproxy upload_certificate '{
"domain": "app.example.com",
"cert": "<PEM certificate>",
"key": "<PEM private key>"
}'
```
### Certificate Status
```bash
ubus call luci.haproxy list_certificates
# Response:
{
"certificates": [{
"domain": "app.example.com",
"status": "valid",
"issuer": "Let's Encrypt",
"expiry": "2025-03-15",
"days_left": 45
}]
}
```
### Request Certificate Manually
```bash
ubus call luci.haproxy request_certificate '{"domain":"app.example.com"}'
```
## 📊 Statistics
### Get Stats
```bash
ubus call luci.haproxy get_stats
# Response:
{
"frontend": {
"requests": 125000,
"bytes_in": 1234567890,
"bytes_out": 9876543210,
"rate": 150
},
"backends": [{
"name": "webapp",
"status": "UP",
"servers_up": 2,
"servers_total": 2,
"requests": 45000,
"response_time_avg": 12
}]
}
```
### Stats Page
Access HAProxy stats at:
```
http://192.168.255.1:8404/stats
```
## 🔧 Configuration
### UCI Structure
```bash
# /etc/config/haproxy
config haproxy 'main'
option enabled '1'
option stats_port '8404'
config backend 'webapp'
option name 'webapp'
option mode 'http'
option balance 'roundrobin'
option enabled '1'
config server 'webapp_srv1'
option backend 'webapp'
option name 'srv1'
option address '192.168.255.10'
option port '8080'
option weight '100'
option check '1'
option enabled '1'
config vhost 'app_example_com'
option domain 'app.example.com'
option backend 'webapp'
option ssl '1'
option ssl_redirect '1'
option acme '1'
option enabled '1'
```
### Generate Config
```bash
# Regenerate haproxy.cfg from UCI
ubus call luci.haproxy generate
# Reload HAProxy
ubus call luci.haproxy reload
```
### Validate Config
```bash
ubus call luci.haproxy validate
# Response:
{
"valid": true,
"message": "Configuration is valid"
}
```
## 📡 RPCD API
### Service Control
| Method | Description |
|--------|-------------|
| `status` | Get HAProxy status |
| `start` | Start HAProxy service |
| `stop` | Stop HAProxy service |
| `restart` | Restart HAProxy |
| `reload` | Reload configuration |
| `generate` | Generate config file |
| `validate` | Validate configuration |
### Vhost Management
| Method | Description |
|--------|-------------|
| `list_vhosts` | List all vhosts |
| `create_vhost` | Create new vhost |
| `update_vhost` | Update vhost |
| `delete_vhost` | Delete vhost |
### Backend Management
| Method | Description |
|--------|-------------|
| `list_backends` | List all backends |
| `create_backend` | Create backend |
| `delete_backend` | Delete backend |
| `create_server` | Add server to backend |
| `delete_server` | Remove server |
### Certificates
| Method | Description |
|--------|-------------|
| `list_certificates` | List all certs |
| `request_certificate` | Request ACME cert |
| `upload_certificate` | Upload manual cert |
| `delete_certificate` | Delete certificate |
## 📁 File Locations
| Path | Description |
|------|-------------|
| `/etc/config/haproxy` | UCI configuration |
| `/var/lib/lxc/haproxy/` | LXC container root |
| `/srv/haproxy/haproxy.cfg` | Generated config |
| `/srv/haproxy/certs/` | SSL certificates |
| `/srv/haproxy/acme/` | ACME challenges |
| `/usr/libexec/rpcd/luci.haproxy` | RPCD backend |
| `/usr/sbin/haproxyctl` | CLI tool |
## 🛠️ CLI Tool
### haproxyctl Commands
```bash
# Status
haproxyctl status
# List vhosts
haproxyctl vhosts
# Add vhost
haproxyctl vhost add app.example.com --backend webapp --ssl --acme
# Remove vhost
haproxyctl vhost del app.example.com
# List certificates
haproxyctl cert list
# Request certificate
haproxyctl cert add app.example.com
# Generate config
haproxyctl generate
# Reload
haproxyctl reload
# Validate
haproxyctl validate
```
## 🛠️ Troubleshooting
### HAProxy Won't Start
```bash
# Check container
lxc-info -n haproxy
# Start container
lxc-start -n haproxy
# Check logs
lxc-attach -n haproxy -- cat /var/log/haproxy.log
```
### 503 Service Unavailable
1. Check backend is configured:
```bash
ubus call luci.haproxy list_backends
```
2. Verify server is reachable:
```bash
curl http://192.168.255.10:8080
```
3. Check HAProxy logs
### Certificate Not Working
1. Ensure DNS resolves to your public IP
2. Ensure ports 80/443 accessible from internet
3. Check ACME challenge:
```bash
curl http://app.example.com/.well-known/acme-challenge/test
```
### Config Validation Fails
```bash
# Show validation errors
lxc-attach -n haproxy -- haproxy -c -f /etc/haproxy/haproxy.cfg
```
## 🔒 Security
### Firewall Rules
HAProxy needs ports 80/443 open from WAN:
```bash
# Auto-created when vhost uses SSL
uci show firewall | grep HAProxy
```
### Rate Limiting
Add to backend config:
```
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
```
## 📜 License
MIT License - Copyright (C) 2025 CyberMind.fr

View File

@ -0,0 +1,405 @@
# 📰 Hexo CMS - Blog Publishing Platform
Full-featured Hexo blog management with multi-instance support, Gitea integration, HAProxy publishing, and Tor hidden services.
## ✨ Features
| Feature | Description |
|---------|-------------|
| 📝 **Post Editor** | Create, edit, publish posts with markdown |
| 📁 **Categories/Tags** | Organize content hierarchically |
| 🖼️ **Media Library** | Manage images and assets |
| 🎨 **Theme Config** | Edit Hexo theme settings |
| 🚀 **One-Click Deploy** | Generate and deploy with single click |
| 🔗 **HAProxy Integration** | Auto-publish to clearnet with SSL |
| 🧅 **Tor Hidden Services** | Publish to .onion addresses |
| 📦 **Gitea Sync** | Push/pull from Git repositories |
| 🧙 **Publishing Profiles** | Wizard presets for common setups |
| 📊 **Health Monitoring** | Pipeline status and diagnostics |
## 🚀 Quick Start Wizard
### Publishing Profiles
Choose a preset to configure your blog:
| Profile | Icon | HAProxy | Tor | Use Case |
|---------|------|---------|-----|----------|
| 🌐 **Blog** | 📰 | ✅ SSL | ❌ | Public blog with custom domain |
| 🎨 **Portfolio** | 🖼️ | ✅ SSL | ❌ | Creative showcase |
| 🔒 **Privacy** | 🧅 | ❌ | ✅ | Anonymous .onion blog |
| 🌍 **Dual** | 🌐🧅 | ✅ | ✅ | Clearnet + Tor access |
| 📚 **Documentation** | 📖 | ✅ SSL | ❌ | Technical docs site |
### Apply a Profile
```bash
# Via LuCI: Services → Hexo CMS → Profiles → Apply
# Via CLI
ubus call luci.hexojs apply_profile '{
"instance": "default",
"profile": "blog",
"domain": "blog.example.com"
}'
```
## 📊 Dashboard
```
┌──────────────────────────────────────────────────────┐
│ 📰 Hexo CMS 🟢 Running │
├──────────────────────────────────────────────────────┤
│ │
│ 📊 Site Stats │
│ ├─ 📝 Posts: 134 │
│ ├─ 📁 Categories: 12 │
│ ├─ 🏷️ Tags: 45 │
│ └─ 🖼️ Media: 89 files │
│ │
│ 🔗 Endpoints │
│ ├─ 🏠 Local: http://192.168.255.1:4000 │
│ ├─ 🌐 Clearnet: https://blog.example.com │
│ └─ 🧅 Tor: http://abc123xyz.onion │
│ │
│ 📈 Pipeline Health: 95/100 │
│ ├─ ✅ Hexo Server: Running │
│ ├─ ✅ HAProxy: Published │
│ ├─ ✅ Certificate: Valid (45 days) │
│ └─ ✅ Gitea: Synced │
│ │
└──────────────────────────────────────────────────────┘
```
## 📝 Content Management
### Create a Post
1. Go to **Services → Hexo CMS → Posts**
2. Click **+ New Post**
3. Fill in:
- **Title**: My First Post
- **Category**: tech/tutorials
- **Tags**: hexo, blog
- **Content**: Your markdown here
4. Click **Save Draft** or **Publish**
### Post Front Matter
```yaml
---
title: My First Post
date: 2025-01-28 10:30:00
categories:
- tech
- tutorials
tags:
- hexo
- blog
---
Your content here...
```
### List Posts via CLI
```bash
ubus call luci.hexojs list_posts '{"instance":"default","limit":10}'
```
## 🚀 Publishing Pipeline
### Full Publish Flow
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Edit │ → │ Generate│ → │ Deploy │ → │ Live │
│ Posts │ │ HTML │ │ HAProxy │ │ Online │
└─────────┘ └─────────┘ │ Tor │ └─────────┘
└─────────┘
```
### Commands
```bash
# Generate static files
ubus call luci.hexojs generate '{"instance":"default"}'
# Deploy to HAProxy (clearnet)
ubus call luci.hexojs publish_to_haproxy '{
"instance": "default",
"domain": "blog.example.com"
}'
# Deploy to Tor (.onion)
ubus call luci.hexojs publish_to_tor '{"instance":"default"}'
# Full pipeline (generate + deploy all)
ubus call luci.hexojs full_publish '{
"instance": "default",
"domain": "blog.example.com",
"tor": true
}'
```
## 🔗 HAProxy Integration
### Publish to Clearnet
1. Go to **Hexo CMS → Publishing**
2. Enter domain: `blog.example.com`
3. Check **Enable SSL**
4. Click **Publish to HAProxy**
### What Happens
1. ✅ Creates HAProxy backend → `hexo_default`
2. ✅ Creates HAProxy server → `127.0.0.1:4000`
3. ✅ Creates vhost → `blog.example.com`
4. ✅ Requests ACME certificate
5. ✅ Reloads HAProxy
### Check HAProxy Status
```bash
ubus call luci.hexojs get_haproxy_status '{"instance":"default"}'
# Response:
{
"published": true,
"domain": "blog.example.com",
"ssl": true,
"cert_status": "valid",
"cert_days": 45,
"dns_status": "ok"
}
```
## 🧅 Tor Hidden Services
### Create .onion Site
```bash
ubus call luci.hexojs publish_to_tor '{"instance":"default"}'
```
### Get Onion Address
```bash
ubus call luci.hexojs get_tor_status '{"instance":"default"}'
# Response:
{
"enabled": true,
"onion_address": "abc123xyz...def.onion",
"virtual_port": 80,
"status": "active"
}
```
### Access via Tor Browser
```
http://abc123xyz...def.onion
```
## 📦 Gitea Integration
### Setup Gitea Sync
1. Go to **Hexo CMS → Git**
2. Enter repository: `user/myblog`
3. Configure credentials (optional)
4. Click **Clone** or **Pull**
### Webhook Auto-Deploy
Enable automatic deployment when you push to Gitea:
```bash
ubus call luci.hexojs setup_webhook '{
"instance": "default",
"auto_build": true
}'
```
### Git Operations
```bash
# Clone repository
ubus call luci.hexojs git_clone '{
"instance": "default",
"url": "http://192.168.255.1:3000/user/myblog.git"
}'
# Pull latest
ubus call luci.hexojs git_pull '{"instance":"default"}'
# Push changes
ubus call luci.hexojs git_push '{"instance":"default"}'
# View log
ubus call luci.hexojs git_log '{"instance":"default","limit":10}'
```
## 📊 Health Monitoring
### Instance Health Score
```bash
ubus call luci.hexojs get_instance_health '{"instance":"default"}'
# Response:
{
"instance": "default",
"score": 95,
"status": "healthy",
"checks": {
"hexo_running": true,
"content_exists": true,
"haproxy_published": true,
"ssl_valid": true,
"dns_resolves": true,
"git_clean": true
},
"issues": []
}
```
### Health Score Breakdown
| Check | Points | Description |
|-------|--------|-------------|
| Hexo Running | 20 | Server process active |
| Content Exists | 15 | Posts directory has content |
| HAProxy Published | 20 | Vhost configured |
| SSL Valid | 15 | Certificate not expiring |
| DNS Resolves | 15 | Domain points to server |
| Git Clean | 15 | No uncommitted changes |
### Pipeline Status
```bash
ubus call luci.hexojs get_pipeline_status
# Returns status of all instances
```
## 🔧 Configuration
### UCI Settings
```bash
# /etc/config/hexojs
config hexojs 'main'
option enabled '1'
option instances_root '/srv/hexojs/instances'
option content_root '/srv/hexojs/content'
config instance 'default'
option name 'default'
option enabled '1'
option port '4000'
option theme 'landscape'
# HAProxy
option haproxy_enabled '1'
option haproxy_domain 'blog.example.com'
option haproxy_ssl '1'
# Tor
option tor_enabled '1'
option tor_onion 'abc123...onion'
# Gitea
option gitea_repo 'user/myblog'
option gitea_auto_build '1'
```
## 📁 File Locations
| Path | Description |
|------|-------------|
| `/etc/config/hexojs` | UCI configuration |
| `/srv/hexojs/instances/` | Instance directories |
| `/srv/hexojs/content/` | Shared content (posts, media) |
| `/srv/hexojs/content/source/_posts/` | Blog posts |
| `/srv/hexojs/content/source/images/` | Media files |
| `/usr/libexec/rpcd/luci.hexojs` | RPCD backend |
## 📡 RPCD Methods
### Content Management
| Method | Description |
|--------|-------------|
| `list_posts` | List all posts |
| `get_post` | Get single post content |
| `create_post` | Create new post |
| `update_post` | Update post content |
| `delete_post` | Delete a post |
| `publish_post` | Move draft to published |
| `search_posts` | Search posts by query |
### Site Operations
| Method | Description |
|--------|-------------|
| `generate` | Generate static HTML |
| `clean` | Clean generated files |
| `deploy` | Deploy to configured targets |
| `preview_start` | Start preview server |
| `preview_status` | Check preview server |
### Publishing
| Method | Description |
|--------|-------------|
| `publish_to_haproxy` | Publish to clearnet |
| `unpublish_from_haproxy` | Remove from HAProxy |
| `publish_to_tor` | Create Tor hidden service |
| `unpublish_from_tor` | Remove Tor service |
| `full_publish` | Complete pipeline |
### Monitoring
| Method | Description |
|--------|-------------|
| `get_instance_health` | Health score & checks |
| `get_pipeline_status` | All instances status |
| `get_instance_endpoints` | All URLs for instance |
## 🛠️ Troubleshooting
### Hexo Server Won't Start
```bash
# Check if port is in use
netstat -tln | grep 4000
# Check logs
logread | grep hexo
# Restart manually
/etc/init.d/hexojs restart
```
### Posts Not Showing
1. Check posts are in `/srv/hexojs/content/source/_posts/`
2. Verify front matter format is correct
3. Run `hexo clean && hexo generate`
### HAProxy 503 Error
1. Verify Hexo is running on expected port
2. Check HAProxy backend configuration
3. Test local access: `curl http://127.0.0.1:4000`
### Git Push Fails
1. Check credentials: `ubus call luci.hexojs git_get_credentials`
2. Verify remote URL is correct
3. Check Gitea is accessible
## 📜 License
MIT License - Copyright (C) 2025 CyberMind.fr

View File

@ -22,7 +22,14 @@
"git_status",
"git_log",
"git_get_credentials",
"gitea_status"
"gitea_status",
"get_workflow_status",
"list_profiles",
"get_haproxy_status",
"get_tor_status",
"get_instance_endpoints",
"get_instance_health",
"get_pipeline_status"
]
},
"uci": ["hexojs"]
@ -57,7 +64,15 @@
"gitea_clone",
"gitea_sync",
"gitea_save_config",
"publish_to_www"
"publish_to_www",
"apply_profile",
"publish_to_haproxy",
"unpublish_from_haproxy",
"publish_to_tor",
"unpublish_from_tor",
"full_publish",
"handle_webhook",
"setup_webhook"
]
},
"uci": ["hexojs"]

View File

@ -0,0 +1,259 @@
# 📝 MetaBlogizer - Static Site Publisher
One-click static website hosting with automatic HAProxy vhosts, SSL certificates, and Gitea sync.
## ✨ Features
| Feature | Description |
|---------|-------------|
| 🌐 **Auto Vhost** | Creates HAProxy vhost + backend automatically |
| 🔒 **ACME SSL** | Automatic Let's Encrypt certificates |
| 📦 **Gitea Sync** | Pull from Gitea repositories |
| 📤 **File Upload** | Drag & drop file uploads |
| 📊 **Health Status** | DNS, certificate, and publish monitoring |
| 🔗 **QR Codes** | Share sites with QR codes |
## 🚀 Quick Start
### Create a Site via LuCI
1. Go to **Services → MetaBlogizer**
2. Click **+ New Site**
3. Fill in:
- **Site Name**: `myblog`
- **Domain**: `blog.example.com`
- **Gitea Repo**: `user/repo` (optional)
4. Click **Create**
### What Happens Automatically
```
┌─────────────────────────────────────────────────────┐
│ 📝 Create Site "myblog" @ blog.example.com │
├─────────────────────────────────────────────────────┤
│ 1. 📁 Create /srv/metablogizer/sites/myblog/ │
│ 2. 🌐 Create HAProxy backend (metablog_myblog) │
│ 3. 🔗 Create HAProxy vhost (blog.example.com) │
│ 4. 🔒 Request ACME certificate │
│ 5. 📄 Generate default index.html │
│ 6. ✅ Site live at https://blog.example.com │
└─────────────────────────────────────────────────────┘
```
## 📊 Dashboard
### Web Hosting Status Panel
The dashboard shows real-time health for all sites:
| Site | Domain | DNS | Resolved IP | Certificate | Status |
|------|--------|-----|-------------|-------------|--------|
| myblog | blog.example.com | 🌐 ok | 185.220.x.x | 🔒 45d | ✅ published |
| docs | docs.example.com | ❌ failed | - | ⚪ missing | 🕐 pending |
### Status Indicators
| Icon | DNS Status | Meaning |
|------|------------|---------|
| 🌐 | ok | DNS resolves to your public IP |
| ⚠️ | private | DNS points to private IP (192.168.x.x) |
| ❗ | mismatch | DNS points to different public IP |
| ❌ | failed | DNS resolution failed |
| Icon | Cert Status | Meaning |
|------|-------------|---------|
| 🔒 | ok | Certificate valid (30+ days) |
| ⚠️ | warning | Certificate expiring (7-30 days) |
| 🔴 | critical | Certificate critical (<7 days) |
| 💀 | expired | Certificate expired |
| ⚪ | missing | No certificate |
| Icon | Publish Status | Meaning |
|------|----------------|---------|
| ✅ | published | Site enabled with content |
| 🕐 | pending | Site enabled, no content yet |
| 📝 | draft | Site disabled |
## 📁 File Management
### Upload Files
1. Click **Upload** on a site card
2. Drag & drop files or click to browse
3. Check "Set first HTML as homepage" to use as index.html
4. Click **Upload**
### Manage Files
1. Click **Files** on a site card
2. View all uploaded files
3. 🏠 Set any HTML file as homepage
4. 🗑️ Delete files
## 🔄 Gitea Sync
### Setup
1. Create/edit a site
2. Enter Gitea repository: `username/repo`
3. Click **Sync** to pull latest
### Auto-Sync
The site syncs from Gitea on:
- Manual sync button click
- Webhook push (if configured)
```bash
# Manual sync via CLI
ubus call luci.metablogizer sync_site '{"id":"site_myblog"}'
```
## 📤 Share & QR
Click **Share** on any site to get:
- 📋 Copy URL to clipboard
- 📱 QR code for mobile access
- 🐦 Twitter share
- 💼 LinkedIn share
- 📘 Facebook share
- ✈️ Telegram share
- 📱 WhatsApp share
- ✉️ Email share
## 🔧 Configuration
### UCI Settings
```bash
# /etc/config/metablogizer
config metablogizer 'main'
option enabled '1'
option runtime 'auto' # auto | uhttpd | nginx
option sites_root '/srv/metablogizer/sites'
option nginx_container 'nginx'
option gitea_url 'http://192.168.255.1:3000'
config site 'site_myblog'
option name 'myblog'
option domain 'blog.example.com'
option gitea_repo 'user/myblog'
option ssl '1'
option enabled '1'
option description 'My personal blog'
option port '8901'
option runtime 'uhttpd'
```
### Runtime Modes
| Mode | Description | Use Case |
|------|-------------|----------|
| **uhttpd** | OpenWrt's built-in web server | Default, lightweight |
| **nginx** | Nginx in LXC container | Advanced features |
| **auto** | Auto-detect available runtime | Recommended |
## 📡 RPCD API
### Site Management
```bash
# List all sites
ubus call luci.metablogizer list_sites
# Create site
ubus call luci.metablogizer create_site '{
"name": "myblog",
"domain": "blog.example.com",
"gitea_repo": "user/myblog",
"ssl": "1",
"description": "My blog"
}'
# Sync from Gitea
ubus call luci.metablogizer sync_site '{"id":"site_myblog"}'
# Delete site
ubus call luci.metablogizer delete_site '{"id":"site_myblog"}'
```
### Health Monitoring
```bash
# Get hosting status for all sites
ubus call luci.metablogizer get_hosting_status
# Response:
{
"success": true,
"public_ip": "185.220.101.12",
"haproxy_status": "running",
"sites": [{
"id": "site_myblog",
"name": "myblog",
"domain": "blog.example.com",
"dns_status": "ok",
"dns_ip": "185.220.101.12",
"cert_status": "ok",
"cert_days": 45,
"publish_status": "published"
}]
}
# Check single site health
ubus call luci.metablogizer check_site_health '{"id":"site_myblog"}'
```
### File Operations
```bash
# List files in site
ubus call luci.metablogizer list_files '{"id":"site_myblog"}'
# Upload file (base64 content)
ubus call luci.metablogizer upload_file '{
"id": "site_myblog",
"filename": "style.css",
"content": "Ym9keSB7IGJhY2tncm91bmQ6ICNmZmY7IH0="
}'
```
## 📁 File Locations
| Path | Description |
|------|-------------|
| `/etc/config/metablogizer` | UCI configuration |
| `/srv/metablogizer/sites/` | Site content directories |
| `/srv/metablogizer/sites/<name>/index.html` | Site homepage |
| `/usr/libexec/rpcd/luci.metablogizer` | RPCD backend |
## 🛠️ Troubleshooting
### Site Shows 503
1. Check HAProxy is running: `lxc-info -n haproxy`
2. Check backend port is listening
3. Verify uhttpd instance: `uci show uhttpd | grep metablog`
### DNS Not Resolving
1. Verify A record points to your public IP
2. Check with: `nslookup blog.example.com`
3. Wait for DNS propagation (up to 48h)
### Certificate Missing
1. Ensure DNS resolves correctly first
2. Ensure ports 80/443 accessible from internet
3. Check ACME logs: `logread | grep acme`
### Gitea Sync Fails
1. Verify Gitea URL: `uci get metablogizer.main.gitea_url`
2. Check repository exists and is public
3. Test manually: `git clone http://192.168.255.1:3000/user/repo.git`
## 📜 License
MIT License - Copyright (C) 2025 CyberMind.fr

View File

@ -755,6 +755,302 @@ method_get_settings() {
json_dump
}
# Helper: Check DNS resolution for a domain
check_dns_resolution() {
local domain="$1"
local resolved_ip=""
if command -v nslookup >/dev/null 2>&1; then
resolved_ip=$(nslookup "$domain" 2>/dev/null | grep -A1 "Name:" | grep "Address" | head -1 | awk '{print $2}')
[ -z "$resolved_ip" ] && resolved_ip=$(nslookup "$domain" 2>/dev/null | grep "Address" | tail -1 | awk '{print $2}' | grep -v "^$")
elif command -v host >/dev/null 2>&1; then
resolved_ip=$(host "$domain" 2>/dev/null | grep "has address" | head -1 | awk '{print $4}')
fi
echo "$resolved_ip"
}
# Helper: Get public IPv4 address
get_public_ipv4() {
local ip=""
ip=$(wget -qO- -T 5 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n')
[ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://api.ipify.org" 2>/dev/null | tr -d '\n')
echo "$ip"
}
# Helper: Check certificate expiry
check_cert_expiry() {
local domain="$1"
local cert_file="/srv/haproxy/certs/${domain}.pem"
if [ ! -f "$cert_file" ]; then
cert_file="/etc/acme/${domain}_ecc/${domain}.cer"
[ ! -f "$cert_file" ] && cert_file="/etc/acme/${domain}/${domain}.cer"
fi
if [ -f "$cert_file" ]; then
local expiry_date
expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" 2>/dev/null | cut -d= -f2)
if [ -n "$expiry_date" ]; then
local expiry_epoch now_epoch days_left
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
now_epoch=$(date +%s)
if [ -n "$expiry_epoch" ]; then
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$days_left"
return 0
fi
fi
fi
return 1
}
# Get hosting status for all sites with DNS and cert health
method_get_hosting_status() {
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
# Get public IP once
local public_ip
public_ip=$(get_public_ipv4)
json_init
json_add_boolean "success" 1
json_add_string "public_ip" "$public_ip"
# HAProxy status
local haproxy_running="stopped"
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
haproxy_running="running"
fi
json_add_string "haproxy_status" "$haproxy_running"
json_add_array "sites"
config_load "$UCI_CONFIG"
config_foreach _add_site_health site "$public_ip"
json_close_array
json_dump
}
_add_site_health() {
local section="$1"
local public_ip="$2"
local name domain ssl enabled has_content port runtime
config_get name "$section" name ""
config_get domain "$section" domain ""
config_get ssl "$section" ssl "1"
config_get enabled "$section" enabled "1"
config_get port "$section" port ""
config_get runtime "$section" runtime ""
[ -z "$name" ] && return
# Check content
has_content="0"
if [ -d "$SITES_ROOT/$name" ] && [ -f "$SITES_ROOT/$name/index.html" ]; then
has_content="1"
fi
json_add_object
json_add_string "id" "$section"
json_add_string "name" "$name"
json_add_string "domain" "$domain"
json_add_boolean "enabled" "$enabled"
json_add_boolean "has_content" "$has_content"
[ -n "$port" ] && json_add_int "port" "$port"
json_add_string "runtime" "$runtime"
# DNS check
if [ -n "$domain" ]; then
local resolved_ip
resolved_ip=$(check_dns_resolution "$domain")
if [ -n "$resolved_ip" ]; then
json_add_string "dns_ip" "$resolved_ip"
# Check if resolves to public IP
case "$resolved_ip" in
10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*)
json_add_string "dns_status" "private"
;;
*)
if [ "$resolved_ip" = "$public_ip" ]; then
json_add_string "dns_status" "ok"
else
json_add_string "dns_status" "mismatch"
fi
;;
esac
else
json_add_string "dns_status" "failed"
fi
# Certificate check
if [ "$ssl" = "1" ]; then
local days_left
days_left=$(check_cert_expiry "$domain")
if [ -n "$days_left" ]; then
if [ "$days_left" -lt 0 ]; then
json_add_string "cert_status" "expired"
elif [ "$days_left" -lt 7 ]; then
json_add_string "cert_status" "critical"
elif [ "$days_left" -lt 30 ]; then
json_add_string "cert_status" "warning"
else
json_add_string "cert_status" "ok"
fi
json_add_int "cert_days" "$days_left"
else
json_add_string "cert_status" "missing"
fi
else
json_add_string "cert_status" "none"
fi
else
json_add_string "dns_status" "none"
json_add_string "cert_status" "none"
fi
# Publish status
local publish_status="draft"
if [ "$enabled" = "1" ] && [ "$has_content" = "1" ]; then
publish_status="published"
elif [ "$enabled" = "1" ]; then
publish_status="pending"
fi
json_add_string "publish_status" "$publish_status"
# URL
local protocol="http"
[ "$ssl" = "1" ] && protocol="https"
json_add_string "url" "${protocol}://${domain}"
json_close_object
}
# Check health for single site
method_check_site_health() {
local id
read -r input
json_load "$input"
json_get_var id id
if [ -z "$id" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Missing site id"
json_dump
return
fi
local name domain ssl
name=$(get_uci "$id" name "")
domain=$(get_uci "$id" domain "")
ssl=$(get_uci "$id" ssl "1")
if [ -z "$name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Site not found"
json_dump
return
fi
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
# Get public IP
local public_ip
public_ip=$(get_public_ipv4)
json_init
json_add_boolean "success" 1
json_add_string "id" "$id"
json_add_string "name" "$name"
json_add_string "domain" "$domain"
json_add_string "public_ip" "$public_ip"
# DNS check
json_add_object "dns"
if [ -n "$domain" ]; then
local resolved_ip
resolved_ip=$(check_dns_resolution "$domain")
if [ -n "$resolved_ip" ]; then
json_add_string "resolved_ip" "$resolved_ip"
case "$resolved_ip" in
10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*)
json_add_string "status" "private"
json_add_string "message" "DNS points to private IP"
;;
*)
if [ "$resolved_ip" = "$public_ip" ]; then
json_add_string "status" "ok"
else
json_add_string "status" "mismatch"
json_add_string "expected" "$public_ip"
fi
;;
esac
else
json_add_string "status" "failed"
json_add_string "message" "DNS resolution failed"
fi
else
json_add_string "status" "none"
fi
json_close_object
# Certificate check
json_add_object "certificate"
if [ -n "$domain" ] && [ "$ssl" = "1" ]; then
local days_left
days_left=$(check_cert_expiry "$domain")
if [ -n "$days_left" ]; then
json_add_int "days_left" "$days_left"
if [ "$days_left" -lt 0 ]; then
json_add_string "status" "expired"
elif [ "$days_left" -lt 7 ]; then
json_add_string "status" "critical"
elif [ "$days_left" -lt 30 ]; then
json_add_string "status" "warning"
else
json_add_string "status" "ok"
fi
else
json_add_string "status" "missing"
fi
else
json_add_string "status" "none"
fi
json_close_object
# Content check
json_add_object "content"
if [ -d "$SITES_ROOT/$name" ]; then
json_add_boolean "exists" 1
local file_count
file_count=$(find "$SITES_ROOT/$name" -type f 2>/dev/null | wc -l)
json_add_int "file_count" "$file_count"
if [ -f "$SITES_ROOT/$name/index.html" ]; then
json_add_boolean "has_index" 1
else
json_add_boolean "has_index" 0
fi
else
json_add_boolean "exists" 0
fi
json_close_object
# HAProxy status
json_add_object "haproxy"
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
json_add_string "status" "running"
else
json_add_string "status" "stopped"
fi
json_close_object
json_dump
}
# Save global settings
method_save_settings() {
local enabled runtime nginx_container sites_root gitea_url
@ -798,25 +1094,29 @@ case "$1" in
"upload_file": { "id": "string", "filename": "string", "content": "string" },
"list_files": { "id": "string" },
"get_settings": {},
"save_settings": { "enabled": "boolean", "nginx_container": "string", "sites_root": "string" }
"save_settings": { "enabled": "boolean", "nginx_container": "string", "sites_root": "string" },
"get_hosting_status": {},
"check_site_health": { "id": "string" }
}
EOF
;;
call)
case "$2" in
status) method_status ;;
list_sites) method_list_sites ;;
get_site) method_get_site ;;
create_site) method_create_site ;;
update_site) method_update_site ;;
delete_site) method_delete_site ;;
sync_site) method_sync_site ;;
get_publish_info) method_get_publish_info ;;
upload_file) method_upload_file ;;
list_files) method_list_files ;;
get_settings) method_get_settings ;;
save_settings) method_save_settings ;;
*) echo '{"error": "unknown method"}' ;;
status) method_status ;;
list_sites) method_list_sites ;;
get_site) method_get_site ;;
create_site) method_create_site ;;
update_site) method_update_site ;;
delete_site) method_delete_site ;;
sync_site) method_sync_site ;;
get_publish_info) method_get_publish_info ;;
upload_file) method_upload_file ;;
list_files) method_list_files ;;
get_settings) method_get_settings ;;
save_settings) method_save_settings ;;
get_hosting_status) method_get_hosting_status ;;
check_site_health) method_check_site_health ;;
*) echo '{"error": "unknown method"}' ;;
esac
;;
esac

View File

@ -8,7 +8,9 @@
"list_sites",
"get_site",
"get_publish_info",
"get_settings"
"get_settings",
"get_hosting_status",
"check_site_health"
],
"file": ["read", "list", "stat"]
},

View File

@ -0,0 +1,377 @@
# 🔐 mitmproxy - HTTPS Interception Proxy
Interactive HTTPS proxy for debugging, testing, and security analysis with transparent mode support and web-based traffic inspection.
## ✨ Features
| Feature | Description |
|---------|-------------|
| 🔍 **Traffic Inspection** | View and analyze HTTP/HTTPS requests in real-time |
| 🖥️ **Web UI** | Built-in mitmweb interface for visual traffic analysis |
| 🎭 **Transparent Mode** | Intercept traffic automatically via nftables |
| 📜 **CA Certificate** | Generate and manage SSL interception certificates |
| 📊 **Statistics** | Track requests, unique hosts, and flow data |
| 🔄 **Request Replay** | Replay captured requests for testing |
| ⚙️ **Filtering** | Filter and track CDN, media, ads, and trackers |
| 🛡️ **Whitelist** | Bypass interception for specific IPs/domains |
## 🚀 Quick Start
### Proxy Modes
| Mode | Icon | Description | Use Case |
|------|------|-------------|----------|
| 🎯 **Regular** | Configure clients manually | Testing specific apps |
| 🎭 **Transparent** | Auto-intercept via firewall | Network-wide inspection |
| ⬆️ **Upstream** | Forward to another proxy | Proxy chaining |
| ⬇️ **Reverse** | Reverse proxy mode | Backend analysis |
### Enable Transparent Mode
1. Go to **Security → mitmproxy → Settings**
2. Set **Proxy Mode** to `Transparent`
3. Enable **Transparent Firewall**
4. Click **Save & Apply**
### Install CA Certificate
For HTTPS interception, install the mitmproxy CA on client devices:
1. Configure device to use proxy (or use transparent mode)
2. Navigate to `http://mitm.it` from the device
3. Download and install the certificate for your OS
4. Trust the certificate in system settings
## 📊 Dashboard
```
┌──────────────────────────────────────────────────────────────┐
│ 🔐 mitmproxy 🟢 Running │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────┐ │
│ │ 📊 12.5K │ │ 🌐 245 │ │ 💾 45 MB │ │ 🔌 8080│ │
│ │ Requests │ │ Hosts │ │ Flow Data │ │ Port │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────┘ │
│ │
│ 🌐 Top Hosts 🔒 CA Certificate │
│ ┌──────────────────────────────┐ ┌─────────────────────────┐│
│ │ 🔗 api.example.com 1,234 │ │ 📜 mitmproxy CA ││
│ │ 🔗 cdn.cloudflare.com 890 │ │ ✅ Certificate installed ││
│ │ 🔗 www.google.com 567 │ │ Expires: 2026-01-28 ││
│ │ 🔗 analytics.google.com 432 │ │ [⬇ Download] ││
│ └──────────────────────────────┘ └─────────────────────────┘│
│ │
└──────────────────────────────────────────────────────────────┘
```
## 🔍 Request Capture
### Live Request Viewer
The Requests tab shows captured HTTP traffic in real-time:
```
┌──────────────────────────────────────────────────────────────┐
│ 🔍 Captured Requests ⏸ Pause │
├──────────────────────────────────────────────────────────────┤
│ │
│ [GET] api.example.com/users 200 application/json │
│ [POST] auth.example.com/login 201 application/json │
│ [GET] cdn.cloudflare.com/script.js 200 text/javascript │
│ [GET] www.google.com/search 200 text/html │
│ [PUT] api.example.com/user/123 204 - │
│ │
└──────────────────────────────────────────────────────────────┘
```
### View Request Details
Click on any request to see:
- Full request headers
- Response headers
- Cookies
- Request/response body (if captured)
## 🎭 Transparent Mode
### Architecture
```
Client Device SecuBox Router
┌──────────────┐ ┌────────────────────────┐
│ │ │ │
│ Browser │◀── HTTP/S ──▶│ nftables REDIRECT │
│ │ │ │ │
└──────────────┘ │ ▼ │
│ ┌──────────────────┐ │
│ │ mitmproxy │ │
│ │ (port 8080) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ Internet │
└────────────────────────┘
```
### Firewall Setup
When transparent mode is enabled, mitmproxy automatically creates nftables rules:
```bash
# HTTP redirect (port 80 → 8080)
nft add rule inet fw4 prerouting tcp dport 80 redirect to :8080
# HTTPS redirect (port 443 → 8080)
nft add rule inet fw4 prerouting tcp dport 443 redirect to :8080
```
## ⚙️ Configuration
### UCI Settings
```bash
# /etc/config/mitmproxy
config mitmproxy 'main'
option enabled '1'
option mode 'transparent' # regular | transparent | upstream | reverse
option proxy_port '8080'
option web_host '0.0.0.0'
option web_port '8081'
option data_path '/srv/mitmproxy'
option memory_limit '256M'
option ssl_insecure '0' # Accept invalid upstream certs
option anticache '0' # Strip cache headers
option anticomp '0' # Disable compression
option flow_detail '1' # Log detail level (0-4)
config transparent 'transparent'
option enabled '1'
option interface 'br-lan'
option redirect_http '1'
option redirect_https '1'
option http_port '80'
option https_port '443'
config whitelist 'whitelist'
option enabled '1'
list bypass_ip '192.168.255.0/24'
list bypass_domain 'banking.com'
config filtering 'filtering'
option enabled '0'
option log_requests '1'
option filter_cdn '0'
option filter_media '0'
option block_ads '0'
config capture 'capture'
option save_flows '0'
option capture_request_headers '1'
option capture_response_headers '1'
option capture_request_body '0'
option capture_response_body '0'
```
## 📡 RPCD API
### Service Control
| Method | Description |
|--------|-------------|
| `get_status` | Get service status |
| `service_start` | Start mitmproxy |
| `service_stop` | Stop mitmproxy |
| `service_restart` | Restart service |
| `install` | Install mitmproxy container |
### Configuration
| Method | Description |
|--------|-------------|
| `get_config` | Get main configuration |
| `get_all_config` | Get all configuration sections |
| `get_transparent_config` | Get transparent mode settings |
| `get_whitelist_config` | Get whitelist settings |
| `get_filtering_config` | Get filtering settings |
| `set_config` | Set configuration value |
### Statistics & Data
| Method | Description |
|--------|-------------|
| `get_stats` | Get traffic statistics |
| `get_requests` | Get captured requests |
| `get_top_hosts` | Get most requested hosts |
| `get_ca_info` | Get CA certificate info |
| `clear_data` | Clear captured data |
### Firewall
| Method | Description |
|--------|-------------|
| `firewall_setup` | Setup transparent mode rules |
| `firewall_clear` | Remove firewall rules |
### Example Usage
```bash
# Get status
ubus call luci.mitmproxy get_status
# Response:
{
"enabled": true,
"running": true,
"installed": true,
"docker_available": true,
"web_port": 8081,
"proxy_port": 8080,
"listen_port": 8080,
"web_url": "http://192.168.255.1:8081"
}
# Get statistics
ubus call luci.mitmproxy get_stats
# Response:
{
"total_requests": 12500,
"unique_hosts": 245,
"flow_file_size": 47185920,
"cdn_requests": 3200,
"media_requests": 890,
"blocked_ads": 156
}
# Get top hosts
ubus call luci.mitmproxy get_top_hosts '{"limit":10}'
# Response:
{
"hosts": [
{ "host": "api.example.com", "count": 1234 },
{ "host": "cdn.cloudflare.com", "count": 890 }
]
}
```
## 🔒 CA Certificate
### Generate New Certificate
```bash
# Certificate is auto-generated on first start
# Located at: /srv/mitmproxy/certs/mitmproxy-ca-cert.pem
```
### Download Certificate
1. Access mitmweb UI at `http://192.168.255.1:8081`
2. Or navigate to `http://mitm.it` from a proxied device
3. Download certificate for your platform
### Certificate Locations
| Path | Description |
|------|-------------|
| `/srv/mitmproxy/certs/mitmproxy-ca.pem` | CA private key + certificate |
| `/srv/mitmproxy/certs/mitmproxy-ca-cert.pem` | CA certificate only |
| `/srv/mitmproxy/certs/mitmproxy-ca-cert.cer` | Certificate (DER format) |
## 🛡️ Filtering & Analytics
### CDN Tracking
Track traffic to major CDN providers:
- Cloudflare
- Akamai
- Fastly
- AWS CloudFront
- Google Cloud CDN
### Media Streaming Tracking
Track streaming services:
- YouTube
- Netflix
- Spotify
- Twitch
- Amazon Prime Video
### Ad Blocking
Block known advertising and tracking domains with the built-in filter addon.
## 📁 File Locations
| Path | Description |
|------|-------------|
| `/etc/config/mitmproxy` | UCI configuration |
| `/srv/mitmproxy/` | Data directory |
| `/srv/mitmproxy/certs/` | CA certificates |
| `/srv/mitmproxy/flows/` | Captured flow files |
| `/var/lib/lxc/mitmproxy/` | LXC container root |
| `/usr/libexec/rpcd/luci.mitmproxy` | RPCD backend |
## 🛠️ Troubleshooting
### Service Won't Start
```bash
# Check container status
lxc-info -n mitmproxy
# Check logs
logread | grep mitmproxy
# Verify Docker is available
docker ps
```
### No Traffic Being Captured
1. **Regular mode**: Verify client proxy settings point to `192.168.255.1:8080`
2. **Transparent mode**: Check firewall rules with `nft list ruleset | grep redirect`
3. Verify mitmproxy is listening: `netstat -tln | grep 8080`
### HTTPS Interception Not Working
1. Install CA certificate on client device
2. Trust the certificate in system settings
3. Some apps use certificate pinning and cannot be intercepted
### Web UI Not Accessible
```bash
# Check web port is listening
netstat -tln | grep 8081
# Verify from router
curl -I http://127.0.0.1:8081
# Check firewall allows access
uci show firewall | grep mitmproxy
```
### Memory Issues
```bash
# Increase memory limit
uci set mitmproxy.main.memory_limit='512M'
uci commit mitmproxy
/etc/init.d/mitmproxy restart
```
## 🔒 Security Notes
1. **Sensitive Tool** - mitmproxy can intercept all network traffic including passwords. Use responsibly.
2. **CA Certificate** - Protect the CA private key. Anyone with access can intercept traffic.
3. **Whitelist Banking** - Add banking and financial sites to the bypass list.
4. **Disable When Not Needed** - Turn off transparent mode when not actively debugging.
5. **Audit Trail** - All captured requests may contain sensitive data.
## 📜 License
MIT License - Copyright (C) 2025 CyberMind.fr

View File

@ -9,88 +9,367 @@ var callStart = rpc.declare({ object: 'luci.mitmproxy', method: 'start', expect:
var callStop = rpc.declare({ object: 'luci.mitmproxy', method: 'stop', expect: {} });
var callRestart = rpc.declare({ object: 'luci.mitmproxy', method: 'restart', expect: {} });
var css = '.mp-container{max-width:900px;margin:0 auto}.mp-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;background:linear-gradient(135deg,#f97316 0%,#ea580c 100%);border-radius:16px;color:#fff;margin-bottom:1.5rem}.mp-header h2{margin:0;font-size:1.5rem;display:flex;align-items:center;gap:.5rem}.mp-status{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:20px;font-size:.9rem}.mp-status.running{background:rgba(16,185,129,.2)}.mp-status.stopped{background:rgba(239,68,68,.2)}.mp-dot{width:10px;height:10px;border-radius:50%;animation:pulse 2s infinite}.mp-status.running .mp-dot{background:#10b981}.mp-status.stopped .mp-dot{background:#ef4444}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}.mp-card{background:#fff;border-radius:12px;padding:1.5rem;box-shadow:0 2px 8px rgba(0,0,0,.08);margin-bottom:1rem}.mp-card-title{font-size:1.1rem;font-weight:600;margin-bottom:1rem;display:flex;align-items:center;gap:.5rem}.mp-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem}.mp-info-item{padding:1rem;background:#f8f9fa;border-radius:8px}.mp-info-label{font-size:.8rem;color:#666;margin-bottom:.25rem}.mp-info-value{font-size:1rem;font-weight:500}.mp-actions{display:flex;gap:.75rem;flex-wrap:wrap}.mp-btn{padding:.6rem 1.2rem;border-radius:8px;border:none;cursor:pointer;font-weight:500;transition:all .2s}.mp-btn-primary{background:linear-gradient(135deg,#f97316,#ea580c);color:#fff}.mp-btn-success{background:#10b981;color:#fff}.mp-btn-danger{background:#ef4444;color:#fff}.mp-btn:disabled{opacity:.5;cursor:not-allowed}.mp-not-installed{text-align:center;padding:3rem}.mp-features{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem;margin:1.5rem 0}.mp-feature{padding:.75rem;background:#fff7ed;border-radius:8px;font-size:.9rem}.mp-warning{background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:1rem;margin-top:1rem;font-size:.9rem;color:#92400e}';
var css = [
':root { --mp-primary: #e74c3c; --mp-primary-light: #ec7063; --mp-secondary: #3498db; --mp-success: #27ae60; --mp-warning: #f39c12; --mp-danger: #c0392b; --mp-bg: #0d0d12; --mp-card: #141419; --mp-border: rgba(255,255,255,0.08); --mp-text: #e0e0e8; --mp-muted: #8a8a9a; }',
'.mp-overview { max-width: 1000px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif; color: var(--mp-text); }',
/* Header */
'.mp-header { display: flex; justify-content: space-between; align-items: center; padding: 24px; background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); border-radius: 16px; color: #fff; margin-bottom: 24px; }',
'.mp-header-left { display: flex; align-items: center; gap: 16px; }',
'.mp-logo { font-size: 48px; }',
'.mp-title { font-size: 28px; font-weight: 700; margin: 0; }',
'.mp-subtitle { font-size: 14px; opacity: 0.9; margin-top: 4px; }',
'.mp-status { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 500; }',
'.mp-status.running { background: rgba(39,174,96,0.3); }',
'.mp-status.stopped { background: rgba(239,68,68,0.3); }',
'.mp-dot { width: 10px; height: 10px; border-radius: 50%; animation: pulse 2s infinite; }',
'.mp-status.running .mp-dot { background: #27ae60; }',
'.mp-status.stopped .mp-dot { background: #ef4444; }',
'@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }',
/* Welcome Banner - shown when not installed/running */
'.mp-welcome { text-align: center; padding: 60px 40px; background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; margin-bottom: 24px; }',
'.mp-welcome-icon { font-size: 80px; margin-bottom: 20px; }',
'.mp-welcome h2 { font-size: 28px; margin: 0 0 12px 0; color: #fff; }',
'.mp-welcome p { font-size: 16px; color: var(--mp-muted); margin: 0 0 30px 0; max-width: 600px; margin-left: auto; margin-right: auto; }',
'.mp-welcome-note { background: rgba(231,76,60,0.1); border: 1px solid rgba(231,76,60,0.3); border-radius: 12px; padding: 16px; margin-top: 24px; font-size: 14px; color: #ec7063; }',
/* Mode Cards */
'.mp-modes { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }',
'.mp-mode-card { background: var(--mp-card); border: 2px solid var(--mp-border); border-radius: 16px; padding: 24px; text-align: center; cursor: pointer; transition: all 0.3s; }',
'.mp-mode-card:hover { border-color: var(--mp-primary); transform: translateY(-4px); box-shadow: 0 8px 32px rgba(231,76,60,0.2); }',
'.mp-mode-card.recommended { border-color: var(--mp-primary); background: linear-gradient(180deg, rgba(231,76,60,0.1) 0%, transparent 100%); }',
'.mp-mode-icon { font-size: 48px; margin-bottom: 16px; }',
'.mp-mode-title { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 8px; }',
'.mp-mode-desc { font-size: 13px; color: var(--mp-muted); line-height: 1.5; }',
'.mp-mode-badge { display: inline-block; background: var(--mp-primary); color: #fff; font-size: 11px; padding: 4px 10px; border-radius: 12px; margin-top: 12px; text-transform: uppercase; font-weight: 600; }',
/* Feature Grid */
'.mp-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }',
'.mp-feature { display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 12px; }',
'.mp-feature-icon { font-size: 24px; }',
'.mp-feature-text { font-size: 14px; color: var(--mp-text); }',
/* Quick Actions */
'.mp-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-bottom: 24px; }',
'.mp-btn { display: inline-flex; align-items: center; gap: 8px; padding: 14px 28px; border-radius: 12px; border: none; cursor: pointer; font-size: 15px; font-weight: 600; transition: all 0.2s; text-decoration: none; }',
'.mp-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.3); }',
'.mp-btn:disabled { opacity: 0.5; cursor: not-allowed; }',
'.mp-btn-primary { background: linear-gradient(135deg, #e74c3c, #c0392b); color: #fff; }',
'.mp-btn-success { background: linear-gradient(135deg, #27ae60, #1e8449); color: #fff; }',
'.mp-btn-danger { background: linear-gradient(135deg, #e74c3c, #c0392b); color: #fff; }',
'.mp-btn-secondary { background: rgba(255,255,255,0.1); color: var(--mp-text); border: 1px solid var(--mp-border); }',
/* Quick Start Card */
'.mp-quickstart { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; padding: 24px; margin-bottom: 24px; }',
'.mp-quickstart-header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }',
'.mp-quickstart-icon { font-size: 28px; }',
'.mp-quickstart-title { font-size: 20px; font-weight: 600; color: #fff; }',
'.mp-quickstart-steps { display: flex; flex-direction: column; gap: 16px; }',
'.mp-step { display: flex; gap: 16px; align-items: flex-start; }',
'.mp-step-num { width: 32px; height: 32px; background: var(--mp-primary); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0; }',
'.mp-step-content h4 { margin: 0 0 4px 0; font-size: 15px; color: #fff; }',
'.mp-step-content p { margin: 0; font-size: 13px; color: var(--mp-muted); }',
/* Info Cards */
'.mp-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 24px; }',
'.mp-info-card { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 12px; padding: 20px; }',
'.mp-info-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }',
'.mp-info-icon { font-size: 24px; }',
'.mp-info-title { font-size: 16px; font-weight: 600; color: #fff; }',
'.mp-info-value { font-size: 24px; font-weight: 700; color: var(--mp-primary); }',
'.mp-info-label { font-size: 13px; color: var(--mp-muted); }',
/* How It Works */
'.mp-howto { background: var(--mp-card); border: 1px solid var(--mp-border); border-radius: 16px; padding: 24px; }',
'.mp-howto-header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }',
'.mp-howto-icon { font-size: 28px; }',
'.mp-howto-title { font-size: 20px; font-weight: 600; color: #fff; }',
'.mp-howto-diagram { background: rgba(0,0,0,0.3); border-radius: 12px; padding: 20px; font-family: monospace; font-size: 13px; line-height: 1.6; overflow-x: auto; }',
'.mp-howto-diagram pre { margin: 0; color: var(--mp-text); }'
].join('\n');
return view.extend({
load: function() { return callStatus(); },
handleInstall: function() {
ui.showModal(_('Installing mitmproxy'), [E('p', { 'class': 'spinning' }, _('Installing...'))]);
ui.showModal(_('Installing mitmproxy'), [
E('p', { 'class': 'spinning' }, _('Downloading and setting up mitmproxy container...')),
E('p', { 'style': 'color: #888; font-size: 13px;' }, _('This may take a few minutes on first install.'))
]);
callInstall().then(function(r) {
ui.hideModal();
ui.addNotification(null, E('p', r.message || _('Installation started')));
ui.addNotification(null, E('p', r.message || _('Installation started. Please wait and refresh the page.')));
}).catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
handleStart: function() {
ui.showModal(_('Starting...'), [E('p', { 'class': 'spinning' }, _('Starting...'))]);
ui.showModal(_('Starting mitmproxy'), [E('p', { 'class': 'spinning' }, _('Starting proxy service...'))]);
callStart().then(function() { ui.hideModal(); location.reload(); })
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
handleStop: function() {
ui.showModal(_('Stopping...'), [E('p', { 'class': 'spinning' }, _('Stopping...'))]);
ui.showModal(_('Stopping mitmproxy'), [E('p', { 'class': 'spinning' }, _('Stopping proxy service...'))]);
callStop().then(function() { ui.hideModal(); location.reload(); })
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
render: function(status) {
if (!document.getElementById('mp-styles')) {
var s = document.createElement('style'); s.id = 'mp-styles'; s.textContent = css; document.head.appendChild(s);
if (!document.getElementById('mp-overview-styles')) {
var s = document.createElement('style');
s.id = 'mp-overview-styles';
s.textContent = css;
document.head.appendChild(s);
}
if (!status.installed || !status.docker_available) {
return E('div', { 'class': 'mp-container' }, [
var isInstalled = status.installed && status.docker_available;
var isRunning = status.running;
// Not installed - show welcome wizard
if (!isInstalled) {
return E('div', { 'class': 'mp-overview' }, [
// Header
E('div', { 'class': 'mp-header' }, [
E('h2', {}, ['\uD83D\uDD0D ', _('mitmproxy')]),
E('div', { 'class': 'mp-status stopped' }, [E('span', { 'class': 'mp-dot' }), _('Not Installed')])
E('div', { 'class': 'mp-header-left' }, [
E('div', { 'class': 'mp-logo' }, '🔐'),
E('div', {}, [
E('h1', { 'class': 'mp-title' }, 'mitmproxy'),
E('div', { 'class': 'mp-subtitle' }, _('HTTPS Interception Proxy'))
])
]),
E('div', { 'class': 'mp-status stopped' }, [
E('span', { 'class': 'mp-dot' }),
_('Not Installed')
])
]),
E('div', { 'class': 'mp-card' }, [
E('div', { 'class': 'mp-not-installed' }, [
E('div', { 'style': 'font-size:4rem;margin-bottom:1rem' }, '\uD83D\uDD0D'),
E('h3', {}, _('mitmproxy')),
E('p', {}, _('Interactive HTTPS proxy for debugging, testing, and security analysis.')),
E('div', { 'class': 'mp-features' }, [
E('div', { 'class': 'mp-feature' }, '\uD83D\uDCCA Web UI'),
E('div', { 'class': 'mp-feature' }, '\uD83D\uDD12 HTTPS'),
E('div', { 'class': 'mp-feature' }, '\uD83D\uDCDD Logging'),
E('div', { 'class': 'mp-feature' }, '\uD83D\uDD04 Replay'),
E('div', { 'class': 'mp-feature' }, '\u2699 Scripting'),
E('div', { 'class': 'mp-feature' }, '\uD83D\uDCE6 Export')
// Welcome Banner
E('div', { 'class': 'mp-welcome' }, [
E('div', { 'class': 'mp-welcome-icon' }, '🔍'),
E('h2', {}, _('Intercept & Analyze Network Traffic')),
E('p', {}, _('mitmproxy is a powerful interactive HTTPS proxy that lets you inspect, modify, and replay HTTP/HTTPS traffic. Perfect for debugging APIs, testing applications, and security analysis.')),
// Features Grid
E('div', { 'class': 'mp-features', 'style': 'margin-bottom: 30px;' }, [
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '📊'),
E('span', { 'class': 'mp-feature-text' }, _('Real-time inspection'))
]),
E('div', { 'class': 'mp-warning' }, _('Note: This is a security analysis tool. Only use for legitimate debugging and testing purposes.')),
!status.docker_available ? E('div', { 'style': 'color:#ef4444;margin:1rem 0' }, _('Docker required')) : '',
E('button', { 'class': 'mp-btn mp-btn-primary', 'style': 'margin-top:1rem', 'click': ui.createHandlerFn(this, 'handleInstall'), 'disabled': !status.docker_available }, _('Install mitmproxy'))
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '🔒'),
E('span', { 'class': 'mp-feature-text' }, _('HTTPS decryption'))
]),
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '🖥️'),
E('span', { 'class': 'mp-feature-text' }, _('Web-based UI'))
]),
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '🎭'),
E('span', { 'class': 'mp-feature-text' }, _('Transparent mode'))
]),
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '🔄'),
E('span', { 'class': 'mp-feature-text' }, _('Request replay'))
]),
E('div', { 'class': 'mp-feature' }, [
E('span', { 'class': 'mp-feature-icon' }, '📝'),
E('span', { 'class': 'mp-feature-text' }, _('Flow logging'))
])
]),
!status.docker_available ?
E('div', { 'style': 'color: #ef4444; margin-bottom: 20px;' }, [
E('span', { 'style': 'font-size: 24px;' }, '⚠️ '),
_('Docker is required but not available')
]) : null,
E('button', {
'class': 'mp-btn mp-btn-primary',
'click': ui.createHandlerFn(this, 'handleInstall'),
'disabled': !status.docker_available
}, ['📦 ', _('Install mitmproxy')]),
E('div', { 'class': 'mp-welcome-note' }, [
'⚠️ ',
_('Security Note: mitmproxy is a powerful security analysis tool. Only use for legitimate debugging, testing, and security research purposes.')
])
]),
// Proxy Modes
E('h3', { 'style': 'margin: 0 0 16px 0; font-size: 18px; color: #fff;' }, '🎯 ' + _('Proxy Modes')),
E('div', { 'class': 'mp-modes' }, [
E('div', { 'class': 'mp-mode-card' }, [
E('div', { 'class': 'mp-mode-icon' }, '🎯'),
E('div', { 'class': 'mp-mode-title' }, _('Regular Proxy')),
E('div', { 'class': 'mp-mode-desc' }, _('Configure clients to use the proxy manually. Best for testing specific applications.'))
]),
E('div', { 'class': 'mp-mode-card recommended' }, [
E('div', { 'class': 'mp-mode-icon' }, '🎭'),
E('div', { 'class': 'mp-mode-title' }, _('Transparent Mode')),
E('div', { 'class': 'mp-mode-desc' }, _('Intercept all network traffic automatically via firewall rules.')),
E('span', { 'class': 'mp-mode-badge' }, _('Recommended'))
]),
E('div', { 'class': 'mp-mode-card' }, [
E('div', { 'class': 'mp-mode-icon' }, '⬆️'),
E('div', { 'class': 'mp-mode-title' }, _('Upstream Proxy')),
E('div', { 'class': 'mp-mode-desc' }, _('Forward traffic to another proxy server for proxy chaining.'))
]),
E('div', { 'class': 'mp-mode-card' }, [
E('div', { 'class': 'mp-mode-icon' }, '⬇️'),
E('div', { 'class': 'mp-mode-title' }, _('Reverse Proxy')),
E('div', { 'class': 'mp-mode-desc' }, _('Act as a reverse proxy to inspect backend server traffic.'))
])
])
]);
}
return E('div', { 'class': 'mp-container' }, [
// Installed - show dashboard overview
return E('div', { 'class': 'mp-overview' }, [
// Header
E('div', { 'class': 'mp-header' }, [
E('h2', {}, ['\uD83D\uDD0D ', _('mitmproxy')]),
E('div', { 'class': 'mp-status ' + (status.running ? 'running' : 'stopped') }, [
E('div', { 'class': 'mp-header-left' }, [
E('div', { 'class': 'mp-logo' }, '🔐'),
E('div', {}, [
E('h1', { 'class': 'mp-title' }, 'mitmproxy'),
E('div', { 'class': 'mp-subtitle' }, _('HTTPS Interception Proxy'))
])
]),
E('div', { 'class': 'mp-status ' + (isRunning ? 'running' : 'stopped') }, [
E('span', { 'class': 'mp-dot' }),
status.running ? _('Running') : _('Stopped')
isRunning ? _('Running') : _('Stopped')
])
]),
E('div', { 'class': 'mp-card' }, [
E('div', { 'class': 'mp-card-title' }, ['\u2139\uFE0F ', _('Configuration')]),
E('div', { 'class': 'mp-info-grid' }, [
E('div', { 'class': 'mp-info-item' }, [E('div', { 'class': 'mp-info-label' }, _('Proxy Port')), E('div', { 'class': 'mp-info-value' }, String(status.proxy_port))]),
E('div', { 'class': 'mp-info-item' }, [E('div', { 'class': 'mp-info-label' }, _('Web UI Port')), E('div', { 'class': 'mp-info-value' }, String(status.web_port))]),
E('div', { 'class': 'mp-info-item' }, [E('div', { 'class': 'mp-info-label' }, _('Web UI')), E('div', { 'class': 'mp-info-value' }, [E('a', { 'href': 'http://' + window.location.hostname + ':' + status.web_port, 'target': '_blank' }, _('Open UI'))])])
// Quick Actions
E('div', { 'class': 'mp-actions' }, [
isRunning ? E('button', {
'class': 'mp-btn mp-btn-danger',
'click': ui.createHandlerFn(this, 'handleStop')
}, ['⏹ ', _('Stop Proxy')]) :
E('button', {
'class': 'mp-btn mp-btn-success',
'click': ui.createHandlerFn(this, 'handleStart')
}, ['▶️ ', _('Start Proxy')]),
E('a', {
'class': 'mp-btn mp-btn-primary',
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', 'dashboard')
}, ['📊 ', _('Dashboard')]),
E('a', {
'class': 'mp-btn mp-btn-secondary',
'href': 'http://' + window.location.hostname + ':' + (status.web_port || 8081),
'target': '_blank'
}, ['🖥️ ', _('Web UI')]),
E('a', {
'class': 'mp-btn mp-btn-secondary',
'href': L.url('admin', 'secubox', 'security', 'mitmproxy', 'settings')
}, ['⚙️ ', _('Settings')])
]),
// Info Cards
E('div', { 'class': 'mp-info-grid' }, [
E('div', { 'class': 'mp-info-card' }, [
E('div', { 'class': 'mp-info-header' }, [
E('span', { 'class': 'mp-info-icon' }, '🔌'),
E('span', { 'class': 'mp-info-title' }, _('Proxy Port'))
]),
E('div', { 'class': 'mp-info-value' }, String(status.proxy_port || 8080)),
E('div', { 'class': 'mp-info-label' }, _('HTTP/HTTPS interception'))
]),
E('div', { 'class': 'mp-info-card' }, [
E('div', { 'class': 'mp-info-header' }, [
E('span', { 'class': 'mp-info-icon' }, '🖥️'),
E('span', { 'class': 'mp-info-title' }, _('Web UI Port'))
]),
E('div', { 'class': 'mp-info-value' }, String(status.web_port || 8081)),
E('div', { 'class': 'mp-info-label' }, _('mitmweb interface'))
]),
E('div', { 'class': 'mp-info-card' }, [
E('div', { 'class': 'mp-info-header' }, [
E('span', { 'class': 'mp-info-icon' }, '💾'),
E('span', { 'class': 'mp-info-title' }, _('Data Path'))
]),
E('div', { 'class': 'mp-info-value', 'style': 'font-size: 14px; word-break: break-all;' }, status.data_path || '/srv/mitmproxy'),
E('div', { 'class': 'mp-info-label' }, _('Certificates & flows'))
])
]),
E('div', { 'class': 'mp-card' }, [
E('div', { 'class': 'mp-card-title' }, ['\u26A1 ', _('Actions')]),
E('div', { 'class': 'mp-actions' }, [
E('button', { 'class': 'mp-btn mp-btn-success', 'click': ui.createHandlerFn(this, 'handleStart'), 'disabled': status.running }, _('Start')),
E('button', { 'class': 'mp-btn mp-btn-danger', 'click': ui.createHandlerFn(this, 'handleStop'), 'disabled': !status.running }, _('Stop'))
// Quick Start Guide
E('div', { 'class': 'mp-quickstart' }, [
E('div', { 'class': 'mp-quickstart-header' }, [
E('span', { 'class': 'mp-quickstart-icon' }, '🚀'),
E('span', { 'class': 'mp-quickstart-title' }, _('Quick Start Guide'))
]),
E('div', { 'class': 'mp-quickstart-steps' }, [
E('div', { 'class': 'mp-step' }, [
E('div', { 'class': 'mp-step-num' }, '1'),
E('div', { 'class': 'mp-step-content' }, [
E('h4', {}, _('Start the Proxy')),
E('p', {}, _('Click the Start button above to begin intercepting traffic.'))
])
]),
E('div', { 'class': 'mp-step' }, [
E('div', { 'class': 'mp-step-num' }, '2'),
E('div', { 'class': 'mp-step-content' }, [
E('h4', {}, _('Install CA Certificate')),
E('p', {}, [
_('Navigate to '),
E('code', { 'style': 'background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px;' }, 'http://mitm.it'),
_(' from a proxied device to download and install the CA certificate.')
])
])
]),
E('div', { 'class': 'mp-step' }, [
E('div', { 'class': 'mp-step-num' }, '3'),
E('div', { 'class': 'mp-step-content' }, [
E('h4', {}, _('Configure Clients')),
E('p', {}, [
_('Set proxy to '),
E('code', { 'style': 'background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px;' }, window.location.hostname + ':' + (status.proxy_port || 8080)),
_(' or enable transparent mode in Settings.')
])
])
]),
E('div', { 'class': 'mp-step' }, [
E('div', { 'class': 'mp-step-num' }, '4'),
E('div', { 'class': 'mp-step-content' }, [
E('h4', {}, _('View Traffic')),
E('p', {}, _('Open the Dashboard or Web UI to see captured requests in real-time.'))
])
])
])
]),
// How It Works Diagram
E('div', { 'class': 'mp-howto' }, [
E('div', { 'class': 'mp-howto-header' }, [
E('span', { 'class': 'mp-howto-icon' }, '📖'),
E('span', { 'class': 'mp-howto-title' }, _('How mitmproxy Works'))
]),
E('div', { 'class': 'mp-howto-diagram' }, [
E('pre', {}, [
' Client Device SecuBox Router Internet\n',
' ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐\n',
' │ Browser │─────▶│ mitmproxy │─────▶│ Server │\n',
' │ │◀─────│ │◀─────│ │\n',
' └─────────────┘ │ 🔍 Inspect │ └─────────────┘\n',
' │ ✏️ Modify │\n',
' │ 📊 Log │\n',
' │ 🔄 Replay │\n',
' └──────────────────┘\n',
'\n',
' Port ' + (status.proxy_port || 8080) + ': HTTP/HTTPS interception\n',
' Port ' + (status.web_port || 8081) + ': Web UI (mitmweb)'
].join(''))
])
])
]);
},
handleSaveApply: null, handleSave: null, handleReset: null
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -976,26 +976,62 @@ get_reverse_dns() {
# Helper: Check certificate expiry
check_cert_expiry() {
local domain="$1"
local cert_file="/srv/haproxy/certs/${domain}.pem"
local cert_file=""
if [ ! -f "$cert_file" ]; then
# Try the ACME path
# Try multiple possible certificate locations
# 1. HAProxy certs directory (combined pem files)
if [ -f "/srv/haproxy/certs/${domain}.pem" ]; then
cert_file="/srv/haproxy/certs/${domain}.pem"
# 2. ACME standard path
elif [ -f "/etc/acme/${domain}/${domain}.cer" ]; then
cert_file="/etc/acme/${domain}/${domain}.cer"
# 3. ACME fullchain (some setups use this)
elif [ -f "/etc/acme/${domain}/fullchain.cer" ]; then
cert_file="/etc/acme/${domain}/fullchain.cer"
# 4. ACME with _ecc suffix (ECC certs)
elif [ -f "/etc/acme/${domain}_ecc/${domain}.cer" ]; then
cert_file="/etc/acme/${domain}_ecc/${domain}.cer"
[ ! -f "$cert_file" ] && cert_file="/etc/acme/${domain}/${domain}.cer"
# 5. Let's Encrypt standard path
elif [ -f "/etc/letsencrypt/live/${domain}/cert.pem" ]; then
cert_file="/etc/letsencrypt/live/${domain}/cert.pem"
fi
if [ -f "$cert_file" ]; then
if [ -n "$cert_file" ] && [ -f "$cert_file" ]; then
# Get expiry date using openssl
local expiry_date
expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" 2>/dev/null | cut -d= -f2)
if [ -n "$expiry_date" ]; then
# Convert to epoch
local expiry_epoch
# Convert to epoch - try multiple date formats for compatibility
local expiry_epoch now_epoch days_left
# BusyBox date may not support -d with GMT format
# Try direct parsing first
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
local now_epoch
# If that fails, try converting the format
if [ -z "$expiry_epoch" ]; then
# Parse "Apr 27 04:05:21 2026 GMT" format manually
local month day time year
month=$(echo "$expiry_date" | awk '{print $1}')
day=$(echo "$expiry_date" | awk '{print $2}')
time=$(echo "$expiry_date" | awk '{print $3}')
year=$(echo "$expiry_date" | awk '{print $4}')
# Convert month name to number
case "$month" in
Jan) month="01" ;; Feb) month="02" ;; Mar) month="03" ;;
Apr) month="04" ;; May) month="05" ;; Jun) month="06" ;;
Jul) month="07" ;; Aug) month="08" ;; Sep) month="09" ;;
Oct) month="10" ;; Nov) month="11" ;; Dec) month="12" ;;
esac
# Try with reformatted date
expiry_epoch=$(date -d "${year}-${month}-${day}" +%s 2>/dev/null)
fi
now_epoch=$(date +%s)
local days_left
if [ -n "$expiry_epoch" ]; then
if [ -n "$expiry_epoch" ] && [ -n "$now_epoch" ]; then
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$days_left"
return 0

View File

@ -0,0 +1,281 @@
# 🧅 Tor Shield - Anonymous Routing Made Simple
Network-wide privacy protection through the Tor network with one-click activation.
## ✨ Features
### 🛡️ Protection Modes
| Mode | Description | Use Case |
|------|-------------|----------|
| 🌐 **Transparent Proxy** | All network traffic routed through Tor automatically | Full network anonymity |
| 🎯 **SOCKS Proxy** | Apps connect via SOCKS5 (127.0.0.1:9050) | Selective app protection |
| 🔓 **Bridge Mode** | Uses obfs4/meek bridges to bypass censorship | Restrictive networks |
### 🚀 Quick Start Presets
| Preset | Icon | Configuration |
|--------|------|---------------|
| **Full Anonymity** | 🛡️ | Transparent + DNS over Tor + Kill Switch |
| **Selective Apps** | 🎯 | SOCKS only, no kill switch |
| **Bypass Censorship** | 🔓 | Bridges enabled + obfs4 |
### 🔒 Security Features
- **🔐 Kill Switch** - Blocks all traffic if Tor disconnects
- **🌍 DNS over Tor** - Prevents DNS leaks
- **🔄 New Identity** - Request fresh circuits instantly
- **🔍 Leak Test** - Verify your protection is working
- **🧅 Hidden Services** - Host .onion sites
## 📊 Dashboard
The dashboard provides real-time monitoring:
```
┌──────────────────────────────────────────────────┐
│ 🧅 Tor Shield 🟢 Protected │
├──────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ Your Protection Status │
│ │ 🧅 │ ───────────────────────── │
│ │ Toggle │ Real IP: 192.168.x.x │
│ │ │ Tor Exit: 185.220.x.x 🇩🇪 │
│ └────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 🛡️ Full │ 🎯 Selective │ 🔓 Censored │ │
│ │ Anonymity │ Apps │ Bypass │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 🔄 Circuits: 5 │ 📊 45 KB/s │ ⏱ 2h 15m │
│ 📥 125 MB │ 📤 45 MB │ │
│ │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │🟢Service│🟢Boot │🟢DNS │🟢Kill │ │
│ │ Running │ 100% │Protected│ Active │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
└──────────────────────────────────────────────────┘
```
## 🧅 Hidden Services
Host your services on the Tor network with .onion addresses:
```bash
# Via LuCI
Services → Tor Shield → Hidden Services → Add
# Via CLI
ubus call luci.tor-shield add_hidden_service '{"name":"mysite","local_port":80,"virtual_port":80}'
# Get onion address
cat /var/lib/tor/hidden_service_mysite/hostname
```
### Example Hidden Services
| Service | Local Port | Onion Port | Use Case |
|---------|-----------|------------|----------|
| Web Server | 80 | 80 | Anonymous website |
| SSH | 22 | 22 | Secure remote access |
| API | 8080 | 80 | Anonymous API endpoint |
## 🌉 Bridges
Bypass network censorship using Tor bridges:
### Bridge Types
| Type | Description | When to Use |
|------|-------------|-------------|
| **obfs4** | Obfuscated protocol | Most censored networks |
| **meek-azure** | Domain fronting via Azure | Highly restrictive networks |
| **snowflake** | WebRTC-based | Dynamic bridge discovery |
### Auto-Bridge Detection
```bash
# Enable automatic bridge selection
uci set tor-shield.main.auto_bridges=1
uci commit tor-shield
/etc/init.d/tor-shield restart
```
## 🔧 Configuration
### UCI Settings
```bash
# /etc/config/tor-shield
config tor-shield 'main'
option enabled '1'
option mode 'transparent' # transparent | socks
option dns_over_tor '1' # Route DNS through Tor
option kill_switch '1' # Block traffic if Tor fails
option auto_bridges '0' # Auto-detect censorship
config socks 'socks'
option port '9050'
option address '127.0.0.1'
config trans 'trans'
option port '9040'
option dns_port '9053'
list excluded_ips '192.168.255.0/24' # LAN bypass
config bridges 'bridges'
option enabled '0'
option type 'obfs4'
config security 'security'
option exit_nodes '' # Country codes: {us},{de}
option exclude_exit_nodes '' # Avoid: {ru},{cn}
option strict_nodes '0'
config hidden_service 'hs_mysite'
option enabled '1'
option name 'mysite'
option local_port '80'
option virtual_port '80'
```
## 📡 RPCD API
### Status & Control
```bash
# Get status
ubus call luci.tor-shield status
# Enable with preset
ubus call luci.tor-shield enable '{"preset":"anonymous"}'
# Disable
ubus call luci.tor-shield disable
# Restart
ubus call luci.tor-shield restart
# Request new identity
ubus call luci.tor-shield new_identity
# Check for leaks
ubus call luci.tor-shield check_leaks
```
### Circuit Management
```bash
# Get active circuits
ubus call luci.tor-shield circuits
# Response:
{
"circuits": [{
"id": "123",
"status": "BUILT",
"path": "$A~Guard,$B~Middle,$C~Exit",
"purpose": "GENERAL",
"nodes": [
{"fingerprint": "ABC123", "name": "Guard"},
{"fingerprint": "DEF456", "name": "Middle"},
{"fingerprint": "GHI789", "name": "Exit"}
]
}]
}
```
### Hidden Services
```bash
# List hidden services
ubus call luci.tor-shield hidden_services
# Add hidden service
ubus call luci.tor-shield add_hidden_service '{"name":"web","local_port":80,"virtual_port":80}'
# Remove hidden service
ubus call luci.tor-shield remove_hidden_service '{"name":"web"}'
```
### Bandwidth Stats
```bash
# Get bandwidth
ubus call luci.tor-shield bandwidth
# Response:
{
"read": 125000000, # Total bytes downloaded
"written": 45000000, # Total bytes uploaded
"read_rate": 45000, # Current download rate (bytes/sec)
"write_rate": 12000 # Current upload rate (bytes/sec)
}
```
## 🛠️ Troubleshooting
### Tor Won't Start
```bash
# Check logs
logread | grep -i tor
# Verify config
tor --verify-config -f /var/run/tor/torrc
# Check control socket
ls -la /var/run/tor/control
```
### Slow Connections
1. **Check bootstrap** - Wait for 100% completion
2. **Try bridges** - Network may be throttling Tor
3. **Change circuits** - Click "New Identity"
4. **Check exit nodes** - Some exits are slow
### DNS Leaks
```bash
# Verify DNS is routed through Tor
nslookup check.torproject.org
# Should resolve via Tor DNS (127.0.0.1:9053)
```
### Kill Switch Issues
```bash
# Check firewall rules
iptables -L -n | grep -i tor
# Verify kill switch config
uci get tor-shield.main.kill_switch
```
## 📁 File Locations
| Path | Description |
|------|-------------|
| `/etc/config/tor-shield` | UCI configuration |
| `/var/run/tor/torrc` | Generated Tor config |
| `/var/run/tor/control` | Control socket |
| `/var/lib/tor/` | Tor data directory |
| `/var/lib/tor/hidden_service_*/` | Hidden service keys |
| `/tmp/tor_exit_ip` | Cached exit IP |
| `/tmp/tor_real_ip` | Cached real IP |
## 🔐 Security Notes
1. **Kill Switch** - Always enable for maximum protection
2. **DNS Leaks** - Enable DNS over Tor to prevent leaks
3. **Hidden Services** - Keys in `/var/lib/tor/` are sensitive - back them up securely
4. **Exit Nodes** - Consider excluding certain countries for sensitive use
5. **Bridges** - Use if your ISP blocks or throttles Tor
## 📜 License
MIT License - Copyright (C) 2025 CyberMind.fr

View File

@ -250,20 +250,49 @@ return view.extend({
var statusClass = 'disabled';
var statusText = _('Disabled');
var statusEmoji = '\u{26AA}';
if (isConnecting) {
statusClass = 'connecting';
statusText = _('Connecting %d%%').format(status.bootstrap);
statusEmoji = '\u{1F7E1}';
} else if (isProtected) {
statusClass = 'protected';
statusText = _('Protected');
statusEmoji = '\u{1F7E2}';
} else if (isActive) {
statusClass = 'exposed';
statusText = _('Exposed');
statusEmoji = '\u{1F534}';
}
var view = E('div', { 'class': 'tor-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('tor-shield/dashboard.css') }),
// Wizard Welcome Banner (shown when disabled)
!isActive ? E('div', { 'class': 'tor-wizard-banner', 'style': 'background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 50%, #06b6d4 100%); border-radius: 16px; padding: 24px; margin-bottom: 20px; color: #fff; text-align: center;' }, [
E('div', { 'style': 'font-size: 48px; margin-bottom: 12px;' }, '\u{1F9D9}\u200D\u2642\uFE0F'),
E('h2', { 'style': 'margin: 0 0 8px 0; font-size: 24px;' }, '\u{2728} ' + _('Welcome to Tor Shield') + ' \u{2728}'),
E('p', { 'style': 'margin: 0 0 16px 0; opacity: 0.9; font-size: 14px;' }, _('Your gateway to anonymous browsing. Choose a protection level below to get started.')),
E('div', { 'style': 'display: flex; justify-content: center; gap: 24px; flex-wrap: wrap; margin-top: 16px;' }, [
E('div', { 'style': 'text-align: center;' }, [
E('div', { 'style': 'font-size: 32px;' }, '\u{1F512}'),
E('div', { 'style': 'font-size: 12px; opacity: 0.8;' }, _('Encrypted'))
]),
E('div', { 'style': 'text-align: center;' }, [
E('div', { 'style': 'font-size: 32px;' }, '\u{1F310}'),
E('div', { 'style': 'font-size: 12px; opacity: 0.8;' }, _('Anonymous'))
]),
E('div', { 'style': 'text-align: center;' }, [
E('div', { 'style': 'font-size: 32px;' }, '\u{1F6E1}'),
E('div', { 'style': 'font-size: 12px; opacity: 0.8;' }, _('Protected'))
]),
E('div', { 'style': 'text-align: center;' }, [
E('div', { 'style': 'font-size: 32px;' }, '\u{1F30D}'),
E('div', { 'style': 'font-size: 12px; opacity: 0.8;' }, _('Worldwide'))
])
])
]) : '',
// Header
E('div', { 'class': 'tor-header' }, [
E('div', { 'class': 'tor-logo' }, [
@ -271,7 +300,7 @@ return view.extend({
E('div', { 'class': 'tor-logo-text' }, ['Tor ', E('span', {}, 'Shield')])
]),
E('div', { 'class': 'tor-status-badge ' + statusClass }, [
E('span', { 'class': 'tor-status-dot' }),
E('span', { 'style': 'margin-right: 6px;' }, statusEmoji),
statusText
])
]),
@ -350,20 +379,40 @@ return view.extend({
])
]),
// Presets
E('div', { 'class': 'tor-presets' },
presets.map(function(preset) {
return E('div', {
'class': 'tor-preset' + (self.currentPreset === preset.id ? ' active' : ''),
'data-preset': preset.id,
'click': L.bind(function() { this.handlePresetSelect(preset.id); }, self)
}, [
E('div', { 'class': 'tor-preset-icon' }, api.getPresetIcon(preset.icon)),
E('div', { 'class': 'tor-preset-name' }, preset.name),
E('div', { 'class': 'tor-preset-desc' }, preset.description)
]);
})
),
// Presets - Wizard Style
E('div', { 'class': 'tor-presets-wizard', 'style': 'margin-bottom: 24px;' }, [
E('div', { 'style': 'text-align: center; margin-bottom: 16px;' }, [
E('span', { 'style': 'font-size: 20px;' }, '\u{1F9D9}\u200D\u2642\uFE0F'),
E('span', { 'style': 'font-weight: 600; margin-left: 8px;' }, _('Choose Your Protection Level'))
]),
E('div', { 'class': 'tor-presets', 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px;' },
[
{ id: 'anonymous', name: _('Full Anonymity'), icon: '\u{1F6E1}', emoji: '\u{1F9D9}', desc: _('All traffic through Tor'), features: ['\u{2705} ' + _('Kill Switch'), '\u{2705} ' + _('DNS Protection'), '\u{2705} ' + _('Full Routing')] },
{ id: 'selective', name: _('Selective Apps'), icon: '\u{1F3AF}', emoji: '\u{1F50D}', desc: _('SOCKS proxy mode'), features: ['\u{26AA} ' + _('No Kill Switch'), '\u{26AA} ' + _('Manual Config'), '\u{2705} ' + _('App Control')] },
{ id: 'censored', name: _('Bypass Censorship'), icon: '\u{1F513}', emoji: '\u{1F30D}', desc: _('Bridge connections'), features: ['\u{2705} ' + _('obfs4 Bridges'), '\u{2705} ' + _('Anti-Censorship'), '\u{2705} ' + _('Stealth Mode')] }
].map(function(preset) {
var isSelected = self.currentPreset === preset.id;
return E('div', {
'class': 'tor-preset' + (isSelected ? ' active' : ''),
'data-preset': preset.id,
'style': 'background: var(--tor-bg-card, #1a1a24); border-radius: 12px; padding: 16px; cursor: pointer; border: 2px solid ' + (isSelected ? '#7c3aed' : 'transparent') + '; transition: all 0.2s;',
'click': L.bind(function() { this.handlePresetSelect(preset.id); }, self)
}, [
E('div', { 'style': 'text-align: center; margin-bottom: 8px;' }, [
E('span', { 'style': 'font-size: 32px;' }, preset.emoji),
isSelected ? E('span', { 'style': 'position: absolute; margin-left: -8px; font-size: 14px;' }, '\u{2714}\uFE0F') : ''
]),
E('div', { 'style': 'font-weight: 600; text-align: center; margin-bottom: 4px;' }, preset.name),
E('div', { 'style': 'font-size: 11px; color: var(--tor-text-muted, #a0a0b0); text-align: center; margin-bottom: 8px;' }, preset.desc),
E('div', { 'style': 'font-size: 10px; color: var(--tor-text-muted, #a0a0b0);' },
preset.features.map(function(f) {
return E('div', { 'style': 'margin: 2px 0;' }, f);
})
)
]);
})
)
]),
// Quick Stats
E('div', { 'class': 'tor-quick-stats' }, [
@ -442,42 +491,109 @@ return view.extend({
])
]),
// Actions Card
// Actions Card - Enhanced Wizard Style
E('div', { 'class': 'tor-card' }, [
E('div', { 'class': 'tor-card-header' }, [
E('div', { 'class': 'tor-card-title' }, [
E('span', { 'class': 'tor-card-title-icon' }, '\u26A1'),
E('span', { 'class': 'tor-card-title-icon' }, '\u{26A1}'),
_('Quick Actions')
]),
E('span', { 'style': 'font-size: 12px; color: var(--tor-text-muted);' }, '\u{1F9D9} ' + _('Wizard Tools'))
]),
E('div', { 'class': 'tor-card-body' }, [
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px;' }, [
E('button', {
'class': 'tor-btn tor-btn-primary',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px;',
'click': L.bind(this.handleNewIdentity, this),
'disabled': !isActive
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F504}'),
E('span', {}, _('New Identity'))
]),
E('button', {
'class': 'tor-btn',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px;',
'click': L.bind(this.handleLeakTest, this),
'disabled': !isActive
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F50D}'),
E('span', {}, _('Leak Test'))
]),
E('button', {
'class': 'tor-btn tor-btn-warning',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px;',
'click': L.bind(this.handleRestart, this)
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F504}'),
E('span', {}, _('Restart'))
]),
E('a', {
'class': 'tor-btn',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px; text-decoration: none;',
'href': L.url('admin', 'services', 'tor-shield', 'circuits')
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F5FA}'),
E('span', {}, _('Circuits'))
]),
E('a', {
'class': 'tor-btn',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px; text-decoration: none;',
'href': L.url('admin', 'services', 'tor-shield', 'hidden-services')
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F9C5}'),
E('span', {}, _('.onion Sites'))
]),
E('a', {
'class': 'tor-btn',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px; text-decoration: none;',
'href': L.url('admin', 'services', 'tor-shield', 'bridges')
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{1F309}'),
E('span', {}, _('Bridges'))
]),
E('a', {
'class': 'tor-btn',
'style': 'display: flex; flex-direction: column; align-items: center; padding: 16px; border-radius: 12px; text-decoration: none;',
'href': L.url('admin', 'services', 'tor-shield', 'settings')
}, [
E('span', { 'style': 'font-size: 24px; margin-bottom: 4px;' }, '\u{2699}\uFE0F'),
E('span', {}, _('Settings'))
])
])
])
]),
// Features Guide Card
E('div', { 'class': 'tor-card', 'style': 'background: linear-gradient(135deg, rgba(124, 58, 237, 0.1) 0%, rgba(6, 182, 212, 0.1) 100%);' }, [
E('div', { 'class': 'tor-card-header' }, [
E('div', { 'class': 'tor-card-title' }, [
E('span', { 'class': 'tor-card-title-icon' }, '\u{1F4D6}'),
_('How Tor Shield Works')
])
]),
E('div', { 'class': 'tor-card-body' }, [
E('div', { 'style': 'display: flex; gap: 12px; flex-wrap: wrap;' }, [
E('button', {
'class': 'tor-btn tor-btn-primary',
'click': L.bind(this.handleNewIdentity, this),
'disabled': !isActive
}, ['\uD83D\uDD04 ', _('New Identity')]),
E('button', {
'class': 'tor-btn',
'click': L.bind(this.handleLeakTest, this),
'disabled': !isActive
}, ['\uD83D\uDD0D ', _('Leak Test')]),
E('button', {
'class': 'tor-btn tor-btn-warning',
'click': L.bind(this.handleRestart, this)
}, ['\u21BB ', _('Restart')]),
E('a', {
'class': 'tor-btn',
'href': L.url('admin', 'services', 'tor-shield', 'circuits')
}, ['\uD83D\uDDFA ', _('View Circuits')]),
E('a', {
'class': 'tor-btn',
'href': L.url('admin', 'services', 'tor-shield', 'hidden-services')
}, ['\uD83E\uDDC5 ', _('Hidden Services')]),
E('a', {
'class': 'tor-btn',
'href': L.url('admin', 'services', 'tor-shield', 'settings')
}, ['\u2699 ', _('Settings')])
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;' }, [
E('div', { 'style': 'text-align: center; padding: 12px;' }, [
E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, '\u{1F512}'),
E('div', { 'style': 'font-weight: 600; margin-bottom: 4px;' }, _('Encrypted')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted);' }, _('3 layers of encryption protect your data'))
]),
E('div', { 'style': 'text-align: center; padding: 12px;' }, [
E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, '\u{1F465}'),
E('div', { 'style': 'font-weight: 600; margin-bottom: 4px;' }, _('Anonymous')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted);' }, _('Your real IP is hidden from websites'))
]),
E('div', { 'style': 'text-align: center; padding: 12px;' }, [
E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, '\u{1F310}'),
E('div', { 'style': 'font-weight: 600; margin-bottom: 4px;' }, _('Decentralized')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted);' }, _('Traffic routed through volunteer relays'))
]),
E('div', { 'style': 'text-align: center; padding: 12px;' }, [
E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, '\u{1F6E1}'),
E('div', { 'style': 'font-weight: 600; margin-bottom: 4px;' }, _('Kill Switch')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted);' }, _('Blocks traffic if Tor disconnects'))
])
])
])
]),

View File

@ -484,7 +484,7 @@ _generate_frontends() {
# HTTP Frontend
cat << EOF
frontend http-in
bind *:$http_port
bind *:$http_port,[::]:$http_port
mode http
# ACME challenge routing (no HAProxy restart needed for cert issuance)
@ -509,7 +509,7 @@ EOF
if [ -d "$CERTS_PATH" ] && ls "$CERTS_PATH"/*.pem >/dev/null 2>&1; then
cat << EOF
frontend https-in
bind *:$https_port ssl crt $CONTAINER_CERTS_PATH/ alpn h2,http/1.1
bind *:$https_port,[::]:$https_port ssl crt $CONTAINER_CERTS_PATH/ alpn h2,http/1.1
mode http
http-request set-header X-Forwarded-Proto https
http-request set-header X-Real-IP %[src]
@ -537,7 +537,7 @@ _add_ssl_redirect() {
local acl_name=$(echo "$domain" | tr '.' '_' | tr '-' '_')
echo " acl host_${acl_name} hdr(host) -i $domain"
echo " http-request redirect scheme https code 301 if host_${acl_name} !{ ssl_fc }"
echo " http-request redirect scheme https code 301 if host_${acl_name} !{ ssl_fc } !is_acme_challenge"
}
_add_vhost_acl() {

View File

@ -625,6 +625,85 @@ sync_packages_to_local_feed() {
print_success "Synchronized $pkg_count packages to local-feed"
}
# Deploy packages to router
deploy_packages() {
local router="$1"
local packages="$2"
local ssh_opts="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
# Test connectivity
print_info "Testing connection to router..."
if ! ssh $ssh_opts root@$router "echo 'Connected'" 2>/dev/null; then
print_error "Cannot connect to router at $router"
return 1
fi
# Find packages to deploy
local pkg_dir="$SDK_DIR/bin/packages/$ARCH_NAME/secubox"
local target_pkg_dir="$SDK_DIR/bin/targets/$SDK_PATH/packages"
if [[ -n "$packages" ]]; then
# Deploy specific packages
print_info "Deploying specific packages: $packages"
for pkg in $packages; do
local ipk=$(find "$pkg_dir" "$target_pkg_dir" -name "${pkg}*.ipk" 2>/dev/null | head -1)
if [[ -n "$ipk" ]]; then
print_info "Deploying $(basename "$ipk")..."
scp $ssh_opts "$ipk" root@$router:/tmp/
ssh $ssh_opts root@$router "opkg install /tmp/$(basename "$ipk") --force-reinstall 2>&1"
else
print_warning "Package not found: $pkg"
fi
done
else
# Deploy all recently built packages
print_info "Deploying all packages from SDK..."
# Find all IPK files built today
local today=$(date +%Y%m%d)
local ipks=$(find "$pkg_dir" -name "*.ipk" -mtime 0 2>/dev/null)
if [[ -z "$ipks" ]]; then
print_warning "No recently built packages found"
print_info "Run 'local-build.sh build <package>' first"
return 1
fi
# Copy packages
print_info "Copying packages to router..."
for ipk in $ipks; do
scp $ssh_opts "$ipk" root@$router:/tmp/
done
# Install packages
print_info "Installing packages..."
ssh $ssh_opts root@$router "opkg install /tmp/*.ipk --force-reinstall 2>&1" || true
fi
# Sync feed to router
print_info "Syncing package feed to router..."
local feed_pkg="$SDK_DIR/bin/packages/$ARCH_NAME/secubox"
if [[ -d "$feed_pkg" ]]; then
ssh $ssh_opts root@$router "mkdir -p /www/secubox-feed"
scp $ssh_opts "$feed_pkg"/*.ipk root@$router:/www/secubox-feed/ 2>/dev/null || true
# Generate Packages index
ssh $ssh_opts root@$router "cd /www/secubox-feed && \
rm -f Packages Packages.gz && \
for ipk in *.ipk; do \
[ -f \"\$ipk\" ] && tar -xzf \"\$ipk\" ./control.tar.gz && \
tar -xzf control.tar.gz ./control && \
cat control >> Packages && echo '' >> Packages && \
rm -f control control.tar.gz; \
done && \
gzip -k Packages 2>/dev/null || true"
print_success "Feed synced to /www/secubox-feed"
fi
print_success "Deployment complete"
}
# Copy packages to SDK feed
copy_packages() {
local single_package="$1"
@ -2249,6 +2328,7 @@ COMMANDS:
clean Clean build directories
clean-all Clean all build directories including OpenWrt source and local-feed
sync Sync packages from package/secubox to local-feed
deploy [router] [packages] Deploy packages to router (default: 192.168.255.1)
help Show this help message
PACKAGES:
@ -2429,6 +2509,13 @@ main() {
print_success "Packages synchronized to local-feed"
;;
deploy)
local router="${1:-192.168.255.1}"
local packages="$2"
print_header "Deploying Packages to Router ($router)"
deploy_packages "$router" "$packages"
;;
help|--help|-h)
show_usage
;;