diff --git a/docker/README.md b/docker/README.md index be2a065b..25017df1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -91,12 +91,23 @@ Updating WGDashboard is currently in **alpha** stage. While the update process m ## ⚙️ Environment Variables -| Variable | Accepted Values | Default | Example | Description | -|---------------|------------------------------------------|-------------------------|------------------------|-----------------------------------------------------------------------------| -| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | -| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | -| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | -| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | +| Variable | Accepted Values | Default | Example | Description | +| ------------------ | ---------------------------------------- | ----------------------- | --------------------- | ----------------------------------------------------------------------- | +| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | +| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | +| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | +| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | +| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. | +| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). | +| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. | +| `wg_autostart` | Wireguard interface name | `false` | `true` | Auto‐start the WireGuard client when the container launches. | +| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. | +| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. | +| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. | +| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. | +| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. | +| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. | +| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. | --- diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 64df0122..f5f41f84 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,10 +1,55 @@ #!/bin/bash -# Path to the configuration file (exists because of previous function). config_file="/data/wg-dashboard.ini" trap 'stop_service' SIGTERM +# Hash password with bcrypt +hash_password() { + python3 -c "import bcrypt; print(bcrypt.hashpw('$1'.encode(), bcrypt.gensalt(12)).decode())" +} + +# Function to set or update section/key/value in the INI file +set_ini() { + local section="$1" key="$2" value="$3" + local current_value + + # Add section if it doesn't exist + grep -q "^\[${section}\]" "$config_file" \ + || printf "\n[%s]\n" "${section}" >> "$config_file" + + # Check current value if key exists + if grep -q "^[[:space:]]*${key}[[:space:]]*=" "$config_file"; then + current_value=$(grep "^[[:space:]]*${key}[[:space:]]*=" "$config_file" | cut -d= -f2- | xargs) + + # Don't display actual value if it's a password field + if [[ "$key" == *"password"* ]]; then + if [ "$current_value" = "$value" ]; then + echo "- $key is already set correctly (value hidden)" + return 0 + fi + sed -i "/^\[${section}\]/,/^\[/{s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|}" "$config_file" + echo "- Updated $key (value hidden)" + else + if [ "$current_value" = "$value" ]; then + echo "- $key is already set correctly ($value)" + return 0 + fi + sed -i "/^\[${section}\]/,/^\[/{s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|}" "$config_file" + echo "- Updated $key to: $value" + fi + else + sed -i "/^\[${section}\]/a ${key} = ${value}" "$config_file" + + # Don't display actual value if it's a password field + if [[ "$key" == *"password"* ]]; then + echo "- Added new setting $key (value hidden)" + else + echo "- Added new setting $key: $value" + fi + fi +} + stop_service() { echo "[WGDashboard] Stopping WGDashboard..." /bin/bash ./wgd.sh stop @@ -15,67 +60,59 @@ echo "------------------------- START ----------------------------" echo "Starting the WireGuard Dashboard Docker container." ensure_installation() { - # When using a custom directory to store the files, this part moves over and makes sure the installation continues. echo "Quick-installing..." - + # Make the wgd.sh script executable. chmod +x "${WGDASH}"/src/wgd.sh cd "${WGDASH}"/src || exit - + # Github issue: https://github.com/donaldzou/WGDashboard/issues/723 echo "Checking for stale pids..." if [[ -f ${WGDASH}/src/gunicorn.pid ]]; then echo "Found stale pid, removing..." rm ${WGDASH}/src/gunicorn.pid fi - + # Removing clear shell command from the wgd.sh script to enhance docker logging. echo "Removing clear command from wgd.sh for better Docker logging." sed -i '/clear/d' ./wgd.sh - - # Create the databases directory if it does not exist yet. + + # Create required directories and links if [ ! -d "/data/db" ]; then echo "Creating database dir" - mkdir /data/db + mkdir -p /data/db fi - - # Linking the database on the persistent directory location to where WGDashboard expects. + if [ ! -d "${WGDASH}/src/db" ]; then ln -s /data/db "${WGDASH}/src/db" fi - - # Create the wg-dashboard.ini file if it does not exist yet. + if [ ! -f "${config_file}" ]; then echo "Creating wg-dashboard.ini file" touch "${config_file}" fi - - # Link the wg-dashboard.ini file from the persistent directory to where WGDashboard expects it. + if [ ! -f "${WGDASH}/src/wg-dashboard.ini" ]; then ln -s "${config_file}" "${WGDASH}/src/wg-dashboard.ini" fi # Create the Python virtual environment. . "${WGDASH}/src/venv/bin/activate" - + # Use the bash interpreter to install WGDashboard according to the wgd.sh script. /bin/bash ./wgd.sh install - + echo "Looks like the installation succeeded. Moving on." - - # This first step is to ensure the wg0.conf file exists, and if not, then its copied over from the ephemeral container storage. - # This is done so WGDashboard it works out of the box, it also sets a randomly generated private key. - + + # Setup WireGuard if needed if [ ! -f "/etc/wireguard/wg0.conf" ]; then - echo "Standard wg0 Configuration file not found, grabbing template." cp -a "/configs/wg0.conf.template" "/etc/wireguard/wg0.conf" - + echo "Setting a secure private key." - local privateKey privateKey=$(wg genkey) sed -i "s|^PrivateKey *=.*$|PrivateKey = ${privateKey}|g" /etc/wireguard/wg0.conf - + echo "Done setting template." else echo "Existing wg0 configuration file found, using that." @@ -84,63 +121,75 @@ ensure_installation() { set_envvars() { printf "\n------------- SETTING ENVIRONMENT VARIABLES ----------------\n" - - # Check if the file is empty + + # Check if config file is empty if [ ! -s "${config_file}" ]; then - echo "Config file is empty. Creating [Peers] section." - - # Create [Peers] section with initial values - { - echo "[Peers]" - echo "peer_global_dns = ${global_dns}" - echo "remote_endpoint = ${public_ip}" - echo -e "\n[Server]" - echo "app_port = ${wgd_port}" - } > "${config_file}" - - else - echo "Config file is not empty, using pre-existing." + echo "Config file is empty. Creating initial structure." fi - - echo "Verifying current variables..." - - # Check and update the DNS if it has changed - current_dns=$(grep "peer_global_dns = " "${config_file}" | awk '{print $NF}') - if [ "${global_dns}" == "$current_dns" ]; then - echo "DNS is set correctly, moving on." - - else - echo "Changing default DNS..." - sed -i "s/^peer_global_dns = .*/peer_global_dns = ${global_dns}/" "${config_file}" + + echo "Checking basic configuration:" + set_ini Peers peer_global_dns "${global_dns}" + + if [ -z "${public_ip}" ]; then + public_ip=$(curl -s ifconfig.me) + echo "Automatically detected public IP: ${public_ip}" fi - - # Checking the current set public IP and changing it if it has changed. - current_public_ip=$(grep "remote_endpoint = " "${config_file}" | awk '{print $NF}') - if [ "${public_ip}" == "" ]; then - default_ip=$(curl -s ifconfig.me) - - echo "Trying to fetch the Public-IP using ifconfig.me: ${default_ip}" - sed -i "s/^remote_endpoint = .*/remote_endpoint = ${default_ip}/" "${config_file}" - elif [ "${current_public_ip}" != "${public_ip}" ]; then - sed -i "s/^remote_endpoint = .*/remote_endpoint = ${public_ip}/" "${config_file}" - else - echo "Public-IP is correct, moving on." + + set_ini Peers remote_endpoint "${public_ip}" + set_ini Server app_port "${wgd_port}" + + # Account settings - process all parameters + echo "Configuring user account:" + # Basic account variables + [[ -n "$username" ]] && set_ini Account username "${username}" + + if [[ -n "$password" ]]; then + echo "- Setting password" + set_ini Account password "$(hash_password "${password}")" fi - - # Checking the current WGDashboard web port and changing if needed. - current_wgd_port=$(grep "app_port = " "${config_file}" | awk '{print $NF}') - if [ "${current_wgd_port}" == "${wgd_port}" ]; then - echo "Current WGD port is set correctly, moving on." - else - echo "Changing default WGD port..." - sed -i "s/^app_port = .*/app_port = ${wgd_port}/" "${config_file}" + + # Additional account variables + [[ -n "$enable_totp" ]] && set_ini Account enable_totp "${enable_totp}" + [[ -n "$totp_verified" ]] && set_ini Account totp_verified "${totp_verified}" + [[ -n "$totp_key" ]] && set_ini Account totp_key "${totp_key}" + + # Welcome session + [[ -n "$welcome_session" ]] && set_ini Other welcome_session "${welcome_session}" + # If username and password are set but welcome_session isn't, disable it + if [[ -n "$username" && -n "$password" && -z "$welcome_session" ]]; then + set_ini Other welcome_session "false" fi + + # Autostart WireGuard + if [[ -n "$wg_autostart" ]]; then + echo "Configuring WireGuard autostart:" + set_ini WireGuardConfiguration autostart "${wg_autostart}" + fi + + # Email (check if any settings need to be configured) + email_vars=("email_server" "email_port" "email_encryption" "email_username" "email_password" "email_from" "email_template") + for var in "${email_vars[@]}"; do + if [ -n "${!var}" ]; then + echo "Configuring email settings:" + break + fi + done + + # Email (iterate through all possible fields) + email_fields=("server:email_server" "port:email_port" "encryption:email_encryption" + "username:email_username" "email_password:email_password" + "send_from:email_from" "email_template:email_template") + + for field_pair in "${email_fields[@]}"; do + IFS=: read -r field var <<< "$field_pair" + [[ -n "${!var}" ]] && set_ini Email "$field" "${!var}" + done } -# === CORE SERVICES === -start_core() { +# Start service and monitor logs +start_and_monitor() { printf "\n---------------------- STARTING CORE -----------------------\n" - + # Due to some instances complaining about this, making sure its there every time. mkdir -p /dev/net mknod /dev/net/tun c 10 200 @@ -148,23 +197,19 @@ start_core() { # Actually starting WGDashboard echo "Activating Python venv and executing the WireGuard Dashboard service." - /bin/bash ./wgd.sh start -} - -ensure_blocking() { + bash ./wgd.sh start + # Wait a second before continuing, to give the python program some time to get ready. - sleep 1s + sleep 1 echo -e "\nEnsuring container continuation." - - # Find and tail the latest error and access logs if they exist + + # Find and monitor log file local logdir="${WGDASH}/src/log" - latestErrLog=$(find "$logdir" -name "error_*.log" -type f -print | sort -r | head -n 1) - + # Only tail the logs if they are found if [ -n "$latestErrLog" ]; then tail -f "$latestErrLog" & - # Wait for the tail process to end. wait $! else @@ -173,8 +218,7 @@ ensure_blocking() { fi } -# Execute functions for the WireGuard Dashboard services, then set the environment variables +# Main execution flow ensure_installation set_envvars -start_core -ensure_blocking +start_and_monitor \ No newline at end of file