feat(metablogizer): Add Tor hidden service integration

Dynamic .onion address generation for hosted sites:
- enable_tor: Create Tor hidden service for a site
- disable_tor: Remove Tor hidden service
- get_tor_status: Get Tor status for all sites
- Sites now include onion_address and onion_url in listings

When enabled, sites are accessible via both:
- Public domain (https://domain.com)
- Tor hidden service (http://xxx.onion)

Also includes DNS resolution fix using Google DNS API.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-30 18:22:36 +01:00
parent 95ff73f6e7
commit 0562730b5f

View File

@ -10,6 +10,7 @@ UCI_CONFIG="metablogizer"
SITES_ROOT="/srv/metablogizer/sites"
NGINX_CONTAINER="nginx"
PORT_BASE=8900
TOR_DATA="/var/lib/tor"
# Helper: Get UCI value with default
get_uci() {
@ -73,6 +74,66 @@ reload_haproxy() {
fi
}
# Get .onion address for a site if Tor hidden service exists
get_onion_address() {
local site_name="$1"
local hs_name="metablog_$(echo "$site_name" | tr -cd 'a-zA-Z0-9_')"
local hostname_file="$TOR_DATA/hidden_service_$hs_name/hostname"
if [ -f "$hostname_file" ]; then
cat "$hostname_file"
fi
}
# Check if Tor hidden service exists for site
has_tor_service() {
local site_name="$1"
local hs_name="metablog_$(echo "$site_name" | tr -cd 'a-zA-Z0-9_')"
uci -q get "tor-shield.hs_$hs_name" >/dev/null 2>&1
}
# Create Tor hidden service for a site
create_tor_hidden_service() {
local site_name="$1"
local local_port="$2"
local hs_name="metablog_$(echo "$site_name" | tr -cd 'a-zA-Z0-9_')"
# Create hidden service in tor-shield config
uci set "tor-shield.hs_$hs_name=hidden_service"
uci set "tor-shield.hs_$hs_name.name=$hs_name"
uci set "tor-shield.hs_$hs_name.enabled=1"
uci set "tor-shield.hs_$hs_name.local_port=$local_port"
uci set "tor-shield.hs_$hs_name.virtual_port=80"
uci commit tor-shield
# Restart Tor to generate .onion address
/etc/init.d/tor-shield restart >/dev/null 2>&1 &
return 0
}
# Remove Tor hidden service for a site
remove_tor_hidden_service() {
local site_name="$1"
local hs_name="metablog_$(echo "$site_name" | tr -cd 'a-zA-Z0-9_')"
uci delete "tor-shield.hs_$hs_name" 2>/dev/null
uci commit tor-shield
# Remove hidden service data directory
rm -rf "$TOR_DATA/hidden_service_$hs_name" 2>/dev/null
# Restart Tor
/etc/init.d/tor-shield restart >/dev/null 2>&1 &
}
# Check if Tor is running and ready
is_tor_ready() {
pgrep -f "/usr/sbin/tor" >/dev/null 2>&1 && \
[ -S "/var/run/tor/control" ]
}
# Status method - get overall status and list all sites
method_status() {
local enabled runtime detected_runtime nginx_running site_count
@ -129,8 +190,8 @@ method_list_sites() {
_add_site() {
local section="$1"
local name domain gitea_repo ssl enabled description
local has_content last_sync
local name domain gitea_repo ssl enabled description tor_enabled
local has_content last_sync onion_address
config_get name "$section" name ""
config_get domain "$section" domain ""
@ -138,6 +199,7 @@ _add_site() {
config_get ssl "$section" ssl "1"
config_get enabled "$section" enabled "1"
config_get description "$section" description ""
config_get tor_enabled "$section" tor_enabled "0"
# Check if site has content
has_content="0"
@ -151,6 +213,12 @@ _add_site() {
last_sync=$(cd "$SITES_ROOT/$name" && git log -1 --format="%ci" 2>/dev/null || echo "")
fi
# Get Tor .onion address if available
onion_address=""
if [ "$tor_enabled" = "1" ] || has_tor_service "$name"; then
onion_address=$(get_onion_address "$name")
fi
json_add_object
json_add_string "id" "$section"
json_add_string "name" "$name"
@ -162,6 +230,12 @@ _add_site() {
json_add_boolean "has_content" "$has_content"
json_add_string "last_sync" "$last_sync"
json_add_string "url" "https://$domain"
# Tor hidden service info
json_add_boolean "tor_enabled" "$(has_tor_service "$name" && echo 1 || echo 0)"
[ -n "$onion_address" ] && json_add_string "onion_address" "$onion_address"
[ -n "$onion_address" ] && json_add_string "onion_url" "http://$onion_address"
json_close_object
}
@ -1342,6 +1416,134 @@ EOF
json_dump
}
# Enable Tor hidden service for a site
method_enable_tor() {
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 port
name=$(get_uci "$id" name "")
port=$(get_uci "$id" port "")
if [ -z "$name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Site not found"
json_dump
return
fi
# Default to port 80 if not set (site uses nginx)
[ -z "$port" ] && port="80"
# Create Tor hidden service
create_tor_hidden_service "$name" "$port"
# Mark site as Tor-enabled
uci set "$UCI_CONFIG.$id.tor_enabled=1"
uci commit "$UCI_CONFIG"
json_init
json_add_boolean "success" 1
json_add_string "message" "Tor hidden service created. Restart Tor Shield to get .onion address."
json_add_string "name" "$name"
json_add_int "port" "$port"
json_dump
}
# Disable Tor hidden service for a site
method_disable_tor() {
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
name=$(get_uci "$id" name "")
if [ -z "$name" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Site not found"
json_dump
return
fi
# Remove Tor hidden service
remove_tor_hidden_service "$name"
# Mark site as Tor-disabled
uci set "$UCI_CONFIG.$id.tor_enabled=0"
uci commit "$UCI_CONFIG"
json_init
json_add_boolean "success" 1
json_add_string "message" "Tor hidden service removed"
json_dump
}
# Get Tor status for all sites
method_get_tor_status() {
json_init
json_add_boolean "tor_running" "$(is_tor_ready && echo 1 || echo 0)"
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
json_add_array "sites"
config_load "$UCI_CONFIG"
config_foreach _add_tor_status site
json_close_array
json_dump
}
_add_tor_status() {
local section="$1"
local name port
config_get name "$section" name ""
config_get port "$section" port ""
[ -z "$name" ] && return
local onion_address=""
local tor_enabled=0
if has_tor_service "$name"; then
tor_enabled=1
onion_address=$(get_onion_address "$name")
fi
json_add_object
json_add_string "id" "$section"
json_add_string "name" "$name"
json_add_boolean "tor_enabled" "$tor_enabled"
[ -n "$onion_address" ] && json_add_string "onion_address" "$onion_address"
[ -n "$onion_address" ] && json_add_boolean "onion_ready" 1 || json_add_boolean "onion_ready" 0
json_close_object
}
# Save global settings
method_save_settings() {
local enabled runtime nginx_container sites_root gitea_url
@ -1388,7 +1590,10 @@ case "$1" in
"save_settings": { "enabled": "boolean", "nginx_container": "string", "sites_root": "string" },
"get_hosting_status": {},
"check_site_health": { "id": "string" },
"repair_site": { "id": "string" }
"repair_site": { "id": "string" },
"enable_tor": { "id": "string" },
"disable_tor": { "id": "string" },
"get_tor_status": {}
}
EOF
;;
@ -1409,6 +1614,9 @@ EOF
get_hosting_status) method_get_hosting_status ;;
check_site_health) method_check_site_health ;;
repair_site) method_repair_site ;;
enable_tor) method_enable_tor ;;
disable_tor) method_disable_tor ;;
get_tor_status) method_get_tor_status ;;
*) echo '{"error": "unknown method"}' ;;
esac
;;