diff --git a/Dockerfile b/Dockerfile index 081080d..c1a2eac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ ARG PHP_SERVER ARG SHELL_OPTS ARG PATH -ARG PACK_LIST="tor torsocks lyrebird privoxy nginx socat unbound bind-tools php$PHP_VERSION" +ARG PACK_LIST="tor torsocks lyrebird privoxy nginx socat unbound bind-tools php$PHP_VERSION php$PHP_VERSION-fpm php$PHP_VERSION-session php$PHP_VERSION-json" ENV ENV=~/.profile ENV SHELL="/bin/sh" diff --git a/README.md b/README.md index 589d994..0c29ed5 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,155 @@ -## ๐Ÿ‘‹ Welcome to tor ๐Ÿš€ +## ๐Ÿง… Tor Docker Container with Admin Panel ๐Ÿš€ -tor README - - -## Install my system scripts +A comprehensive Docker container providing Tor services (bridge, relay, server) with hidden service support, DNS resolution, HTTP proxy, and a complete web-based admin panel. -```shell - sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")" - sudo systemmgr --config && sudo systemmgr install scripts -``` +## โœจ Features -## Get source files +- **Tor Services**: Bridge, Relay, and Server modes +- **Hidden Services**: Easy .onion site creation and management +- **DNS Resolution**: Unbound DNS resolver with .onion domain support +- **HTTP Proxy**: Privoxy for web traffic routing through Tor +- **Web Interface**: Nginx with PHP support +- **Admin Panel**: Complete web-based management interface +- **REST API**: JWT-authenticated API for automation -```shell -dockermgr download src tor -``` +## ๐Ÿš€ Quick Start -OR +### Build Container ```shell git clone "https://github.com/casjaysdevdocker/tor" "$HOME/Projects/github/casjaysdevdocker/tor" +cd "$HOME/Projects/github/casjaysdevdocker/tor" +docker build -t casjaysdevdocker/tor . ``` -## Build container +### Run Container ```shell -cd "$HOME/Projects/github/casjaysdevdocker/tor" -buildx +docker run -d \ + --name tor-container \ + -p 8080:80 \ + -p 9050:9050 \ + -p 9053:9053 \ + -p 8118:8118 \ + -e TOR_ADMIN_USER=admin \ + -e TOR_ADMIN_PASS=secure_password \ + -v "/var/lib/srv/$USER/docker/casjaysdevdocker/tor/latest/data:/data" \ + -v "/var/lib/srv/$USER/docker/casjaysdevdocker/tor/latest/config:/config" \ + casjaysdevdocker/tor +``` + +## ๐Ÿ” Admin Panel + +Access the web-based admin panel at: `http://localhost:8080/admin/` + +### Authentication + +Set admin credentials via environment variables: + +- `TOR_ADMIN_USER` (default: admin) +- `TOR_ADMIN_PASS` (default: torpass123) +- `TOR_JWT_SECRET` (optional: custom JWT secret) + +### Features + +- **Dashboard**: Service status monitoring and control +- **Configuration**: Edit Tor, Nginx, Unbound, Privoxy configs +- **Hidden Services**: Create and manage .onion sites +- **Logs**: Real-time log monitoring with auto-refresh +- **API Tokens**: Generate JWT tokens for programmatic access + +## ๐Ÿ”— REST API + +### Authentication + +```bash +# Get JWT token +curl -X POST http://localhost:8080/admin/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"your_password"}' +``` + +### Service Management + +```bash +# List all services +curl -H "Authorization: Bearer YOUR_TOKEN" \ + http://localhost:8080/admin/api/services + +# Get specific service status +curl -H "Authorization: Bearer YOUR_TOKEN" \ + http://localhost:8080/admin/api/services/tor-server + +# Restart a service +curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \ + http://localhost:8080/admin/api/services/tor-server/restart +``` + +### Hidden Services + +```bash +# List hidden services +curl -H "Authorization: Bearer YOUR_TOKEN" \ + http://localhost:8080/admin/api/hidden-services + +# Create hidden service +curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"myapp","port_mapping":"80 127.0.0.1:8080"}' \ + http://localhost:8080/admin/api/hidden-services +``` + +## ๐ŸŒ Network Ports + +| Port | Service | Description | +| ----------- | ------- | -------------------------- | +| 80 | Nginx | Web server and admin panel | +| 8118 | Privoxy | HTTP proxy | +| 9040 | Tor | Transparent proxy | +| 9050 | Tor | SOCKS proxy | +| 9053 | Unbound | DNS resolver | +| 9080 | Tor | HTTP tunnel | +| 57000-57010 | Tor | Bridge/Relay ports | + +## ๐Ÿ“ Directory Structure + +- `/config` - Configuration files (persistent) +- `/data` - Data files and logs (persistent) +- `/data/logs` - Service logs +- `/data/tor/server/services` - Hidden service data +- `/data/htdocs` - Web content for hidden services + +## โš™๏ธ Environment Variables + +### Admin Panel + +- `TOR_ADMIN_USER` - Admin username (default: admin) +- `TOR_ADMIN_PASS` - Admin password (default: torpass123) +- `TOR_JWT_SECRET` - JWT signing secret (auto-generated if not set) + +### Tor Configuration + +- `TOR_BRIDGE_ENABLED` - Enable bridge mode (default: yes) +- `TOR_RELAY_ENABLED` - Enable relay mode (default: yes) +- `TOR_HIDDEN_ENABLED` - Enable hidden services (default: yes) +- `TOR_DNS_ENABLED` - Enable DNS forwarding (default: yes) +- `TOR_DEBUG` - Enable debug logging (default: no) + +### Service Ports + +- `TOR_BRIDGE_PT_PORT` - Bridge transport port (default: 57003) +- `TOR_RELAY_PORT` - Relay transport port (default: 57000) +- `TOR_SERVER_ACCOUNT_MAX` - Monthly bandwidth limit (default: 250 GBytes) + +## ๐Ÿ”ง Development + +### Build with Custom Options + +```shell +docker build \ + --build-arg BUILD_DATE=$(date +%Y%m%d%H%M) \ + --build-arg PHP_VERSION=84 \ + -t tor-custom . ``` ## Authors diff --git a/rootfs/tmp/etc/nginx/nginx.conf b/rootfs/tmp/etc/nginx/nginx.conf index 4df2e00..d8d8532 100644 --- a/rootfs/tmp/etc/nginx/nginx.conf +++ b/rootfs/tmp/etc/nginx/nginx.conf @@ -92,6 +92,31 @@ http { stub_status; } + # Admin panel access + location ^~ /admin { + alias /usr/local/share/webpanel; + index index.php; + + # REST API routing + location ^~ /admin/api/ { + try_files $uri $uri/ @api; + } + + location @api { + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME /usr/local/share/webpanel/api/index.php; + fastcgi_param REQUEST_URI $request_uri; + include fastcgi_params; + } + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /usr/local/share/webpanel$fastcgi_script_name; + include fastcgi_params; + } + } + location ~ [^/]\.php(/|$) { fastcgi_split_path_info ^(.+?\.php)(/.*)$; if (!-f $document_root$fastcgi_script_name) { diff --git a/rootfs/tmp/etc/nginx/vhosts.d/admin.conf b/rootfs/tmp/etc/nginx/vhosts.d/admin.conf new file mode 100644 index 0000000..af07627 --- /dev/null +++ b/rootfs/tmp/etc/nginx/vhosts.d/admin.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name admin.tor localhost; + root /usr/local/share/webpanel; + index index.php; + + access_log /data/logs/nginx/admin.access.log; + error_log /data/logs/nginx/admin.error.log; + + # Security headers + add_header X-Frame-Options "DENY" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # PHP processing + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + # Static files + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Deny access to sensitive files + location ~ /\.(ht|git) { + deny all; + } + + location ~ \.(conf|log|txt)$ { + deny all; + } +} \ No newline at end of file diff --git a/rootfs/usr/local/bin/entrypoint.sh b/rootfs/usr/local/bin/entrypoint.sh index 2371ee9..070020c 100755 --- a/rootfs/usr/local/bin/entrypoint.sh +++ b/rootfs/usr/local/bin/entrypoint.sh @@ -88,7 +88,7 @@ SERVER_PORTS="" # specifiy other ports # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Healthcheck variables HEALTH_ENABLED="yes" # enable healthcheck [yes/no] -SERVICES_LIST="tini,tor-bridge,tor-relay,tor-server,unbound,privoxy,zz-nginx" +SERVICES_LIST="tini,tor-bridge,tor-relay,tor-server,unbound,privoxy,php-fpm,zz-nginx" HEALTH_ENDPOINTS="" # url endpoints: [http://localhost/health,http://localhost/test] # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Update path var diff --git a/rootfs/usr/local/etc/docker/init.d/99-php-fpm.sh b/rootfs/usr/local/etc/docker/init.d/99-php-fpm.sh new file mode 100755 index 0000000..d1bac88 --- /dev/null +++ b/rootfs/usr/local/etc/docker/init.d/99-php-fpm.sh @@ -0,0 +1,702 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +##@Version : 202509200348-git +# @@Author : Jason Hempstead +# @@Contact : jason@casjaysdev.pro +# @@License : LICENSE.md +# @@ReadME : 99-php-fpm.sh --help +# @@Copyright : Copyright: (c) 2025 Jason Hempstead, Casjays Developments +# @@Created : Saturday, Sep 20, 2025 03:48 EDT +# @@File : 99-php-fpm.sh +# @@Description : +# @@Changelog : New script +# @@TODO : Better documentation +# @@Other : +# @@Resource : +# @@Terminal App : no +# @@sudo/root : no +# @@Template : other/start-service +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2120,SC2155,SC2199,SC2317,SC2329 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +set -e +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# run trap command on exit +trap 'retVal=$?; echo "โŒ Fatal error occurred: Exit code $retVal at line $LINENO in command: $BASH_COMMAND"; kill -TERM 1' ERR +trap 'retVal=$?;[ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE";exit $retVal' SIGINT SIGTERM SIGPWR +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SCRIPT_FILE="$0" +SERVICE_NAME="php-fpm" +SCRIPT_NAME="$(basename -- "$SCRIPT_FILE" 2>/dev/null)" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Exit if service is disabled +[ -z "$PHP_FPM_APPNAME_ENABLED" ] || if [ "$PHP_FPM_APPNAME_ENABLED" != "yes" ]; then export SERVICE_DISABLED="$SERVICE_NAME" && exit 0; fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# setup debugging - https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +[ -f "/config/.debug" ] && [ -z "$DEBUGGER_OPTIONS" ] && export DEBUGGER_OPTIONS="$(<"/config/.debug")" || DEBUGGER_OPTIONS="${DEBUGGER_OPTIONS:-}" +{ [ "$DEBUGGER" = "on" ] || [ -f "/config/.debug" ]; } && echo "Enabling debugging" && set -xo pipefail -x$DEBUGGER_OPTIONS && export DEBUGGER="on" || set -o pipefail +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +export PATH="/usr/local/etc/docker/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# import the functions file +if [ -f "/usr/local/etc/docker/functions/entrypoint.sh" ]; then + . "/usr/local/etc/docker/functions/entrypoint.sh" +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# import variables +for set_env in "/root/env.sh" "/usr/local/etc/docker/env"/*.sh "/config/env"/*.sh; do + [ -f "$set_env" ] && . "$set_env" +done +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# exit if __start_init_scripts function hasn't been Initialized +if [ ! -f "/run/__start_init_scripts.pid" ]; then + echo "__start_init_scripts function hasn't been Initialized" >&2 + SERVICE_IS_RUNNING="no" + exit 1 +fi +# Clean up any stale PID file for this service on startup +if [ -n "$SERVICE_NAME" ] && [ -f "/run/init.d/$SERVICE_NAME.pid" ]; then + old_pid=$(cat "/run/init.d/$SERVICE_NAME.pid" 2>/dev/null) + if [ -n "$old_pid" ] && ! kill -0 "$old_pid" 2>/dev/null; then + echo "๐Ÿงน Removing stale PID file for $SERVICE_NAME" + rm -f "/run/init.d/$SERVICE_NAME.pid" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom functions + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Script to execute +START_SCRIPT="/usr/local/etc/docker/exec/$SERVICE_NAME" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Reset environment before executing service +RESET_ENV="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set webroot +WWW_ROOT_DIR="/usr/local/share/httpd/default" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Default predefined variables +DATA_DIR="/data/php-fpm" # set data directory +CONF_DIR="/config/php-fpm" # set config directory +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# set the containers etc directory +ETC_DIR="/etc/php-fpm" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# set the var dir +VAR_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TMP_DIR="/tmp/php-fpm" # set the temp dir +RUN_DIR="/run/php-fpm" # set scripts pid dir +LOG_DIR="/data/logs/php-fpm" # set log directory +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the working dir +WORK_DIR="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# port which service is listening on +SERVICE_PORT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# User to use to launch service - IE: postgres +RUNAS_USER="root" # normally root +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# User and group in which the service switches to - IE: nginx,apache,mysql,postgres +#SERVICE_USER="php-fpm" # execute command as another user +#SERVICE_GROUP="php-fpm" # Set the service group +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set password length +RANDOM_PASS_USER="" +RANDOM_PASS_ROOT="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set user and group ID +SERVICE_UID="0" # set the user id +SERVICE_GID="0" # set the group id +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# execute command variables - keep single quotes variables will be expanded later +EXEC_CMD_BIN='php-fpm84' # command to execute +EXEC_CMD_ARGS='--nodaemonize --fpm-config /etc/php84/php-fpm.conf' # command arguments +EXEC_PRE_SCRIPT='' # execute script before +SERVICE_USES_PID='' # Set to no if the service is not running otherwise leave blank +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a web server +IS_WEB_SERVER="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Is this service a database server +IS_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Does this service use a database server +USES_DATABASE_SERVICE="no" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set defualt type - [custom,sqlite,redis,postgres,mariadb,mysql,couchdb,mongodb,supabase] +DATABASE_SERVICE_TYPE="sqlite" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Show message before execute +PRE_EXEC_MESSAGE="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set the wait time to execute __post_execute function - minutes +POST_EXECUTE_WAIT_TIME="1" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Update path var +PATH="$PATH:." +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Lets get containers ip address +IP4_ADDRESS="$(__get_ip4)" +IP6_ADDRESS="$(__get_ip6)" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Where to save passwords to +ROOT_FILE_PREFIX="/config/secure/auth/root" # directory to save username/password for root user +USER_FILE_PREFIX="/config/secure/auth/user" # directory to save username/password for normal user +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info password/random] +root_user_name="${PHP_FPM_ROOT_USER_NAME:-}" # root user name +root_user_pass="${PHP_FPM_ROOT_PASS_WORD:-}" # root user password +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Normal user info [password/random] +user_name="${PHP_FPM_USER_NAME:-}" # normal user name +user_pass="${PHP_FPM_USER_PASS_WORD:-}" # normal user password +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Load variables from config +[ -f "/config/env/php-fpm.script.sh" ] && . "/config/env/php-fpm.script.sh" # Generated by my dockermgr script +[ -f "/config/env/php-fpm.sh" ] && . "/config/env/php-fpm.sh" # Overwrite the variabes +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional predefined variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional variables + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Specifiy custom directories to be created +ADD_APPLICATION_FILES="" +ADD_APPLICATION_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +APPLICATION_FILES="$LOG_DIR/$SERVICE_NAME.log" +APPLICATION_DIRS="$ETC_DIR $CONF_DIR $LOG_DIR $TMP_DIR $RUN_DIR $VAR_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Additional config dirs - will be Copied to /etc/$name +ADDITIONAL_CONFIG_DIRS="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# define variables that need to be loaded into the service - escape quotes - var=\"value\",other=\"test\" +CMD_ENV="" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Overwrite based on file/directory + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Per Application Variables or imports + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom commands to run before copying to /config +__run_precopy() { + # Define environment + local hostname=${HOSTNAME} + [ -d "/run/healthcheck" ] || mkdir -p "/run/healthcheck" + # Define actions/commands + + # allow custom functions + if builtin type -t __run_precopy_local | grep -q 'function'; then __run_precopy_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Custom prerun functions - IE setup WWW_ROOT_DIR +__execute_prerun() { + # Define environment + local hostname=${HOSTNAME} + # Define actions/commands + + # allow custom functions + if builtin type -t __execute_prerun_local | grep -q 'function'; then __execute_prerun_local; fi +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Run any pre-execution checks +__run_pre_execute_checks() { + # Set variables + local exitStatus=0 + local pre_execute_checks_MessageST="Running preexecute check for $SERVICE_NAME" # message to show at start + local pre_execute_checks_MessageEnd="Finished preexecute check for $SERVICE_NAME" # message to show at completion + __banner "$pre_execute_checks_MessageST" + # Put command to execute in parentheses + { + true + } + exitStatus=$? + __banner "$pre_execute_checks_MessageEnd: Status $exitStatus" + + # show exit message + if [ $exitStatus -ne 0 ]; then + echo "The pre-execution check has failed" >&2 + [ -f "$SERVICE_PID_FILE" ] && rm -Rf "$SERVICE_PID_FILE" + exit 1 + fi + # allow custom functions + if builtin type -t __run_pre_execute_checks_local | grep -q 'function'; then __run_pre_execute_checks_local; fi + # exit function + return $exitStatus +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__update_conf_files() { + local exitCode=0 # default exit code + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # delete files + #__rm "" + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # custom commands + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # replace variables + # __replace "" "" "$CONF_DIR/php-fpm.conf" + # replace variables recursively + # __find_replace "" "" "$CONF_DIR" + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions + + # allow custom functions + if builtin type -t __update_conf_files_local | grep -q 'function'; then __update_conf_files_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run before executing +__pre_execute() { + local exitCode=0 # default exit code + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname + # execute if directories is empty + # __is_dir_empty "$CONF_DIR" && true + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # define actions to run after copying to /config + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # unset unneeded variables + unset sysname + # Lets wait a few seconds before continuing + sleep 5 + # allow custom functions + if builtin type -t __pre_execute_local | grep -q 'function'; then __pre_execute_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# function to run after executing +__post_execute() { + local pid="" # init pid var + local retVal=0 # set default exit code + local ctime=${POST_EXECUTE_WAIT_TIME:-1} # how long to wait before executing + local waitTime=$((ctime * 60)) # convert minutes to seconds + local postMessageST="Running post commands for $SERVICE_NAME" # message to show at start + local postMessageEnd="Finished post commands for $SERVICE_NAME" # message to show at completion + # wait + sleep $waitTime + # execute commands after waiting + ( + # show message + __banner "$postMessageST" + # commands to execute + sleep 5 + # show exit message + __banner "$postMessageEnd: Status $retVal" + ) 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & + pid=$! + ps ax | awk '{print $1}' | grep -v grep | grep -q "$execPid$" && retVal=0 || retVal=10 + # allow custom functions + if builtin type -t __post_execute_local | grep -q 'function'; then __post_execute_local; fi + # exit function + return $retVal +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to update config files - IE: change port +__pre_message() { + local exitCode=0 + [ -n "$PRE_EXEC_MESSAGE" ] && eval echo "$PRE_EXEC_MESSAGE" + # execute commands + + # allow custom functions + if builtin type -t __pre_message_local | grep -q 'function'; then __pre_message_local; fi + # exit function + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# use this function to setup ssl support +__update_ssl_conf() { + local exitCode=0 + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname + # execute commands + + # allow custom functions + if builtin type -t __update_ssl_conf_local | grep -q 'function'; then __update_ssl_conf_local; fi + # set exitCode + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env() { + local exitCode=0 + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ]; then + cat </dev/null +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# root/admin user info [password/random] +#ENV_ROOT_USER_NAME="${ENV_ROOT_USER_NAME:-$PHP_FPM_ROOT_USER_NAME}" # root user name +#ENV_ROOT_USER_PASS="${ENV_ROOT_USER_NAME:-$PHP_FPM_ROOT_PASS_WORD}" # root user password +#root_user_name="${ENV_ROOT_USER_NAME:-$root_user_name}" # +#root_user_pass="${ENV_ROOT_USER_PASS:-$root_user_pass}" # +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#Normal user info [password/random] +#ENV_USER_NAME="${ENV_USER_NAME:-$PHP_FPM_USER_NAME}" # +#ENV_USER_PASS="${ENV_USER_PASS:-$PHP_FPM_USER_PASS_WORD}" # +#user_name="${ENV_USER_NAME:-$user_name}" # normal user name +#user_pass="${ENV_USER_PASS:-$user_pass}" # normal user password + +EOF + fi + if [ ! -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" ]; then + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __run_precopy_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __execute_prerun_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __run_pre_execute_checks_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __update_conf_files_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __pre_execute_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __post_execute_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __pre_message_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + __update_ssl_conf_local() { true; } + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + fi + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" || exitCode=$((exitCode + 1)) + __file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" || exitCode=$((exitCode + 1)) + return $exitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# script to start server +__run_start_script() { + local runExitCode=0 + local workdir="$(eval echo "${WORK_DIR:-}")" # expand variables + local cmd="$(eval echo "${EXEC_CMD_BIN:-}")" # expand variables + local args="$(eval echo "${EXEC_CMD_ARGS:-}")" # expand variables + local name="$(eval echo "${EXEC_CMD_NAME:-}")" # expand variables + local pre="$(eval echo "${EXEC_PRE_SCRIPT:-}")" # expand variables + local extra_env="$(eval echo "${CMD_ENV//,/ }")" # expand variables + local lc_type="$(eval echo "${LANG:-${LC_ALL:-$LC_CTYPE}}")" # expand variables + local home="$(eval echo "${workdir//\/root/\/tmp\/docker}")" # expand variables + local path="$(eval echo "$PATH")" # expand variables + local message="$(eval echo "")" # expand variables + local sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" # set hostname + [ -f "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" ] && . "$CONF_DIR/$SERVICE_NAME.exec_cmd.sh" + # + if [ -z "$cmd" ]; then + __post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" + retVal=$? + echo "Initializing $SCRIPT_NAME has completed" + exit $retVal + else + # ensure the command exists + if [ ! -x "$cmd" ]; then + echo "$name is not a valid executable" + return 2 + fi + # check and exit if already running + if __proc_check "$name" || __proc_check "$cmd"; then + echo "$name is already running" >&2 + return 0 + else + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # show message if env exists + if [ -n "$cmd" ]; then + [ -n "$SERVICE_USER" ] && echo "Setting up $cmd to run as $SERVICE_USER" || SERVICE_USER="root" + [ -n "$SERVICE_PORT" ] && echo "$name will be running on port $SERVICE_PORT" || SERVICE_PORT="" + fi + if [ -n "$pre" ] && [ -n "$(command -v "$pre" 2>/dev/null)" ]; then + export cmd_exec="$pre $cmd $args" + message="Starting service: $name $args through $pre" + else + export cmd_exec="$cmd $args" + message="Starting service: $name $args" + fi + [ -n "$su_exec" ] && echo "using $su_exec" | tee -a -p "/data/logs/init.txt" + echo "$message" | tee -a -p "/data/logs/init.txt" + su_cmd touch "$SERVICE_PID_FILE" + if [ "$RESET_ENV" = "yes" ]; then + env_command="$(echo "env -i HOME=\"$home\" LC_CTYPE=\"$lc_type\" PATH=\"$path\" HOSTNAME=\"$sysname\" USER=\"${SERVICE_USER:-$RUNAS_USER}\" $extra_env")" + execute_command="$(__trim "$su_exec $env_command $cmd_exec")" + if [ ! -f "$START_SCRIPT" ]; then + cat <"$START_SCRIPT" +#!/usr/bin/env bash +trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f "\$SERVICE_PID_FILE" ] && rm -Rf "\$SERVICE_PID_FILE";exit \$exitCode' EXIT +# +set -Eeo pipefail +# Setting up $cmd to run as ${SERVICE_USER:-root} with env +retVal=10 +cmd="$cmd" +SERVICE_NAME="$SERVICE_NAME" +SERVICE_PID_FILE="$SERVICE_PID_FILE" +$execute_command 2>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" & +execPid=\$! +sleep 2 +checkPID="\$(ps ax | awk '{print \$1}' | grep -v grep | grep "\$execPid$" || false)" +[ -n "\$execPid" ] && [ -n "\$checkPID" ] && echo "\$execPid" >"\$SERVICE_PID_FILE" && retVal=0 || retVal=10 +[ "\$retVal" = 0 ] && echo "\$cmd has been started" && printf '%s\n' "\$SERVICE_NAME: \$execPid" >"/run/healthcheck/\$SERVICE_NAME" || echo "Failed to start $execute_command" >&2 +exit \$retVal + +EOF + fi + else + if [ ! -f "$START_SCRIPT" ]; then + execute_command="$(__trim "$su_exec $cmd_exec")" + cat <"$START_SCRIPT" +#!/usr/bin/env bash +trap 'exitCode=\$?;[ \$exitCode -ne 0 ] && [ -f "\$SERVICE_PID_FILE" ] && rm -Rf "\$SERVICE_PID_FILE";exit \$exitCode' EXIT +# +set -Eeo pipefail +# Setting up $cmd to run as ${SERVICE_USER:-root} +retVal=10 +cmd="$cmd" +SERVICE_NAME="$SERVICE_NAME" +SERVICE_PID_FILE="$SERVICE_PID_FILE" +$execute_command 2>>"/dev/stderr" >>"$LOG_DIR/$SERVICE_NAME.log" & +execPid=\$! +sleep 2 +checkPID="\$(ps ax | awk '{print \$1}' | grep -v grep | grep "\$execPid$" || false)" +[ -n "\$execPid" ] && [ -n "\$checkPID" ] && echo "\$execPid" >"\$SERVICE_PID_FILE" && retVal=0 || retVal=10 +[ "\$retVal" = 0 ] && echo "\$cmd has been started" || echo "Failed to start $execute_command" >&2 >&2 +exit \$retVal + +EOF + fi + fi + fi + [ -x "$START_SCRIPT" ] || chmod 755 -Rf "$START_SCRIPT" + [ "$CONTAINER_INIT" = "yes" ] || eval sh -c "$START_SCRIPT" + runExitCode=$? + fi + return $runExitCode +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# username and password actions +__run_secure_function() { + local filesperms + if [ -n "$user_name" ] || [ -n "$user_pass" ]; then + for filesperms in "${USER_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + if [ -n "$root_user_name" ] || [ -n "$root_user_pass" ]; then + for filesperms in "${ROOT_FILE_PREFIX}"/*; do + if [ -e "$filesperms" ]; then + chmod -Rf 600 "$filesperms" + chown -Rf $SERVICE_USER:$SERVICE_USER "$filesperms" 2>/dev/null + fi + done 2>/dev/null | tee -p -a "/data/logs/init.txt" + fi + unset filesperms +} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow ENV_ variable - Import env file +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +__file_exists_with_content "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.local.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SERVICE_EXIT_CODE=0 # default exit code +# application specific +EXEC_CMD_NAME="$(basename -- "$EXEC_CMD_BIN")" # set the binary name +SERVICE_PID_FILE="/run/init.d/$EXEC_CMD_NAME.pid" # set the pid file location +SERVICE_PID_NUMBER="$(__pgrep)" # check if running +EXEC_CMD_BIN="$(type -P "$EXEC_CMD_BIN" || echo "$EXEC_CMD_BIN")" # set full path +EXEC_PRE_SCRIPT="$(type -P "$EXEC_PRE_SCRIPT" || echo "$EXEC_PRE_SCRIPT")" # set full path +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Only run check +__check_service "$1" && SERVICE_IS_RUNNING=yes +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# ensure needed directories exists +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# create auth directories +[ -n "$USER_FILE_PREFIX" ] && { [ -d "$USER_FILE_PREFIX" ] || mkdir -p "$USER_FILE_PREFIX"; } +[ -n "$ROOT_FILE_PREFIX" ] && { [ -d "$ROOT_FILE_PREFIX" ] || mkdir -p "$ROOT_FILE_PREFIX"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[ -n "$RUNAS_USER" ] || RUNAS_USER="root" +[ -n "$SERVICE_USER" ] || SERVICE_USER="$RUNAS_USER" +[ -n "$SERVICE_GROUP" ] || SERVICE_GROUP="${SERVICE_USER:-$RUNAS_USER}" +[ "$IS_WEB_SERVER" = "yes" ] && RESET_ENV="yes" && __is_htdocs_mounted +[ "$IS_WEB_SERVER" = "yes" ] && [ -z "$SERVICE_PORT" ] && SERVICE_PORT="80" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Database env +if [ "$IS_DATABASE_SERVICE" = "yes" ] || [ "$USES_DATABASE_SERVICE" = "yes" ]; then + RESET_ENV="no" + DATABASE_CREATE="${ENV_DATABASE_CREATE:-$DATABASE_CREATE}" + DATABASE_USER_NORMAL="${ENV_DATABASE_USER:-${DATABASE_USER_NORMAL:-$user_name}}" + DATABASE_PASS_NORMAL="${ENV_DATABASE_PASSWORD:-${DATABASE_PASS_NORMAL:-$user_pass}}" + DATABASE_USER_ROOT="${ENV_DATABASE_ROOT_USER:-${DATABASE_USER_ROOT:-$root_user_name}}" + DATABASE_PASS_ROOT="${ENV_DATABASE_ROOT_PASSWORD:-${DATABASE_PASS_ROOT:-$root_user_pass}}" + if [ -n "$DATABASE_PASS_NORMAL" ] && [ ! -f "${USER_FILE_PREFIX}/db_pass_user" ]; then + echo "$DATABASE_PASS_NORMAL" >"${USER_FILE_PREFIX}/db_pass_user" + fi + if [ -n "$DATABASE_PASS_ROOT" ] && [ ! -f "${ROOT_FILE_PREFIX}/db_pass_root" ]; then + echo "$DATABASE_PASS_ROOT" >"${ROOT_FILE_PREFIX}/db_pass_root" + fi +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# [DATABASE_DIR_[SQLITE,REDIS,POSTGRES,MARIADB,COUCHDB,MONGODB,SUPABASE]] +if [ "$DATABASE_SERVICE_TYPE" = "custom" ]; then + DATABASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_BASE_DIR="${DATABASE_DIR_CUSTOM:-/data/db/custom}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_CUSTOM:-/usr/local/share/httpd/admin/databases}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_CUSTOM:-/admin/dbadmin}" +elif [ "$SERVICE_NAME" = "redis" ] || [ "$DATABASE_SERVICE_TYPE" = "redis" ]; then + DATABASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_BASE_DIR="${DATABASE_DIR_REDIS:-/data/db/redis}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_REDIS:-/usr/local/share/httpd/admin/redis}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_REDIS:-/admin/redis}" +elif [ "$SERVICE_NAME" = "postgres" ] || [ "$DATABASE_SERVICE_TYPE" = "postgres" ]; then + DATABASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_BASE_DIR="${DATABASE_DIR_POSTGRES:-/data/db/postgres}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_POSTGRES:-/usr/local/share/httpd/admin/postgres}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_POSTGRES:-/admin/postgres}" +elif [ "$SERVICE_NAME" = "mariadb" ] || [ "$DATABASE_SERVICE_TYPE" = "mariadb" ]; then + DATABASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MARIADB:-/data/db/mariadb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MARIADB:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MARIADB:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "mysql" ] || [ "$DATABASE_SERVICE_TYPE" = "mysql" ]; then + DATABASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_BASE_DIR="${DATABASE_DIR_MYSQL:-/data/db/mysql}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MYSQL:-/usr/local/share/httpd/admin/mysql}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MYSQL:-/admin/mysql}" +elif [ "$SERVICE_NAME" = "couchdb" ] || [ "$DATABASE_SERVICE_TYPE" = "couchdb" ]; then + DATABASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_BASE_DIR="${DATABASE_DIR_COUCHDB:-/data/db/couchdb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_COUCHDB:-/usr/local/share/httpd/admin/couchdb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_COUCHDB:-/admin/couchdb}" +elif [ "$SERVICE_NAME" = "mongodb" ] || [ "$DATABASE_SERVICE_TYPE" = "mongodb" ]; then + DATABASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_BASE_DIR="${DATABASE_DIR_MONGODB:-/data/db/mongodb}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_MONGODB:-/usr/local/share/httpd/admin/mongodb}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_MONGODB:-/admin/mongodb}" +elif [ "$SERVICE_NAME" = "supabase" ] || [ "$DATABASE_SERVICE_TYPE" = "supabase" ]; then + DATABASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_BASE_DIR="${DATABASE_DIR_SUPABASE:-/data/db/supabase}" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SUPABASE:-/usr/local/share/httpd/admin/supabase}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SUPBASE:-/admin/supabase}" +elif [ "$SERVICE_NAME" = "sqlite" ] || [ "$DATABASE_SERVICE_TYPE" = "sqlite" ]; then + DATABASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_BASE_DIR="${DATABASE_DIR_SQLITE:-/data/db/sqlite}/$SERVER_NAME" + DATABASE_ADMIN_WWW_ROOT="${DATABASE_ADMIN_WWW_ROOT_SQLITE:-/usr/local/share/httpd/admin/sqlite}" + [ -d "$DATABASE_ADMIN_WWW_ROOT" ] && SERVER_ADMIN_URL="${SERVER_ADMIN_URL_SQLITE:-/admin/sqlite}" + [ -d "$DATABASE_DIR" ] || mkdir -p "$DATABASE_DIR" + chmod 777 "$DATABASE_DIR" +fi +[ -n "$DATABASE_ADMIN_WWW_ROOT" ] && { [ ! -d "$DATABASE_ADMIN_WWW_ROOT" ] || mkdir -p "${DATABASE_ADMIN_WWW_ROOT}"; } +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow variables via imports - Overwrite existing +[ -f "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" ] && . "/config/env/${SERVICE_NAME:-$SCRIPT_NAME}.sh" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# set password to random if variable is random +[ "$user_pass" = "random" ] && user_pass="$(__random_password ${RANDOM_PASS_USER:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[ "$root_user_pass" = "random" ] && root_user_pass="$(__random_password ${RANDOM_PASS_ROOT:-16})" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow setting initial users and passwords via environment and save to file +[ -n "$user_name" ] && echo "$user_name" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$user_pass" ] && echo "$user_pass" >"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" +[ -n "$root_user_name" ] && echo "$root_user_name" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" +[ -n "$root_user_pass" ] && echo "$root_user_pass" >"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# create needed dirs +[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR" +[ -d "$RUN_DIR" ] || mkdir -p "$RUN_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow per init script usernames and passwords +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_name" && user_name="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${USER_FILE_PREFIX}/${SERVICE_NAME}_pass" && user_pass="$(<"${USER_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name" && root_user_name="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_name")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass" && root_user_pass="$(<"${ROOT_FILE_PREFIX}/${SERVICE_NAME}_pass")" +__file_exists_with_content "${USER_FILE_PREFIX}/db_pass_user" && DATABASE_PASS_NORMAL="$(<"${USER_FILE_PREFIX}/db_pass_user")" +__file_exists_with_content "${ROOT_FILE_PREFIX}/db_pass_root" && DATABASE_PASS_ROOT="$(<"${ROOT_FILE_PREFIX}/db_pass_root")" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# set hostname for script +sysname="${SERVER_NAME:-${FULL_DOMAIN_NAME:-$HOSTNAME}}" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +__create_service_env +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Setup /config directories +__init_config_etc +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# pre-run function +__execute_prerun +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# create user if needed +__create_service_user "$SERVICE_USER" "$SERVICE_GROUP" "${WORK_DIR:-/home/$SERVICE_USER}" "${SERVICE_UID:-}" "${SERVICE_GID:-}" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Modify user if needed +__set_user_group_id $SERVICE_USER ${SERVICE_UID:-} ${SERVICE_GID:-} +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Create base directories +__setup_directories +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# set switch user command +__switch_to_user +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize the home/working dir +__init_working_dir +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# show init message +__pre_message +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_db_users +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Initialize ssl +__update_ssl_conf +__update_ssl_certs +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions in ${USER_FILE_PREFIX} and ${ROOT_FILE_PREFIX} +__run_secure_function +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +__run_precopy +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Copy /config to /etc +for config_2_etc in $CONF_DIR $ADDITIONAL_CONFIG_DIRS; do + __initialize_system_etc "$config_2_etc" 2>/dev/stderr | tee -p -a "/data/logs/init.txt" +done +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Replace variables +__initialize_replace_variables "$ETC_DIR" "$CONF_DIR" "$ADDITIONAL_CONFIG_DIRS" "$WWW_ROOT_DIR" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +__initialize_database +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Updating config files +__update_conf_files +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# run the pre execute commands +__pre_execute +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Set permissions +__fix_permissions "$SERVICE_USER" "$SERVICE_GROUP" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +__run_pre_execute_checks 2>/dev/stderr | tee -a -p "/data/logs/entrypoint.log" "/data/logs/init.txt" || return 20 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +__run_start_script 2>>/dev/stderr | tee -p -a "/data/logs/entrypoint.log" +errorCode=$? +if [ -n "$EXEC_CMD_BIN" ]; then + if [ "$errorCode" -eq 0 ]; then + SERVICE_EXIT_CODE=0 + SERVICE_IS_RUNNING="yes" + else + SERVICE_EXIT_CODE=$errorCode + SERVICE_IS_RUNNING="${SERVICE_IS_RUNNING:-no}" + [ -s "$SERVICE_PID_FILE" ] || rm -Rf "$SERVICE_PID_FILE" + fi + SERVICE_EXIT_CODE=0 +fi +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# start the post execute function in background +__post_execute 2>"/dev/stderr" | tee -p -a "/data/logs/init.txt" & +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +exit $SERVICE_EXIT_CODE diff --git a/rootfs/usr/local/share/webpanel/.htaccess b/rootfs/usr/local/share/webpanel/.htaccess new file mode 100644 index 0000000..0ec5351 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/.htaccess @@ -0,0 +1,21 @@ +# Security settings + + Require all denied + + + + Require all denied + + + + Require all denied + + + + Require all denied + + +# PHP settings +php_value session.cookie_httponly 1 +php_value session.cookie_secure 0 +php_value session.use_strict_mode 1 \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/api.php b/rootfs/usr/local/share/webpanel/api.php new file mode 100644 index 0000000..9f289af --- /dev/null +++ b/rootfs/usr/local/share/webpanel/api.php @@ -0,0 +1,125 @@ + 'Authentication required. Use Bearer token or login session.']); + exit; +} + +$action = $_GET['action'] ?? ''; + +switch ($action) { + case 'login': + // Token-based login endpoint + $username = $_POST['username'] ?? ''; + $password = $_POST['password'] ?? ''; + + if (authenticate($username, $password)) { + $token = generateApiToken($username); + echo json_encode([ + 'success' => true, + 'token' => $token, + 'expires_in' => 86400 // 24 hours + ]); + } else { + http_response_code(401); + echo json_encode(['error' => 'Invalid credentials']); + } + break; + + case 'status': + $services = [ + 'tor-bridge' => 'Tor Bridge', + 'tor-relay' => 'Tor Relay', + 'tor-server' => 'Tor Server', + 'unbound' => 'DNS Resolver', + 'privoxy' => 'HTTP Proxy', + 'nginx' => 'Web Server' + ]; + + $status = []; + foreach ($services as $service => $name) { + $status[$service] = [ + 'name' => $name, + 'running' => getServiceStatus($service), + 'pid' => getServicePid($service) + ]; + } + + echo json_encode($status); + break; + + case 'stats': + echo json_encode(getSystemStats()); + break; + + case 'hidden-services': + echo json_encode(getHiddenServices()); + break; + + case 'service-control': + $service = $_POST['service'] ?? ''; + $command = $_POST['command'] ?? ''; + + $valid_services = ['tor-bridge', 'tor-relay', 'tor-server', 'unbound', 'privoxy', 'nginx']; + $valid_commands = ['start', 'stop', 'restart']; + + if (!in_array($service, $valid_services)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid service']); + break; + } + + if (!in_array($command, $valid_commands)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid command']); + break; + } + + $success = false; + switch ($command) { + case 'start': + $success = startService($service); + break; + case 'stop': + $success = stopService($service); + break; + case 'restart': + $success = restartService($service); + break; + } + + echo json_encode([ + 'success' => $success, + 'message' => $success ? + "Successfully {$command}ed $service" : + "Failed to $command $service" + ]); + break; + + default: + http_response_code(400); + echo json_encode(['error' => 'Invalid action']); +} +?> \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/api/index.php b/rootfs/usr/local/share/webpanel/api/index.php new file mode 100644 index 0000000..d861c0d --- /dev/null +++ b/rootfs/usr/local/share/webpanel/api/index.php @@ -0,0 +1,235 @@ + true, + 'token' => $token, + 'expires_in' => 86400 + ]); + } else { + http_response_code(401); + echo json_encode(['error' => 'Invalid credentials']); + } + exit; +} + +// Require authentication for all other endpoints +if (!$authenticated) { + http_response_code(401); + echo json_encode(['error' => 'Authentication required']); + exit; +} + +// Protected endpoints +switch ($method) { + case 'GET': + handleGetRequest($path); + break; + case 'POST': + handlePostRequest($path); + break; + case 'PUT': + handlePutRequest($path); + break; + case 'DELETE': + handleDeleteRequest($path); + break; + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); +} + +function handleGetRequest($path) { + switch ($path) { + case 'services': + case 'services/status': + $services = [ + 'tor-bridge' => 'Tor Bridge', + 'tor-relay' => 'Tor Relay', + 'tor-server' => 'Tor Server', + 'unbound' => 'DNS Resolver', + 'privoxy' => 'HTTP Proxy', + 'nginx' => 'Web Server' + ]; + + $status = []; + foreach ($services as $service => $name) { + $status[] = [ + 'id' => $service, + 'name' => $name, + 'running' => getServiceStatus($service), + 'pid' => getServicePid($service) + ]; + } + + echo json_encode(['data' => $status]); + break; + + case 'system/stats': + echo json_encode(['data' => getSystemStats()]); + break; + + case 'hidden-services': + echo json_encode(['data' => getHiddenServices()]); + break; + + default: + if (preg_match('/^services\/([^\/]+)$/', $path, $matches)) { + $service = $matches[1]; + $valid_services = ['tor-bridge', 'tor-relay', 'tor-server', 'unbound', 'privoxy', 'nginx']; + + if (in_array($service, $valid_services)) { + echo json_encode([ + 'data' => [ + 'id' => $service, + 'running' => getServiceStatus($service), + 'pid' => getServicePid($service) + ] + ]); + } else { + http_response_code(404); + echo json_encode(['error' => 'Service not found']); + } + } else { + http_response_code(404); + echo json_encode(['error' => 'Endpoint not found']); + } + } +} + +function handlePostRequest($path) { + $input = json_decode(file_get_contents('php://input'), true); + + switch ($path) { + case 'hidden-services': + $name = $input['name'] ?? ''; + $port_mapping = $input['port_mapping'] ?? ''; + + if (empty($name) || empty($port_mapping)) { + http_response_code(400); + echo json_encode(['error' => 'Name and port_mapping required']); + return; + } + + if (!validateServiceName($name) || !validatePortMapping($port_mapping)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid name or port mapping format']); + return; + } + + if (createHiddenService($name, $port_mapping)) { + echo json_encode(['success' => true, 'message' => 'Hidden service created']); + } else { + http_response_code(500); + echo json_encode(['error' => 'Failed to create hidden service']); + } + break; + + default: + if (preg_match('/^services\/([^\/]+)\/([^\/]+)$/', $path, $matches)) { + $service = $matches[1]; + $action = $matches[2]; + + $valid_services = ['tor-bridge', 'tor-relay', 'tor-server', 'unbound', 'privoxy', 'nginx']; + $valid_actions = ['start', 'stop', 'restart']; + + if (!in_array($service, $valid_services)) { + http_response_code(404); + echo json_encode(['error' => 'Service not found']); + return; + } + + if (!in_array($action, $valid_actions)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid action']); + return; + } + + $success = false; + switch ($action) { + case 'start': + $success = startService($service); + break; + case 'stop': + $success = stopService($service); + break; + case 'restart': + $success = restartService($service); + break; + } + + echo json_encode([ + 'success' => $success, + 'message' => $success ? + "Successfully {$action}ed $service" : + "Failed to $action $service" + ]); + } else { + http_response_code(404); + echo json_encode(['error' => 'Endpoint not found']); + } + } +} + +function handlePutRequest($path) { + http_response_code(501); + echo json_encode(['error' => 'PUT method not implemented yet']); +} + +function handleDeleteRequest($path) { + if (preg_match('/^hidden-services\/([^\/]+)$/', $path, $matches)) { + $name = $matches[1]; + + if (deleteHiddenService($name)) { + echo json_encode(['success' => true, 'message' => 'Hidden service deleted']); + } else { + http_response_code(500); + echo json_encode(['error' => 'Failed to delete hidden service']); + } + } else { + http_response_code(404); + echo json_encode(['error' => 'Endpoint not found']); + } +} +?> \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/auth.php b/rootfs/usr/local/share/webpanel/auth.php new file mode 100644 index 0000000..5dd49be --- /dev/null +++ b/rootfs/usr/local/share/webpanel/auth.php @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/components/footer.php b/rootfs/usr/local/share/webpanel/components/footer.php new file mode 100644 index 0000000..e60ff5f --- /dev/null +++ b/rootfs/usr/local/share/webpanel/components/footer.php @@ -0,0 +1,31 @@ + + + +
+ +
+ + + + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/components/header.php b/rootfs/usr/local/share/webpanel/components/header.php new file mode 100644 index 0000000..42404f2 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/components/header.php @@ -0,0 +1,21 @@ + + + + + + <?php echo $page_title ?? 'Tor Admin Panel'; ?> + + + + + + +
+
+
+

๐Ÿง…

+
+ Welcome, + Logout +
+
\ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/components/nav.php b/rootfs/usr/local/share/webpanel/components/nav.php new file mode 100644 index 0000000..db7d788 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/components/nav.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/config.php b/rootfs/usr/local/share/webpanel/config.php new file mode 100644 index 0000000..33918e1 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/config.php @@ -0,0 +1,116 @@ + '/config/tor/server/server.conf', + 'tor-bridge' => '/config/tor/bridge/bridge.conf', + 'tor-relay' => '/config/tor/relay/relay.conf', + 'unbound' => '/config/unbound/unbound.conf', + 'privoxy' => '/config/privoxy/config', + 'nginx' => '/config/nginx/nginx.conf' +]; + +$current_config = $_GET['config'] ?? 'tor-server'; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $config_name = $_POST['config'] ?? ''; + $content = $_POST['content'] ?? ''; + + if (isset($config_files[$config_name])) { + $config_file = $config_files[$config_name]; + $config_dir = dirname($config_file); + + if (!is_dir($config_dir)) { + mkdir($config_dir, 0755, true); + } + + if (saveConfigContent($config_file, $content)) { + $message = "Configuration saved successfully for $config_name"; + $messageType = 'success'; + + // Restart the service after config change + if (in_array($config_name, ['tor-server', 'tor-bridge', 'tor-relay'])) { + restartService($config_name); + $message .= " and service restarted"; + } + } else { + $message = "Failed to save configuration for $config_name"; + $messageType = 'error'; + } + } +} + +$config_content = getConfigContent($config_files[$current_config]); +?> + + + + + + Configuration - Tor Admin Panel + + + +
+
+

๐Ÿง… Tor Admin Panel - Configuration

+
+ Welcome, + Logout +
+
+ + + + +
+ +
+ + +
+ $file): ?> +
+ +
+ +
+ +
+

Editing: Configuration

+

File:

+ +
+ +
+ + +
+ +
+
+ +
+ Important Notes:
+ โ€ข Changes to Tor configurations will automatically restart the affected service
+ โ€ข Backup your configurations before making changes
+ โ€ข Invalid configurations may prevent services from starting
+ โ€ข Check logs if services fail to start after configuration changes +
+
+ + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/functions.php b/rootfs/usr/local/share/webpanel/functions.php new file mode 100644 index 0000000..6fd9dc7 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/functions.php @@ -0,0 +1,139 @@ +/dev/null"); + return !empty(trim($output)); +} + +function getServicePid($service) { + $output = shell_exec("pgrep -f '$service' 2>/dev/null"); + return trim($output) ?: 'N/A'; +} + +function startService($service) { + $init_script = "/usr/local/etc/docker/init.d/*$service*.sh"; + $files = glob($init_script); + if (!empty($files)) { + $script = $files[0]; + shell_exec("bash '$script' > /dev/null 2>&1 &"); + return true; + } + return false; +} + +function stopService($service) { + $pid = getServicePid($service); + if ($pid !== 'N/A') { + shell_exec("kill $pid 2>/dev/null"); + return true; + } + return false; +} + +function restartService($service) { + stopService($service); + sleep(2); + return startService($service); +} + +function getLogTail($logfile, $lines = 50) { + if (file_exists($logfile)) { + return shell_exec("tail -n $lines '$logfile' 2>/dev/null"); + } + return "Log file not found: $logfile"; +} + +function getConfigContent($configfile) { + if (file_exists($configfile)) { + return file_get_contents($configfile); + } + return ''; +} + +function saveConfigContent($configfile, $content) { + return file_put_contents($configfile, $content) !== false; +} + +function getHiddenServices() { + $services = []; + $services_dir = '/data/tor/server/services'; + + if (is_dir($services_dir)) { + $dirs = glob($services_dir . '/*', GLOB_ONLYDIR); + foreach ($dirs as $dir) { + $name = basename($dir); + $hostname_file = $dir . '/hostname'; + $hostname = file_exists($hostname_file) ? trim(file_get_contents($hostname_file)) : 'Not generated yet'; + $services[] = [ + 'name' => $name, + 'hostname' => $hostname, + 'path' => $dir + ]; + } + } + + return $services; +} + +function createHiddenService($name, $port_mapping) { + $services_dir = '/data/tor/server/services'; + $service_dir = $services_dir . '/' . $name; + + if (!is_dir($service_dir)) { + mkdir($service_dir, 0700, true); + } + + $config_content = "HiddenServiceDir $service_dir\n"; + $config_content .= "HiddenServicePort $port_mapping\n"; + + $config_file = "/config/tor/server/hidden.d/$name.conf"; + return file_put_contents($config_file, $config_content) !== false; +} + +function deleteHiddenService($name) { + $services_dir = '/data/tor/server/services'; + $service_dir = $services_dir . '/' . $name; + $config_file = "/config/tor/server/hidden.d/$name.conf"; + + $success = true; + if (is_dir($service_dir)) { + $success &= shell_exec("rm -rf '$service_dir'") !== false; + } + if (file_exists($config_file)) { + $success &= unlink($config_file); + } + + return $success; +} + +function getSystemStats() { + $stats = []; + + $uptime = trim(shell_exec('uptime -p 2>/dev/null || echo "N/A"')); + $stats['uptime'] = $uptime; + + $memory = shell_exec('free -m | grep "Mem:" | awk \'{print $3"/"$2" MB"}\''); + $stats['memory'] = trim($memory) ?: 'N/A'; + + $disk = shell_exec('df -h / | tail -1 | awk \'{print $3"/"$2" ("$5")"}\''); + $stats['disk'] = trim($disk) ?: 'N/A'; + + $load = trim(shell_exec('uptime | awk -F"load average:" \'{print $2}\' | awk \'{print $1}\' | tr -d ","')); + $stats['load'] = $load ?: 'N/A'; + + return $stats; +} + +function sanitizeInput($input) { + return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); +} + +function validatePortMapping($mapping) { + return preg_match('/^\d+\s+\d+\.\d+\.\d+\.\d+:\d+$/', $mapping); +} + +function validateServiceName($name) { + return preg_match('/^[a-zA-Z0-9_-]+$/', $name); +} + +?> \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/hidden.php b/rootfs/usr/local/share/webpanel/hidden.php new file mode 100644 index 0000000..ff09932 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/hidden.php @@ -0,0 +1,153 @@ + + + + + + + Hidden Services - Tor Admin Panel + + + +
+
+

๐Ÿง… Tor Admin Panel - Hidden Services

+
+ Welcome, + Logout +
+
+ + + + +
+ +
+ + +
+

Create New Hidden Service

+
+ +
+ + +
+
+ + + Format: external_port internal_ip:internal_port +
+ +
+
+ +

Existing Hidden Services

+ + +
+ No hidden services configured yet. Create one above to get started. +
+ + +
+

+
+ Onion Address:
+ +
+

Data Directory:

+ +
+ + + +
+
+ + +
+
+ + +
+ Restart Tor server to activate new services or apply deletions +
+ + +
+ Hidden Service Management:
+ โ€ข Hidden services allow you to host websites accessible only via Tor
+ โ€ข Each service gets a unique .onion address
+ โ€ข Port mapping routes external .onion traffic to internal services
+ โ€ข Services are stored in /data/tor/server/services/
+ โ€ข Restart Tor server after creating/deleting services +
+
+ + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/index.php b/rootfs/usr/local/share/webpanel/index.php new file mode 100644 index 0000000..7463bfa --- /dev/null +++ b/rootfs/usr/local/share/webpanel/index.php @@ -0,0 +1,151 @@ + 'Tor Bridge', + 'tor-relay' => 'Tor Relay', + 'tor-server' => 'Tor Server', + 'unbound' => 'DNS Resolver', + 'privoxy' => 'HTTP Proxy', + 'nginx' => 'Web Server' +]; + +$message = ''; +$messageType = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + $service = $_POST['service'] ?? ''; + + if (in_array($service, array_keys($services))) { + switch ($action) { + case 'start': + if (startService($service)) { + $message = "Started $services[$service]"; + $messageType = 'success'; + } else { + $message = "Failed to start $services[$service]"; + $messageType = 'error'; + } + break; + + case 'stop': + if (stopService($service)) { + $message = "Stopped $services[$service]"; + $messageType = 'success'; + } else { + $message = "Failed to stop $services[$service]"; + $messageType = 'error'; + } + break; + + case 'restart': + if (restartService($service)) { + $message = "Restarted $services[$service]"; + $messageType = 'success'; + } else { + $message = "Failed to restart $services[$service]"; + $messageType = 'error'; + } + break; + } + } +} + +$stats = getSystemStats(); +?> + + + + + + Tor Admin Panel + + + + +
+
+

๐Ÿง… Tor Admin Panel

+
+ Welcome, + Logout +
+
+ + + + +
+ +
+ + +
+
+
+
System Uptime
+
+
+
+
Memory Usage
+
+
+
+
Disk Usage
+
+
+
+
Load Average
+
+
+ +

Service Management

+
+ $name): ?> + +
+

+
+ +
+

PID:

+ +
+ + + + +
+
+ +
+ +
+ Quick Info:
+ โ€ข SOCKS Proxy: localhost:9050
+ โ€ข HTTP Proxy: localhost:8118
+ โ€ข DNS Resolver: localhost:9053
+ โ€ข Control Port: localhost:9051
+ โ€ข Web Interface: localhost:80 +
+
+ + + + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/jwt.php b/rootfs/usr/local/share/webpanel/jwt.php new file mode 100644 index 0000000..309e9c9 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/jwt.php @@ -0,0 +1,94 @@ + 'JWT', 'alg' => 'HS256']); + $payload = json_encode(array_merge($payload, [ + 'iat' => time(), + 'exp' => time() + (24 * 60 * 60) // 24 hours + ])); + + $base64Header = self::base64UrlEncode($header); + $base64Payload = self::base64UrlEncode($payload); + + $signature = hash_hmac('sha256', $base64Header . '.' . $base64Payload, self::$secret_key, true); + $base64Signature = self::base64UrlEncode($signature); + + return $base64Header . '.' . $base64Payload . '.' . $base64Signature; + } + + public static function decode($jwt) { + self::init(); + + $parts = explode('.', $jwt); + if (count($parts) !== 3) { + return false; + } + + list($base64Header, $base64Payload, $base64Signature) = $parts; + + $signature = self::base64UrlDecode($base64Signature); + $expectedSignature = hash_hmac('sha256', $base64Header . '.' . $base64Payload, self::$secret_key, true); + + if (!hash_equals($signature, $expectedSignature)) { + return false; + } + + $payload = json_decode(self::base64UrlDecode($base64Payload), true); + + if (isset($payload['exp']) && $payload['exp'] < time()) { + return false; // Token expired + } + + return $payload; + } + + private static function base64UrlEncode($data) { + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); + } + + private static function base64UrlDecode($data) { + return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); + } +} + +function generateApiToken($username) { + return SimpleJWT::encode([ + 'username' => $username, + 'role' => 'admin' + ]); +} + +function validateApiToken($token) { + $payload = SimpleJWT::decode($token); + return $payload !== false ? $payload : null; +} + +?> \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/login.php b/rootfs/usr/local/share/webpanel/login.php new file mode 100644 index 0000000..0677594 --- /dev/null +++ b/rootfs/usr/local/share/webpanel/login.php @@ -0,0 +1,52 @@ + + + + + + + Tor Admin Panel - Login + + + + + + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/logs.php b/rootfs/usr/local/share/webpanel/logs.php new file mode 100644 index 0000000..96ff6bd --- /dev/null +++ b/rootfs/usr/local/share/webpanel/logs.php @@ -0,0 +1,106 @@ + '/data/logs/tor/server.log', + 'tor-bridge' => '/data/logs/tor/bridge.log', + 'tor-relay' => '/data/logs/tor/relay.log', + 'unbound' => '/data/logs/unbound/unbound.log', + 'privoxy' => '/data/logs/privoxy/privoxy.log', + 'nginx-access' => '/data/logs/nginx/access.log', + 'nginx-error' => '/data/logs/nginx/error.log', + 'entrypoint' => '/data/logs/entrypoint.log' +]; + +$current_log = $_GET['log'] ?? 'tor-server'; +$lines = (int)($_GET['lines'] ?? 100); + +$log_content = ''; +if (isset($log_files[$current_log])) { + $log_content = getLogTail($log_files[$current_log], $lines); +} +?> + + + + + + Logs - Tor Admin Panel + + + + +
+
+

๐Ÿง… Tor Admin Panel - Logs

+
+ Welcome, + Logout +
+
+ + + +
+ $file): ?> +
+ +
+ +
+ +
+
+

Viewing: Log

+
+ + +
+
+ +

File:

+ +
+ +
+ +
+ + +
+
+ +
+ Log Monitoring:
+ โ€ข Logs auto-refresh every 10 seconds
+ โ€ข Tor logs show connection and circuit information
+ โ€ข Nginx logs show web access and errors
+ โ€ข Entrypoint log shows container initialization
+ โ€ข Check logs if services fail to start +
+
+ + + + \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/style.css b/rootfs/usr/local/share/webpanel/style.css new file mode 100644 index 0000000..c3f752b --- /dev/null +++ b/rootfs/usr/local/share/webpanel/style.css @@ -0,0 +1,446 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: #0d1117; + min-height: 100vh; + color: #f0f6fc; + line-height: 1.6; + display: flex; + flex-direction: column; +} + +.main-wrapper { + flex: 1; + display: flex; + flex-direction: column; +} + +.login-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + padding: 20px; +} + +.login-form { + background: #161b22; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + width: 100%; + max-width: 400px; + border: 1px solid #30363d; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem; + background: #0d1117; + flex: 1; +} + +.header { + background: #161b22; + color: #f0f6fc; + padding: 1.5rem; + border-radius: 12px; + margin-bottom: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid #30363d; + flex-wrap: wrap; + gap: 1rem; +} + +.nav { + background: #161b22; + padding: 1rem; + border-radius: 12px; + margin-bottom: 1.5rem; + border: 1px solid #30363d; + overflow-x: auto; + white-space: nowrap; +} + +.nav a { + color: #7c3aed; + text-decoration: none; + margin-right: 1rem; + padding: 0.75rem 1rem; + border-radius: 8px; + transition: all 0.2s; + display: inline-block; + font-weight: 500; +} + +.nav a:hover, .nav a.active { + background: #7c3aed; + color: white; +} + +h1 { + color: #f0f6fc; + margin-bottom: 1.5rem; + text-align: center; + font-size: clamp(1.5rem, 4vw, 2rem); +} + +.form-group { + margin-bottom: 1.5rem; +} + +label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + color: #f0f6fc; + font-size: 0.9rem; +} + +input, select, textarea { + width: 100%; + padding: 0.875rem; + border: 1px solid #30363d; + border-radius: 8px; + font-size: 1rem; + transition: all 0.2s; + background: #0d1117; + color: #f0f6fc; +} + +input:focus, select:focus, textarea:focus { + outline: none; + border-color: #7c3aed; + box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); +} + +button { + background: #7c3aed; + color: white; + padding: 0.875rem 1.5rem; + border: none; + border-radius: 8px; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s; + width: 100%; + font-weight: 600; +} + +button:hover { + background: #8b5cf6; + transform: translateY(-1px); +} + +.btn-small { + padding: 0.5rem 0.875rem; + font-size: 0.875rem; + width: auto; + margin: 0.25rem; + min-width: 4rem; +} + +.error { + background: #1a1a1a; + color: #f85149; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1.5rem; + border-left: 4px solid #f85149; + border: 1px solid #30363d; +} + +.success { + background: #1a1a1a; + color: #56d364; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1.5rem; + border-left: 4px solid #56d364; + border: 1px solid #30363d; +} + +.info { + background: #161b22; + color: #79c0ff; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1.5rem; + border-left: 4px solid #79c0ff; + border: 1px solid #30363d; +} + +.service-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; +} + +.service-card { + background: #161b22; + border: 1px solid #30363d; + border-radius: 12px; + padding: 1.5rem; + transition: all 0.2s; +} + +.service-card:hover { + border-color: #7c3aed; + transform: translateY(-2px); +} + +.service-status { + display: inline-block; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; +} + +.status-running { + background: #e6ffe6; + color: #00b894; +} + +.status-stopped { + background: #ffe6e6; + color: #d63031; +} + +.config-section { + background: #161b22; + padding: 1.5rem; + border-radius: 12px; + margin-bottom: 1.5rem; + border: 1px solid #30363d; +} + +.log-container { + background: #0d1117; + color: #56d364; + padding: 1.5rem; + border-radius: 12px; + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; + font-size: 0.875rem; + max-height: 400px; + overflow-y: auto; + white-space: pre-wrap; + border: 1px solid #30363d; +} + +.hidden-service { + background: #161b22; + border: 1px solid #30363d; + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; +} + +.onion-address { + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace; + background: #0d1117; + color: #79c0ff; + padding: 1rem; + border-radius: 8px; + word-break: break-all; + margin: 1rem 0; + border: 1px solid #30363d; + font-size: 0.875rem; +} + +.auth-info { + text-align: center; + margin-top: 20px; + color: #666; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; +} + +.stat-card { + background: #161b22; + padding: 1.5rem; + border-radius: 12px; + text-align: center; + border: 1px solid #30363d; + transition: all 0.2s; +} + +.stat-card:hover { + border-color: #7c3aed; + transform: translateY(-2px); +} + +.stat-value { + font-size: 1.5rem; + font-weight: bold; + color: #7c3aed; + margin-bottom: 0.5rem; +} + +.tabs { + display: flex; + margin-bottom: 20px; + border-bottom: 2px solid #e0e0e0; +} + +.tab { + padding: 12px 24px; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: all 0.3s; +} + +.tab.active { + border-bottom-color: #667eea; + color: #667eea; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +.footer { + background: #161b22; + border-top: 1px solid #30363d; + padding: 1.5rem; + text-align: center; + margin-top: auto; + border-radius: 12px 12px 0 0; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; +} + +.footer a { + color: #7c3aed; + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +.logout-btn { + background: #f85149; + color: white; + padding: 0.5rem 1rem; + border-radius: 6px; + text-decoration: none; + font-size: 0.875rem; + margin-left: 1rem; + transition: all 0.2s; +} + +.logout-btn:hover { + background: #da3633; +} + +.header-actions { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; +} + +/* Mobile First Design */ +@media (max-width: 768px) { + .container { + padding: 0.5rem; + } + + .header { + padding: 1rem; + text-align: center; + } + + .header-actions { + justify-content: center; + width: 100%; + margin-top: 0.5rem; + } + + .nav { + padding: 0.5rem; + text-align: center; + } + + .nav a { + display: inline-block; + margin: 0.25rem; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + } + + .service-grid { + grid-template-columns: 1fr; + gap: 0.75rem; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + } + + .service-card, .stat-card, .config-section { + padding: 1rem; + } + + .btn-small { + padding: 0.5rem; + font-size: 0.8rem; + min-width: 3.5rem; + } + + .onion-address { + font-size: 0.8rem; + word-break: break-all; + } + + h1 { + font-size: 1.5rem; + } + + h2 { + font-size: 1.25rem; + } + + h3 { + font-size: 1.1rem; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .header { + padding: 0.75rem; + } + + .nav a { + font-size: 0.8rem; + padding: 0.5rem; + } +} \ No newline at end of file diff --git a/rootfs/usr/local/share/webpanel/tokens.php b/rootfs/usr/local/share/webpanel/tokens.php new file mode 100644 index 0000000..0997d9b --- /dev/null +++ b/rootfs/usr/local/share/webpanel/tokens.php @@ -0,0 +1,147 @@ + + + + + + + API Tokens - Tor Admin Panel + + + +
+
+

๐Ÿง… Tor Admin Panel - API Tokens

+
+ Welcome, + Logout +
+
+ + + + +
+ +
+ + + +
+

๐Ÿ”‘ Your New API Token

+
+ +
+
+ Important: Save this token now! It will not be shown again. +
+
+ + +
+

๐Ÿ” JWT Secret Key

+

Current JWT secret (auto-generated if TOR_JWT_SECRET not set):

+
+ +
+

Set TOR_JWT_SECRET environment variable to use a custom secret

+
+ +
+

Generate New API Token

+
+ +
+ + +
+ +
+
+ +
+

๐Ÿ“š API Documentation

+ +

Authentication

+
+

1. Get a token:

+ +curl -X POST http://your-server/admin/api.php?action=login \
+ -d "username=admin&password=yourpass" +
+ +

2. Use the token:

+ +curl -H "Authorization: Bearer YOUR_TOKEN" \
+ http://your-server/admin/api.php?action=status +
+
+ +

Available Endpoints

+
+

๐Ÿ“Š GET /admin/api.php?action=status

+

Get status of all services

+ +

๐Ÿ“ˆ GET /admin/api.php?action=stats

+

Get system statistics

+ +

๐Ÿง… GET /admin/api.php?action=hidden-services

+

Get list of hidden services

+ +

โš™๏ธ POST /admin/api.php?action=service-control

+

Control services (start/stop/restart)

+ +POST data: service=tor-server&command=restart + + +

๐Ÿ”‘ POST /admin/api.php?action=login

+

Get authentication token

+ +POST data: username=admin&password=yourpass + +
+
+ +
+ Security Notes:
+ โ€ข Tokens expire after 24 hours
+ โ€ข Set custom JWT secret via TOR_JWT_SECRET environment variable
+ โ€ข Store tokens securely - they provide full admin access
+ โ€ข API supports both Bearer tokens and web session authentication +
+
+ + \ No newline at end of file