#!/bin/bash # Joplin Server setup: DuckDNS dynamic DNS plus the Docker compose stack. # # Sourced by install_joplin.sh when it is run with --with-server; split out to # keep that script under the 250-line cap. Expects the caller to have defined # info/warn/error and the JOPLIN_* and DUCKDNS_* variables. Sourced, not run, # so it inherits the caller's strict mode; the shebang is for shellcheck and # matches scripts/lib/common.sh. # ── Set up DuckDNS for stable URL ─────────────────────────────────── setup_duckdns() { info "Setting up DuckDNS for stable server URL..." if [[ -z "$DUCKDNS_DOMAIN" ]]; then echo "" info "Your public IP may change. DuckDNS provides a free stable hostname." info "1. Go to https://www.duckdns.org/ and sign in (Google/GitHub/etc.)" info "2. Create a subdomain (e.g. 'myjoplin' for myjoplin.duckdns.org)" info "3. Copy your token from the DuckDNS dashboard" echo "" read -r -p "Enter your DuckDNS subdomain (without .duckdns.org): " DUCKDNS_DOMAIN read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN fi if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then warn "DuckDNS not configured. Falling back to raw public IP (may change!)." return 1 fi local full_domain="${DUCKDNS_DOMAIN}.duckdns.org" # Update DuckDNS now info "Updating DuckDNS record for ${full_domain}..." local result result=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=") if [[ "$result" == "OK" ]]; then info "DuckDNS updated successfully: ${full_domain}" else warn "DuckDNS update returned: $result" fi # Set up cron job to keep IP updated every 5 minutes local duckdns_script="$JOPLIN_DATA_DIR/duckdns-update.sh" cat >"$duckdns_script" <> "$JOPLIN_DATA_DIR/duckdns.log" DUCKEOF chmod +x "$duckdns_script" # Add cron job (remove old one if exists, add new) ( crontab -l 2>/dev/null | grep -v "duckdns-update.sh" echo "*/5 * * * * $duckdns_script" ) | crontab - info "DuckDNS cron job installed (updates every 5 minutes)" # Save config for future runs local config_file="$JOPLIN_DATA_DIR/.duckdns.conf" cat >"$config_file" </dev/null 2>&1; then info "Installing Docker..." sudo pacman -S --needed --noconfirm docker docker-compose fi if ! systemctl is-active --quiet docker; then info "Starting Docker service..." sudo systemctl enable --now docker fi # Add user to docker group if not already a member if ! groups | grep -q '\bdocker\b'; then warn "Adding $USER to docker group (re-login required for group to take effect)." sudo usermod -aG docker "$USER" fi # Create data directory mkdir -p "$JOPLIN_DATA_DIR" local compose_file="$JOPLIN_DATA_DIR/docker-compose.yml" # Load saved DuckDNS config if it exists if [[ -f "$JOPLIN_DATA_DIR/.duckdns.conf" ]]; then # shellcheck source=/dev/null source "$JOPLIN_DATA_DIR/.duckdns.conf" fi # Set up DuckDNS for a stable hostname local server_url if setup_duckdns; then server_url="http://${DUCKDNS_DOMAIN}.duckdns.org:${JOPLIN_SERVER_PORT}" info "Using stable DuckDNS URL: $server_url" else # Fallback to public IP local host_ip host_ip="$(curl -s --max-time 5 ifconfig.me 2>/dev/null)" if [[ -z "$host_ip" ]]; then host_ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')" fi if [[ -z "$host_ip" ]]; then host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" fi if [[ -z "$host_ip" ]]; then warn "Could not detect external IP. Falling back to 0.0.0.0" host_ip="0.0.0.0" fi server_url="http://${host_ip}:${JOPLIN_SERVER_PORT}" warn "Using raw IP URL (may change!): $server_url" fi cat >"$compose_file" </dev/null 2>&1; then sudo ufw allow "${JOPLIN_SERVER_PORT}/tcp" || warn "Could not configure ufw" elif command -v firewall-cmd >/dev/null 2>&1; then if sudo firewall-cmd --permanent --add-port="${JOPLIN_SERVER_PORT}/tcp" && sudo firewall-cmd --reload; then : else warn "Could not configure firewalld" fi elif command -v iptables >/dev/null 2>&1; then sudo iptables -A INPUT -p tcp --dport "${JOPLIN_SERVER_PORT}" -j ACCEPT || warn "Could not configure iptables" else warn "No firewall tool found. Ensure port ${JOPLIN_SERVER_PORT}/tcp is open manually." fi echo "" echo " To connect Joplin desktop/Android to this server:" echo " 1. Open Joplin → Tools → Options → Synchronisation" echo " 2. Set target to 'Joplin Server'" echo " 3. Enter URL: ${server_url}" echo " 4. Enter your Joplin Server email and password" echo "" echo " Server management:" echo " Start: docker compose -f $compose_file up -d" echo " Stop: docker compose -f $compose_file down" echo " Logs: docker compose -f $compose_file logs -f" echo " Update: docker compose -f $compose_file pull && docker compose -f $compose_file up -d" }