From 364f19d421482e6a7730102ac08db9d9095fbcf5 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 10 Feb 2026 06:42:50 +0100 Subject: [PATCH] feat: Add Gitea auto-push and fix Tor Shield server mode Streamlit/MetaBlogizer: - Add 'gitea push ' command to both streamlitctl and metablogizerctl - Auto-creates Gitea repo via API if it doesn't exist - Initializes git, commits all files, and pushes to Gitea - Stores repo reference in UCI for future syncs Tor Shield: - Add 'wan_input_allow' option for server preset - Server mode now properly allows WAN inbound (ports 80, 443, 8443) - Uses nftables rules to integrate with OpenWrt firewall4 - Outbound traffic still routed through Tor (kill_switch) - Cleanup nftables rules on stop/disable Co-Authored-By: Claude Opus 4.5 --- .../files/usr/sbin/metablogizerctl | 114 +++++++++++++++++- .../files/usr/sbin/streamlitctl | 97 ++++++++++++++- .../files/etc/config/tor-shield | 1 + .../files/etc/init.d/tor-shield | 59 ++++++++- .../secubox-app-tor/files/usr/sbin/torctl | 8 +- 5 files changed, 274 insertions(+), 5 deletions(-) diff --git a/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl b/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl index 9539dddd..6276ce37 100644 --- a/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl +++ b/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl @@ -38,6 +38,7 @@ Site Commands: delete Delete site sync Sync site from git repo publish Publish site (create HAProxy vhost) + gitea push Create Gitea repo and push site content emancipate KISS ULTIME MODE - Full exposure workflow: 1. DNS A record (Gandi/OVH) 2. Vortex DNS mesh publication @@ -409,7 +410,8 @@ cmd_sync() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; return 1; } - local gitea_repo=$(uci_get site_${name}.gitea_repo) + local section=$(get_section "$name") + local gitea_repo=$(uci_get ${section}.gitea_repo) [ -z "$gitea_repo" ] && { log_error "No git repo configured for $name"; return 1; } local site_dir="$SITES_ROOT/$name" @@ -434,6 +436,109 @@ cmd_sync() { log_info "Sync complete" } +# Create Gitea repo via API and push local site content +cmd_gitea_push() { + local name="$1" + [ -z "$name" ] && { log_error "Site name required"; return 1; } + + if ! site_exists "$name"; then + log_error "Site '$name' not found" + return 1 + fi + + # Load Gitea config + local gitea_enabled=$(uci_get main.gitea_enabled) + local gitea_url=$(uci_get main.gitea_url) + local gitea_user=$(uci_get main.gitea_user) + local gitea_token=$(uci_get main.gitea_token) + + [ -z "$gitea_url" ] && gitea_url="http://localhost:3000" + + if [ -z "$gitea_token" ]; then + log_error "Gitea token not configured" + log_info "Configure with:" + log_info " uci set metablogizer.main.gitea_url='http://192.168.255.1:3000'" + log_info " uci set metablogizer.main.gitea_user='admin'" + log_info " uci set metablogizer.main.gitea_token='your-token'" + log_info " uci commit metablogizer" + return 1 + fi + + local site_dir="$SITES_ROOT/$name" + + if [ ! -d "$site_dir" ]; then + log_error "Site '$name' not found at $site_dir" + return 1 + fi + + local gitea_host=$(echo "$gitea_url" | sed 's|^https\?://||' | sed 's|/.*||') + local gitea_proto=$(echo "$gitea_url" | grep -q '^https' && echo "https" || echo "http") + local repo_name="metablog-$name" + + log_info "Creating Gitea repository: $repo_name" + + # Check if repo exists, create if not + local repo_check=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $gitea_token" \ + "${gitea_url}/api/v1/repos/${gitea_user}/${repo_name}" 2>/dev/null) + + if [ "$repo_check" != "200" ]; then + log_info "Repository doesn't exist, creating..." + local create_result=$(curl -s -X POST \ + -H "Authorization: token $gitea_token" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"${repo_name}\",\"description\":\"MetaBlogizer site: ${name}\",\"private\":false,\"auto_init\":false}" \ + "${gitea_url}/api/v1/user/repos" 2>/dev/null) + + if ! echo "$create_result" | grep -q "\"name\":"; then + log_error "Failed to create repository" + log_error "Response: $create_result" + return 1 + fi + log_info "Repository created: ${gitea_user}/${repo_name}" + else + log_info "Repository exists: ${gitea_user}/${repo_name}" + fi + + # Initialize git in site directory if needed + cd "$site_dir" + + if [ ! -d ".git" ]; then + log_info "Initializing git repository..." + git init + git config user.name "$gitea_user" + git config user.email "${gitea_user}@localhost" + fi + + # Set remote + local remote_url="${gitea_proto}://${gitea_user}:${gitea_token}@${gitea_host}/${gitea_user}/${repo_name}.git" + git remote remove origin 2>/dev/null + git remote add origin "$remote_url" + + # Add, commit and push + log_info "Adding files and committing..." + git add -A + git commit -m "Auto-push from SecuBox MetaBlogizer at $(date -Iseconds)" 2>/dev/null || \ + log_info "No changes to commit" + + log_info "Pushing to Gitea..." + git push -u origin HEAD:main --force 2>&1 || { + # Try master branch as fallback + git push -u origin HEAD:master --force 2>&1 || { + log_error "Failed to push to Gitea" + return 1 + } + } + + # Save repo reference in UCI + local section=$(get_section "$name") + uci set "${CONFIG}.${section}.gitea_repo=${gitea_user}/${repo_name}" + uci set "${CONFIG}.${section}.gitea_synced=$(date -Iseconds)" + uci commit "$CONFIG" + + log_info "Push complete: ${gitea_url}/${gitea_user}/${repo_name}" +} + cmd_runtime() { local action="$1" local value="$2" @@ -764,6 +869,13 @@ case "${1:-}" in runtime) shift; cmd_runtime "$@" ;; status) shift; cmd_status "$@" ;; install-nginx) shift; cmd_install_nginx "$@" ;; + gitea) + shift + case "${1:-}" in + push) shift; cmd_gitea_push "$@" ;; + *) echo "Usage: metablogizerctl gitea push "; exit 1 ;; + esac + ;; help|--help|-h) usage ;; *) usage ;; esac diff --git a/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl b/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl index 03eb4227..fd255105 100644 --- a/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl +++ b/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl @@ -96,6 +96,7 @@ Gitea Integration: gitea setup Configure git credentials gitea clone Clone app from Gitea repo gitea pull Pull latest from Gitea + gitea push Create Gitea repo and push app content Exposure: emancipate [domain] KISS ULTIME MODE - Full exposure workflow: @@ -1028,6 +1029,99 @@ cmd_gitea_pull() { log_info "Update complete" } +# Create Gitea repo via API and push local content +cmd_gitea_push() { + require_root + load_config + + local name="$1" + [ -z "$name" ] && { log_error "App name required"; return 1; } + + if [ "$gitea_enabled" != "1" ]; then + log_error "Gitea integration not enabled" + log_info "Enable with: uci set streamlit.gitea.enabled=1 && uci commit streamlit" + return 1 + fi + + if [ -z "$gitea_token" ]; then + log_error "Gitea token not configured" + return 1 + fi + + local app_dir="$APPS_PATH/$name" + + if [ ! -d "$app_dir" ]; then + log_error "App '$name' not found at $app_dir" + return 1 + fi + + local gitea_host=$(echo "$gitea_url" | sed 's|^https\?://||' | sed 's|/.*||') + local gitea_proto=$(echo "$gitea_url" | grep -q '^https' && echo "https" || echo "http") + local repo_name="streamlit-$name" + + log_info "Creating Gitea repository: $repo_name" + + # Check if repo exists, create if not + local repo_check=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $gitea_token" \ + "${gitea_url}/api/v1/repos/${gitea_user}/${repo_name}" 2>/dev/null) + + if [ "$repo_check" != "200" ]; then + log_info "Repository doesn't exist, creating..." + local create_result=$(curl -s -X POST \ + -H "Authorization: token $gitea_token" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"${repo_name}\",\"description\":\"Streamlit app: ${name}\",\"private\":false,\"auto_init\":false}" \ + "${gitea_url}/api/v1/user/repos" 2>/dev/null) + + if ! echo "$create_result" | grep -q "\"name\":"; then + log_error "Failed to create repository" + log_error "Response: $create_result" + return 1 + fi + log_info "Repository created: ${gitea_user}/${repo_name}" + else + log_info "Repository exists: ${gitea_user}/${repo_name}" + fi + + # Initialize git in app directory if needed + cd "$app_dir" + + if [ ! -d ".git" ]; then + log_info "Initializing git repository..." + git init + git config user.name "$gitea_user" + git config user.email "${gitea_user}@localhost" + fi + + # Set remote + local remote_url="${gitea_proto}://${gitea_user}:${gitea_token}@${gitea_host}/${gitea_user}/${repo_name}.git" + git remote remove origin 2>/dev/null + git remote add origin "$remote_url" + + # Add, commit and push + log_info "Adding files and committing..." + git add -A + git commit -m "Auto-push from SecuBox Streamlit at $(date -Iseconds)" 2>/dev/null || \ + log_info "No changes to commit" + + log_info "Pushing to Gitea..." + git push -u origin HEAD:main --force 2>&1 || { + # Try master branch as fallback + git push -u origin HEAD:master --force 2>&1 || { + log_error "Failed to push to Gitea" + return 1 + } + } + + # Save repo reference in UCI + uci set "${CONFIG}.${name}.repo=${gitea_user}/${repo_name}" + uci set "${CONFIG}.${name}.gitea_synced=$(date -Iseconds)" + uci commit "$CONFIG" + + log_info "Push complete: ${gitea_url}/${gitea_user}/${repo_name}" +} + # =========================================== # KISS ULTIME MODE - Emancipate # =========================================== @@ -1462,7 +1556,8 @@ case "${1:-}" in setup) shift; cmd_gitea_setup "$@" ;; clone) shift; cmd_gitea_clone "$@" ;; pull) shift; cmd_gitea_pull "$@" ;; - *) echo "Usage: streamlitctl gitea {setup|clone|pull}"; exit 1 ;; + push) shift; cmd_gitea_push "$@" ;; + *) echo "Usage: streamlitctl gitea {setup|clone|pull|push}"; exit 1 ;; esac ;; diff --git a/package/secubox/secubox-app-tor/files/etc/config/tor-shield b/package/secubox/secubox-app-tor/files/etc/config/tor-shield index 0b724b7a..018b2a55 100644 --- a/package/secubox/secubox-app-tor/files/etc/config/tor-shield +++ b/package/secubox/secubox-app-tor/files/etc/config/tor-shield @@ -36,6 +36,7 @@ config preset 'server' option dns_over_tor '1' option kill_switch '1' option lan_proxy '1' + option wan_input_allow '1' config proxy 'socks' option port '9050' diff --git a/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield b/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield index 23698b79..6e9c4541 100755 --- a/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield +++ b/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield @@ -161,12 +161,13 @@ EOF } setup_iptables() { - local mode trans_port dns_port dns_over_tor kill_switch + local mode trans_port dns_port dns_over_tor kill_switch wan_input_allow config_load "$CONFIG" config_get mode main mode 'transparent' config_get kill_switch main kill_switch '1' config_get dns_over_tor main dns_over_tor '1' + config_get wan_input_allow main wan_input_allow '0' config_get trans_port trans port '9040' config_get dns_port trans dns_port '9053' @@ -186,6 +187,9 @@ setup_iptables() { iptables -t filter -F TOR_SHIELD 2>/dev/null iptables -t filter -X TOR_SHIELD 2>/dev/null + # Clean up nftables rules for server mode + cleanup_nftables_wan + [ "$mode" = "transparent" ] || return 0 # Create chains (ignore "already exists" errors) @@ -221,10 +225,60 @@ setup_iptables() { iptables -t filter -A OUTPUT -j TOR_SHIELD fi + # Server mode: Allow WAN inbound traffic (for published services) + if [ "$wan_input_allow" = "1" ]; then + setup_nftables_wan_allow + fi + # LAN client Tor routing via PREROUTING setup_lan_proxy } +# nftables helpers for server mode WAN access +cleanup_nftables_wan() { + # Remove any previous tor-shield nftables rules + if command -v nft >/dev/null 2>&1; then + nft delete chain inet fw4 tor_shield_wan_in 2>/dev/null || true + fi +} + +setup_nftables_wan_allow() { + # Server mode: Ensure WAN inbound is allowed + # This creates nftables rules to allow inbound connections from WAN + # Works with OpenWrt firewall4 (nftables-based) + + if ! command -v nft >/dev/null 2>&1; then + echo "Warning: nft command not found, WAN input rules may not apply" + return 1 + fi + + # Check if fw4 table exists (OpenWrt firewall4) + if ! nft list table inet fw4 >/dev/null 2>&1; then + echo "Note: fw4 table not found, using legacy firewall" + return 0 + fi + + # Create chain for Tor Shield WAN input if not exists + nft add chain inet fw4 tor_shield_wan_in 2>/dev/null || true + + # Allow all established/related (responses to outgoing) + nft add rule inet fw4 tor_shield_wan_in ct state established,related accept 2>/dev/null || true + + # Allow new inbound on common service ports (HAProxy, HTTPS, HTTP) + # These are the ports typically used for published services + nft add rule inet fw4 tor_shield_wan_in tcp dport 80 accept 2>/dev/null || true + nft add rule inet fw4 tor_shield_wan_in tcp dport 443 accept 2>/dev/null || true + nft add rule inet fw4 tor_shield_wan_in tcp dport 8443 accept 2>/dev/null || true + + # Hook into input_wan chain (before drop) + # First remove any existing jump to avoid duplicates + nft delete rule inet fw4 input_wan jump tor_shield_wan_in 2>/dev/null || true + # Add jump at the beginning of input_wan + nft insert rule inet fw4 input_wan jump tor_shield_wan_in 2>/dev/null || true + + echo "Server mode: WAN inbound allowed on ports 80, 443, 8443" +} + add_excluded_ip() { iptables -t nat -A TOR_SHIELD -d "$1" -j RETURN } @@ -279,6 +333,9 @@ remove_iptables() { done iptables -t filter -F TOR_SHIELD 2>/dev/null iptables -t filter -X TOR_SHIELD 2>/dev/null + + # Clean up nftables rules for server mode + cleanup_nftables_wan } start_service() { diff --git a/package/secubox/secubox-app-tor/files/usr/sbin/torctl b/package/secubox/secubox-app-tor/files/usr/sbin/torctl index a8abb584..dfa134a8 100644 --- a/package/secubox/secubox-app-tor/files/usr/sbin/torctl +++ b/package/secubox/secubox-app-tor/files/usr/sbin/torctl @@ -123,14 +123,16 @@ cmd_status() { fi # Show config details - local dns_over_tor kill_switch + local dns_over_tor kill_switch wan_input_allow config_get dns_over_tor main dns_over_tor '1' config_get kill_switch main kill_switch '1' + config_get wan_input_allow main wan_input_allow '0' echo "" echo "Configuration:" echo " DNS over Tor: $([ "$dns_over_tor" = "1" ] && echo "Yes" || echo "No")" echo " Kill Switch: $([ "$kill_switch" = "1" ] && echo "Yes" || echo "No")" + echo " WAN Input Allow: $([ "$wan_input_allow" = "1" ] && echo "Yes (Server Mode)" || echo "No")" # Bridge status local bridges_enabled @@ -151,18 +153,20 @@ cmd_enable() { # Load preset configuration config_load "$CONFIG" - local preset_mode preset_dns preset_kill preset_bridges preset_lan_proxy + local preset_mode preset_dns preset_kill preset_bridges preset_lan_proxy preset_wan_input config_get preset_mode "$preset" mode 'transparent' config_get preset_dns "$preset" dns_over_tor '1' config_get preset_kill "$preset" kill_switch '1' config_get preset_bridges "$preset" use_bridges '0' config_get preset_lan_proxy "$preset" lan_proxy '0' + config_get preset_wan_input "$preset" wan_input_allow '0' # Apply preset settings uci set tor-shield.main.enabled='1' uci set tor-shield.main.mode="$preset_mode" uci set tor-shield.main.dns_over_tor="$preset_dns" uci set tor-shield.main.kill_switch="$preset_kill" + uci set tor-shield.main.wan_input_allow="$preset_wan_input" uci set tor-shield.trans.lan_proxy="$preset_lan_proxy" if [ "$preset_bridges" = "1" ]; then