#!/usr/bin/env bash set -euo pipefail # Ensure script is run as root (required for apt + /etc) if [[ "$EUID" -ne 0 ]]; then echo "Please run as root (or use sudo)." >&2 exit 1 fi #set script directory scriptdir="$(dirname "$(realpath "$0")")" # Capture the start time (Unix timestamp) START_TIME=$(date +%s) # recursive replace function replace_string_recursive() { local DIRECTORY="$1" local OLD_STRING="$2" local NEW_STRING="$3" if [ ! -d "$DIRECTORY" ]; then echo "Error: Directory '$DIRECTORY' does not exist." return 1 fi echo "Starting replacement of '$OLD_STRING' with '$NEW_STRING' in '$DIRECTORY'..." find "$DIRECTORY" -type f -exec sed -i "s|$OLD_STRING|$NEW_STRING|g" {} + if [ $? -eq 0 ]; then echo "Replacement completed successfully." return 0 else echo "An error occurred during replacement." return 1 fi } # example use # replace_string_recursive "/path/to/directory" "oldstring" "newstring" # sqlite db replace function replace_in_sqlite_db() { local DB_PATH="$1" local OLD_STRING="$2" local NEW_STRING="$3" # Check if the database file exists if [ ! -f "$DB_PATH" ]; then echo "Error: Database file '$DB_PATH' does not exist." exit 1 fi echo "Starting replacement of '$OLD_STRING' with '$NEW_STRING' in '$DB_PATH'..." # Create a backup of the original database local BACKUP_PATH="${DB_PATH}.bak" cp "$DB_PATH" "$BACKUP_PATH" # Escape single quotes for SQL local OLD_STRING_ESC=$(printf '%s\n' "$OLD_STRING" | sed "s/'/''/g") local NEW_STRING_ESC=$(printf '%s\n' "$NEW_STRING" | sed "s/'/''/g") # Get a list of all tables in the database local TABLES TABLES=$(sqlite3 "$DB_PATH" ".tables") # Loop through each table for TABLE in $TABLES; do echo "Processing table: $TABLE" # Escape table name if it's a reserved keyword local ESCAPED_TABLE if [[ "$TABLE" =~ ^(index|group|order|table|view|database|schema|trigger|transaction|commit|rollback|savepoint|release|alter|create|drop|insert|update|delete|select|from|where|join|union|intersect|except|limit|offset|order|by|group|having|as|with|replace|cast|case|when|then|else|end|and|or|not|is|null|between|in|like|glob|match|regexp|collate|exists|unique|primary|key|foreign|references|check|constraint|default|collate|asc|desc|on|using|natural|left|right|full|outer|cross|inner)$ ]]; then ESCAPED_TABLE="\"$TABLE\"" else ESCAPED_TABLE="$TABLE" fi # Get a list of all non-BLOB columns in the table sqlite3 "$DB_PATH" "PRAGMA table_info($ESCAPED_TABLE);" 2>/dev/null | awk -F'|' ' { if ($3 != "BLOB") { print $2 } }' | while read -r COLUMN; do if [ -n "$COLUMN" ]; then echo " Processing column: $COLUMN" # Escape column name if it's a reserved keyword local ESCAPED_COLUMN if [[ "$COLUMN" =~ ^(index|group|order|table|view|database|schema|trigger|transaction|commit|rollback|savepoint|release|alter|create|drop|insert|update|delete|select|from|where|join|union|intersect|except|limit|offset|order|by|group|having|as|with|replace|cast|case|when|then|else|end|and|or|not|is|null|between|in|like|glob|match|regexp|collate|exists|unique|primary|key|foreign|references|check|constraint|default|collate|asc|desc|on|using|natural|left|right|full|outer|cross|inner)$ ]]; then ESCAPED_COLUMN="\"$COLUMN\"" else ESCAPED_COLUMN="$COLUMN" fi # Update only rows where the column contains OLD_STRING # Run each update in its own connection to avoid locking issues sqlite3 "$DB_PATH" " UPDATE $ESCAPED_TABLE SET $ESCAPED_COLUMN = replace(CAST($ESCAPED_COLUMN AS TEXT), '$OLD_STRING_ESC', '$NEW_STRING_ESC') WHERE CAST($ESCAPED_COLUMN AS TEXT) LIKE '%$OLD_STRING_ESC%'; " fi done done echo "Replacement completed in '$DB_PATH'. Original database backed up to '$BACKUP_PATH'." } #example usage: replace_in_sqlite_db "database.sqlite" "Europe/Amsterdam" "UTC" get_country_from_timezone() { local timezone="$1" # Extract the region and city from the timezone (e.g., "America/New_York" -> "New_York") local city=$(echo "$timezone" | cut -d'/' -f2) # Use a predefined mapping for common cities (fallback if no better method is found) # This is a minimal mapping; you can expand it as needed. local declare -A city_to_country=( ["New_York"]="United States" ["Toronto"]="Canada" ["Amsterdam"]="Netherlands" ["London"]="United Kingdom" ["Tokyo"]="Japan" ["Paris"]="France" ["Berlin"]="Germany" ["Sydney"]="Australia" ["Melbourne"]="Australia" ) # Check if the city exists in the mapping if [ -n "${city_to_country[$city]}" ]; then echo "${city_to_country[$city]}" else # Fallback: Try to extract country from the timezone file (less reliable) # This is a placeholder; actual implementation would require parsing timezone files. echo "Unknown" fi } oc_installapp() { local appid="$1" docker exec owncloud_server occ market:install $appid echo "$appid installed for owncloud" } pause_if_enabled() { if [ "$SHOULD_PAUSE" = true ]; then read -n 1 -s -r -p "Press any key to continue..." echo # Move to a new line after keypress fi } # what this script needs to do: #formatting variables: green='\033[0;32m' cyan='\033[0;36m' cyanbold='\033[1;36m' greenbold='\033[1;32m' nc='\033[0m' underline='\033[4m' # Public IP (tries multiple services) publicip="$(curl -fsS https://api.ipify.org || curl -fsS https://ifconfig.me || echo "UNKNOWN")" # Local IP (first non-loopback) localip="$(hostname -I | awk '{print $1}')" # request all install parameters needed from user # Selection menu for external access method echo "--------------------------------------------------------" echo -e "${cyan}Select your external access method:${nc}" echo "" echo "1. Public DNS - You will need to purchase a domain name from a DNS provider and configure it." echo "2. Local DNS, Local IP - All local devices must use $localip as their DNS. Remote devices must use WireGuard VPN." echo "3. Local DNS, Public IP - All local devices must use $localip as their DNS. Remote devices must use $publicip as their DNS, you must forward port 53 to $localip" echo "" while true; do read -p "Enter your choice (1-3): " external_access_method case "$external_access_method" in (1|2|3) echo "Selected option: $external_access_method" break ;; (*) echo "Invalid choice. Please enter 1, 2, or 3." ;; esac done # ---- User input ---- read -rp "Admin email: " adminemail < /dev/tty read -rsp "Admin password: " adminpass < /dev/tty echo read -rp "Timezone (e.g. Europe/Amsterdam): " timezone < /dev/tty read -rp "Domain (e.g. example.com): " domain < /dev/tty # Prompt the user to set the pause behavior (defaults to true) echo -e "${cyan}pause prompts${nc} will ask you to finish configuration for each application as the script goes." echo "disabling these means you'll have to do this after the script finishes, but allows you to run the script unattended." read -p "Enable pause prompts? [Y/n] " -n 1 -r echo # Move to a new line if [[ $REPLY =~ ^[Nn]$ ]]; then SHOULD_PAUSE=false else SHOULD_PAUSE=true fi if [ "$external_access_method" -eq 1 ]; then # Verify DNS records echo -e "${cyan}Verifying DNS records...${nc}" echo "" # Check A record echo -e "${cyan}Checking A record for @...${nc}" a_record_check=$(dig +short A "$domain" @8.8.8.8 2>/dev/null | grep -c "^$publicip$" || echo "0") if [ "$a_record_check" -eq 0 ]; then echo -e "${cyan}Warning: A record for @ is not set or not pointing to $publicip${nc}" else echo -e "${cyan}A record for @ is correctly set to $publicip${nc}" fi # Check CNAME record echo -e "${cyan}Checking CNAME record for *...${nc}" cname_record_check=$(dig +short CNAME "*.$domain" @8.8.8.8 2>/dev/null | grep -c "$domain\.$" || echo "0") if [ "$cname_record_check" -eq 0 ]; then echo -e "${cyan}Warning: CNAME record for * is not set or not pointing to @${nc}" else echo -e "${cyan}CNAME record for * is correctly set to @${nc}" fi echo "--------------------------------------------------------" echo -e "please double-check your ${cyan}DNS records${nc} to ensure they are set. the following dns records need to be set:" echo "" echo "|name |type |value " echo "|@ |A |$publicip " echo "|* |CNAME |@ " echo "" echo -e "once you've done this, press any key to ${underline}continue${nc}" pause_if_enabled fi # Ask about static public IP if using public DNS if [ "$external_access_method" -eq 1 ]; then while true; do read -p "Do you have a static public IP address? (yes/no): " static_ip_answer case "$static_ip_answer" in ([yY]|[yY][eE][sS]) static_public_ip="yes" break ;; ([nN]|[nN][oO]) static_public_ip="no" break ;; (*) echo "Please answer with 'yes' or 'no'." ;; esac done # Store this information for later use echo "Static public IP setting: $static_public_ip" fi # generate random passwords for DB hosts and other secrets # ---- Random generators ---- rand_hex() { openssl rand -hex 24; } rand_b64() { openssl rand -base64 32; } ownclouddbpass="$(rand_hex)" ownclouddbrootpass="$(rand_hex)" convertxJWT="$(rand_hex)" bookstackkey="$(rand_b64)" # base64 as requested bookstackdbpass="$(rand_hex)" bookstackdbrootpass="$(rand_hex)" onlyofficeJWT="$(rand_hex)" # install docker echo "Updating apt and installing prerequisites..." apt update apt install -y ca-certificates curl git openssl curl gawk coreutils grep jq sqlite3 iso-codes net-tools echo "Setting up Docker GPG key..." install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc echo "Adding Docker apt repository..." . /etc/os-release cat > /etc/apt/sources.list.d/docker.sources <" $domain replace_in_sqlite_db "/opt/stacks/npm/data/database.sqlite" "" $localip replace_in_sqlite_db "/opt/stacks/npm/data/database.sqlite" "" $publicip cd /opt/stacks/npm docker compose up -d echo -e "${cyan}Nginx Proxy Manager${nc} has been installed and launched. go to http://$localip:81 and configure your username and password, then press any key to continue" pause_if_enabled echo "" echo -e "Multiple applications will now be installed in quick succession without continuous user prompting." echo -e "when each application is installed, you will see an output providing you the url to verify it is online" echo -e "do not worry if you miss it, you will be reminded later" echo -e "you can skip verifying as the script runs, and take a short coffee break here." echo -e "the script will continue automatically in 30 seconds" sleep 30 cd /opt/stacks/dozzle docker compose up -d echo -e "${cyan}dozzle${nc} has been launched from http://dozzle.$domain, verify it is online, do not worry if the UI gives a time-out error, this is due to docker being busy while this script runs." cd /opt/stacks/convertx docker compose up -d echo -e "${cyan}convertx${nc} has been launched from http://convert.$domain, verify it is online" cd /opt/stacks/it-tools docker compose up -d echo -e "${cyan}it-tools${nc} has been launched from http://tools.$domain, verify it is online" cd /opt/stacks/onlyoffice docker compose up -d echo -e "${cyan}onlyoffice${nc} has been launched from http://office.$domain, verify it is online" #owncloud # todo: use OC config commands to install addons and configure onlyoffice cd /opt/stacks/owncloud docker compose up -d echo "awaiting owncloud boot" sleep 120 oc_installapp bookmarks oc_installapp calendar oc_installapp camerarawpreviews oc_installapp checksum oc_installapp contacts oc_installapp drawio oc_installapp extract oc_installapp files_3dviewer oc_installapp files_clipboard oc_installapp files_textviewer oc_installapp gallery oc_installapp metadata oc_installapp music oc_installapp notes oc_installapp onlyoffice oc_installapp tasks docker exec owncloud_server occ config:app:set onlyoffice DocumentServerUrl --value="https://office.$domain/" docker exec owncloud_server occ config:app:set onlyoffice jwt_secret --value="$onlyofficeJWT" echo -e "${cyan}owncloud${nc} has been launched from http://cloud.$domain, check if it is online and working, it may take a bit to launch. 502 error = still starting" cd /opt/stacks/site docker compose up -d echo -e "${cyan}site${nc} has been launched from http://www.$domain, verify it is online" cd /opt/stacks/vaultwarden docker compose up -d echo -e "${cyan}vaultwarden${nc} has been launched from http://vault.$domain, verify it is online" #pihole cd /opt/stacks/pihole docker compose up -d echo -e "${cyan}pihole${nc} has been launched from http://dns.$domain, to use pihole as your DNS provider, set your DNS to $localip in your router for DHCP and on your device for any device with a static ip. Currently, the DNS is configured to use the joindns4.eu DNS, which *also* has built-in adblocking." cd /opt/stacks/wireguard docker compose up -d echo -e "${cyan}wireguard${nc} has been launched from http://vpn.$domain, verify it is online" cd /opt/stacks/downloader docker compose up -d echo -e "${cyan}downloader${nc} has been launched from http://download.$domain, verify it is online" echo "" echo -e "recap of all available services up to now:" echo -e "${cyan}owncloud${nc} has been launched from http://cloud.$domain, check if it is online and working, it may take a bit to launch. feel free to add marketplace plugins" echo -e "${cyan}vaultwarden${nc} has been launched from http://vault.$domain, verify it is online" echo -e "${cyan}site${nc} has been launched from http://www.$domain, verify it is online" echo -e "${cyan}onlyoffice${nc} has been launched from http://tools.$domain, verify it is online" echo -e "${cyan}it-tools${nc} has been launched from http://tools.$domain, verify it is online" echo -e "${cyan}wireguard${nc} has been launched from http://vpn.$domain, verify it is online" echo -e "${cyan}convertx${nc} has been launched from http://convert.$domain, verify it is online" echo -e "${cyan}downloader${nc} has been launched from http://download.$domain, verify it is online" echo -e "${cyan}dozzle${nc} has been launched from http://dozzle.$domain, verify it is online, do not worry if the UI gives a time-out error, this is due to docker being busy while this script runs." echo -e "${cyan}Nginx Proxy Manager${nc} has been installed, configured and launched from http://proxy.$domain" echo -e "${cyan}Dockge${nc} has been installed, configured and launched from http://docker.$domain" echo -e "${cyan}pihole${nc} has been launched from http://dns.$domain, to use pihole as your DNS provider, set your DNS to $localip in your router for DHCP and on your device for any device with a static ip. Currently, the DNS is configured to use the joindns4.eu DNS, which *also* has built-in adblocking." echo "" echo "you may have to go to settings > additional in owncloud and click "save" for the onlyoffice server settings." #bookstack cd /opt/stacks/bookstack docker compose up -d echo -e "${cyan}bookstack${nc} has been launched from http://docs.$domain, verify it is online (this may take a bit) and log in with email 'admin@admin.com' and password 'password', then reset this account to use $adminemail and your password. afterwards, press any key to continue" pause_if_enabled #browser cd /opt/stacks/browser docker compose up -d echo -e "${cyan}filebrowser${nc} has been launched from http://browser.$domain, head to dockge (http://docker.$domain), open the filebrowser stack and check the logs for the initial admin password. make sure to change this in filebrowser's config, then press any key to continue" pause_if_enabled #jellyfin cd /opt/stacks/jellyfin docker compose up -d mkdir /opt/stacks/jellyfin/media/shows mkdir /opt/stacks/jellyfin/media/movies echo -e "${cyan}jellyfin${nc} has launched from http://video.$domain, perform the initial setup and press any key to continue" pause_if_enabled #uptimekuma cd /opt/stacks/kuma docker compose up -d echo -e "${cyan}uptime kuma${nc} has been launched from http://status.$domain, perform the initial setup, choosing embedded mariaDB, then press any key to continue" pause_if_enabled #dashboard replace_in_sqlite_db "/opt/stacks/dashboard/config/www/app.sqlite" "" $domain cd /opt/stacks/dashboard docker compose up -d echo -e "${cyan}dashboard${nc} has been launched from http://dash.$domain, verify it is online, check that its entries work and press any button to continue" pause_if_enabled cat >/opt/stacks/setup/npmcertlist.txt </opt/stacks/pihole/dns/02-wildcard.conf < 255) if [ "$new_octet4" -gt 255 ]; then new_octet4=$((new_octet4 - 256)) octet3=$((2#$octet3 + 1)) # Carry over to the third octet octet3=$(printf "%08d" "$(echo "obase=2; $octet3" | bc)") # Convert back to 8-bit binary fi # Update the last octet octet4=$(printf "%08d" "$(echo "obase=2; $new_octet4" | bc)") # Reconstruct the binary IP string echo "${octet1}${octet2}${octet3}${octet4}" } # Function to subtract 1 from a binary IP string subtract_one_from_ip() { local bin_ip=$1 local dec_ip=$((2#${bin_ip:0:8} * 256**3 + 2#${bin_ip:8:8} * 256**2 + 2#${bin_ip:16:8} * 256 + 2#${bin_ip:24:8})) dec_ip=$((dec_ip - 1)) printf "%08d%08d%08d%08d" \ "$(echo "obase=2; ($dec_ip >> 24) & 255" | bc)" \ "$(echo "obase=2; ($dec_ip >> 16) & 255" | bc)" \ "$(echo "obase=2; ($dec_ip >> 8) & 255" | bc)" \ "$(echo "obase=2; $dec_ip & 255" | bc)" } # Calculate network and broadcast addresses using binary operations gateway_bin=$(ip_to_bin "$gateway") netmask_bin=$(ip_to_bin "$netmask") localip_bin=$(ip_to_bin "$localip") # Calculate network address (bitwise AND of gateway and netmask) network_bin="" for i in {0..31}; do if [ "${gateway_bin:$i:1}" = "1" ] && [ "${netmask_bin:$i:1}" = "1" ]; then network_bin="${network_bin}1" else network_bin="${network_bin}0" fi done # Calculate broadcast address (bitwise OR of network and inverted netmask) broadcast_bin="" for i in {0..31}; do if [ "${netmask_bin:$i:1}" = "0" ]; then broadcast_bin="${broadcast_bin}1" else broadcast_bin="${broadcast_bin}${network_bin:$i:1}" fi done network_address=$(bin_to_ip "$network_bin") broadcast_address=$(bin_to_ip "$broadcast_bin") echo " Network Address: $network_address" echo " Broadcast Address: $broadcast_address" # Calculate DHCP start: network_address + 10 dhcp_start_bin=$(add_binary_to_ip "$network_bin" "1010") # 10 in binary # Ensure DHCP start is not before network+1 if [ "$(bin_to_ip "$dhcp_start_bin")" = "$(bin_to_ip "$network_bin")" ]; then dhcp_start_bin=$(add_binary_to_ip "$network_bin" "1") # network + 1 fi # Calculate DHCP end: dhcp_start + 100 dhcp_end_bin=$(add_binary_to_ip "$dhcp_start_bin" "1100100") # 100 in binary # Ensure DHCP end is not after broadcast-1 broadcast_minus_1_bin=$(subtract_one_from_ip "$broadcast_bin") if [ "$(printf "%d" "$(bin_to_ip "$dhcp_end_bin" | tr '.' ' ')")" -ge "$(printf "%d" "$(bin_to_ip "$broadcast_bin" | tr '.' ' ')")" ]; then dhcp_end_bin="$broadcast_minus_1_bin" fi # Exclude local IP from range localip_dec=$(printf "%d" "$(bin_to_ip "$localip_bin" | tr '.' ' ')") dhcp_start_dec=$(printf "%d" "$(bin_to_ip "$dhcp_start_bin" | tr '.' ' ')") dhcp_end_dec=$(printf "%d" "$(bin_to_ip "$dhcp_end_bin" | tr '.' ' ')") if [ "$localip_dec" -ge "$dhcp_start_dec" ] && [ "$localip_dec" -le "$dhcp_end_dec" ]; then echo "Local IP is in DHCP range. Adjusting..." if [ $((localip_dec - dhcp_start_dec)) -lt $((dhcp_end_dec - localip_dec)) ]; then dhcp_start_bin=$(add_binary_to_ip "$localip_bin" "1") # Move start above local IP else dhcp_end_bin=$(subtract_one_from_ip "$localip_bin") # Move end below local IP fi fi # Convert to IP format dhcp_start=$(bin_to_ip "$dhcp_start_bin") dhcp_end=$(bin_to_ip "$dhcp_end_bin") # Validate DHCP range if [ -z "$dhcp_start" ] || [ -z "$dhcp_end" ]; then echo "Error: Invalid DHCP range calculated. DHCP start or end is empty. Aborting." exit 1 fi echo "--- DHCP Configuration Summary ---" echo " DHCP Range: $dhcp_start to $dhcp_end" echo " Router: $gateway" echo " Netmask: $netmask" echo "----------------------------------" # Apply the DHCP configuration echo "Applying DHCP configuration..." docker exec pihole pihole-FTL --config dhcp.active true docker exec pihole pihole-FTL --config dhcp.start "$dhcp_start" docker exec pihole pihole-FTL --config dhcp.end "$dhcp_end" docker exec pihole pihole-FTL --config dhcp.router "$gateway" docker exec pihole pihole-FTL --config dhcp.netmask "$netmask" # Restart PiHole to apply changes echo "Restarting PiHole to apply changes..." docker restart pihole echo "DHCP configuration applied successfully." fi # Generate post-install tasks file echo "Generating post-install tasks file..." tasks_file="/opt/files/post-install.txt" # Start with common header cat > "$tasks_file" <> "$tasks_file" < DDNS 3. Select your DDNS provider (No-IP or ASUS DDNS) 4. Enter your DDNS hostname 5. Provide your DDNS credentials 6. Click Apply to save settings TP-LINK ROUTERS --------------- 1. Log in to your router's admin panel at http://$gateway 2. Navigate to Advanced > Network > Dynamic DNS 3. Select your DDNS provider (No-IP or TP-Link DDNS) 4. Enter your DDNS hostname 5. Provide your DDNS credentials 6. Click Save to apply settings -------------------------------------------------------------------------------- OPTION 2: CONFIGURING DDNS ON YOUR SERVER USING DDCLIENT -------------------------------------------------------------------------------- 1. INSTALL DDCLIENT ------------------- sudo apt update && sudo apt install ddclient 2. CONFIGURE DDCLIENT ---------------------- Edit the configuration file: sudo nano /etc/ddclient/ddclient.conf For DuckDNS: protocol=duckdns use=web server=www.duckdns.org login=your_duckdns_token password= yourDDNSdomain.duckdns.org For No-IP: protocol=noip use=web server=dynupdate.no-ip.com login=your_noip_username password=your_noip_password yourDDNSdomain.ddns.net 3. RESTART DDCLIENT ------------------- sudo systemctl restart ddclient sudo systemctl enable ddclient 4. VERIFY CONFIGURATION ----------------------- sudo tail -n 20 /var/log/syslog | grep ddclient EOF section=$((section + 1)) fi # 2. PiHole Configuration (for local DNS methods) if [ "$external_access_method" -eq 2 ] || [ "$external_access_method" -eq 3 ]; then cat >> "$tasks_file" < DHCP Server 3. Set "Enable the DHCP Server" to "No" 4. Click "Apply" to save changes TP-LINK ROUTERS: --------------- 1. Log in to router admin (http://$gateway) 2. Go to Advanced > Network > DHCP Server 3. Toggle DHCP Server to "Disable" 4. Click "Save" to apply -------------------------------------------------------------------------------- 2. CONFIGURING STATIC IPS WITH CUSTOM DNS ($localip or $publicip) -------------------------------------------------------------------------------- use $publicip if you are forwarding port 53, or $localip if not. if you use $localip, the device will not have DNS outside of your local network. you only need to do this for devices that have a static ip address, devices that get their ip assigned via DHCP (which is default for most devices, especially wireless ones) get their DNS address from the router. WINDOWS: -------- 1. Open Network Settings (Win+I > Network & Internet) 2. Select your connection (Wi-Fi/Ethernet) 3. Click "Hardware properties" > "Edit" next to DNS 4. Set manual DNS to $localip or $publicip 5. Save changes MACOS: ------ 1. Open System Preferences > Network 2. Select your connection 3. Click "Advanced" > DNS tab 4. Add $localip or $publicip to DNS servers 5. Click "OK" > "Apply" LINUX (NETWORK MANAGER): ------------------------ 1. Edit connection settings: nm-connection-editor 2. Select your connection 3. Go to IPv4/IPv6 settings 4. Set DNS to $localip or $publicip 5. Save and restart connection ANDROID: -------- 1. Open Wi-Fi settings 2. Long-press your network > Modify network 3. Enable "Advanced options" 4. Set IP to static 5. Enter DNS as $localip or $publicip IOS: ---- 1. Open Wi-Fi settings 2. Tap (i) next to your network 3. Configure DNS > Manual 4. Add $localip or $publicip 5. Save changes EOF section=$((section + 1)) fi # 5. Router Configuration (for public DNS) if [ "$external_access_method" -eq 1 ]; then cat >> "$tasks_file" < DHCP Server 3. Under "DNS Server", enter $localip 4. Click "Apply" to save TP-LINK ROUTERS: --------------- 1. Log in to router admin (http://$gateway) 2. Go to Advanced > Network > DHCP Server 3. Set "Primary DNS" to $localip 4. Click "Save" to apply GENERAL NOTES: -------------- - Changes affect all DHCP clients - Some routers allow multiple DNS servers (comma-separated) - Restart devices to receive new DNS settings - after waiting up to 4 hours for changes to propagate: Verify DNS propagation with: nslookup $domain EOF section=$((section + 1)) fi if [ "$SHOULD_PAUSE" -eq "false" ]; then # 3. Nginx and Dockge Pre-Configuration cat >> "$tasks_file" <> "$tasks_file" < Let's Encrypt via HTTP # Step 3: copy the contents of the "npmcertlist.txt" file into the domain names field. copy one line at a time if using ctrl+c/ctrl+v, be sure to press enter between each line. if you make use of clickpaste (or any tool that allows you to paste by simulating keyboard input), you can use that to paste the entire list in one go or you can type the entire list manually # Step 4: hit "save" and wait. # Step 5: go to the Hosts > proxy hosts tab and go through each of the hosts # Step 6: repeat for each host: >go to the SSL tab >select your certificate >enable force SSL in the case of owncloud you may also want to enable HTTP/2 then hit save. once you've done this for all your sites, your entire cloud is now running with SSL encryption. EOF else # Local DNS methods - Self-signed certs cat >> "$tasks_file" < add certificate > custom fill in your main domain as the Name for the certificate key, use the wildcard.key file for the certificate, use the wildcard.crt file for the intermediate certificate, use the intermediate.crt file because this certificate is not backed by a public certificate authority like letsencrypt, you have to manually trust the root cert on each device you want to use the cloud on, or deal with "certificate untrusted" warnings. below are guides for doing this: windows: download and double-click rootCA.pfx select "Local Machine" and click next, password is empty. choose "place all certificates in the following store" and choose "Trusted Root Certification Authorities" click finish and confirm with "yes" if prompted. macOS: download and double-click rootCA.pfx if prompted for a password, leave it blank and click yes open Keychain Access (applications/utilities/keychain access) locate the imported rootCA.pfx certificate in the login or system keychains. double-click the certificate, expand the "trust" section and set "When using this certificate" to "always trust" Linux: download the rootCA.crt file copy rootCA.crt to /usr/local/share/ca-certificates/ using the following command from the directory rootCA.crt is in, or by using your file manager. sudo cp rootCA.crt /usr/local/share/ca-certificates/ then update the CA store by rebooting or running the following command: sudo update-ca-certificates android: download rootCA.crt to your device open settings > security > encryption & credentials > install a certificate select rootCA.crt and set a Name reboot if prompted IOS: download rootCA.crt to your device open the file in safari and tap "install" go to settings > general > VPN & Device management > configuration profile and install the certificate enable full trust in settings > general > about > certificate trust settings OWNCLOUD: for owncloud's onlyoffice integration, you'll have to disable certificate validation in the settings menu. now that you've set your DNS correctly and trusted the cert, you should be able to visit all of your sites via https://dash.$domain the certificate is valid for 10 years, after which you can generate a new one with gencerts.sh EOF fi section=$((section + 1)) # 6. Service Configurations if [ "$SHOULD_PAUSE" -eq "false" ]; then cat >> "$tasks_file" < EOF section=$((section + 1)) fi # 7. Uptime Kuma Configuration (last since it depends on DNS and HTTPS) cat >> "$tasks_file" < EOF # Add footer with additional information cat >> "$tasks_file" < IMPORTANT NOTES: - Your local network is: $network_address/$netmask - Your broadcast address is: $broadcast_address EOF # Output success message echo "Post-install tasks file generated: $tasks_file" echo "Please follow the tasks in order as some services depend on others!" echo "if you are using public DNS with a static ip, you can also already access this from http://browser.$domain, if you are using local DNS, you will need to do additional configuration" echo "you can open this file on this machine using the command 'sudo nano $tasks_file'" echo "" echo "for your convenience, the tasks file will be copied to the current user's home directory" cp $tasks_file ~/post-install.txt # Capture the end time (Unix timestamp) END_TIME=$(date +%s) # Calculate the elapsed time ELAPSED_TIME=$((END_TIME - START_TIME)) echo "Script took $ELAPSED_TIME seconds to complete." echo "please reboot before further use"