From c658f4bd003fdb75d3b1baab186dc00df11c6cb2 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 29 Jun 2025 22:03:24 -0600 Subject: [PATCH] Updates to online workflow (moved cloudflare back to system service), full rework of homepage, updated a bunch of stuff in general --- config.sh | 256 +++--- .../843f83a4-247a-4c29-8a7e-cde50e2f4222.json | 1 + configs/cloudflare/tunnel-config.yml | 23 +- .../tunnel-config.yml.backup_20250629_154241 | 34 - .../tunnel-config.yml.backup_20250629_154929 | 34 - configs/homepage/logs/homepage.log | 511 ++++++++++++ configs/homepage/services.yaml | 115 ++- .../services.yaml.backup_20250629_154241 | 87 -- .../services.yaml.backup_20250629_154929 | 87 -- configs/homepage/settings.yaml | 9 + configs/homepage/widgets.yaml | 16 +- docker-compose.yml | 27 +- docs-cloudflare.md | 40 + map/docker-compose.yml | 6 + .../112206e21dc1efc3b2fdcaf783b198ac.png | Bin 0 -> 30791 bytes .../main.html.backup_20250629_154241 | 9 - .../main.html.backup_20250629_154929 | 9 - mkdocs/mkdocs.yml.backup_20250629_154929 | 66 -- start-production.sh | 748 ++++++++++++------ 19 files changed, 1304 insertions(+), 774 deletions(-) create mode 100644 configs/cloudflare/843f83a4-247a-4c29-8a7e-cde50e2f4222.json delete mode 100644 configs/cloudflare/tunnel-config.yml.backup_20250629_154241 delete mode 100644 configs/cloudflare/tunnel-config.yml.backup_20250629_154929 delete mode 100644 configs/homepage/services.yaml.backup_20250629_154241 delete mode 100644 configs/homepage/services.yaml.backup_20250629_154929 create mode 100644 docs-cloudflare.md create mode 100644 mkdocs/.cache/plugin/social/112206e21dc1efc3b2fdcaf783b198ac.png delete mode 100644 mkdocs/docs/overrides/main.html.backup_20250629_154241 delete mode 100644 mkdocs/docs/overrides/main.html.backup_20250629_154929 delete mode 100644 mkdocs/mkdocs.yml.backup_20250629_154929 mode change 100644 => 100755 start-production.sh diff --git a/config.sh b/config.sh index 9bbba49..bdf0670 100755 --- a/config.sh +++ b/config.sh @@ -25,6 +25,7 @@ TUNNEL_CONFIG_DIR="$SCRIPT_DIR/configs/cloudflare" TUNNEL_CONFIG_FILE="$TUNNEL_CONFIG_DIR/tunnel-config.yml" SERVICES_YAML="$SCRIPT_DIR/configs/homepage/services.yaml" MAIN_HTML="$SCRIPT_DIR/mkdocs/docs/overrides/main.html" +MAP_ENV_FILE="$SCRIPT_DIR/map/.env" # Add the map's .env file path echo "Looking for .env file at: $ENV_FILE" @@ -180,6 +181,8 @@ initialize_available_ports() { ["HOMEPAGE_PORT"]=3010 ["GITEA_WEB_PORT"]=3030 ["GITEA_SSH_PORT"]=2222 + ["MAP_PORT"]=3000 + ["MINI_QR_PORT"]=8089 ) # Find available ports for each service @@ -243,6 +246,8 @@ NOCODB_PORT=${NOCODB_PORT:-8090} HOMEPAGE_PORT=${HOMEPAGE_PORT:-3010} GITEA_WEB_PORT=${GITEA_WEB_PORT:-3030} GITEA_SSH_PORT=${GITEA_SSH_PORT:-2222} +MAP_PORT=${MAP_PORT:-3000} +MINI_QR_PORT=${MINI_QR_PORT:-8089} # Domain Configuration BASE_DOMAIN=https://changeme.org @@ -251,12 +256,18 @@ LISTMONK_HOSTNAME=listmonk.changeme.org N8N_HOST=n8n.changeme.org GITEA_DOMAIN=git.changeme.org GITEA_ROOT_URL=https://git.changeme.org +HOMEPAGE_VAR_BASE_URL=https://changeme.org + +# Cookie and CORS Configuration +COOKIE_DOMAIN=.changeme.org +ALLOWED_ORIGINS=https://map.changeme.org,http://localhost:3000 # Cloudflare Configuration CF_API_TOKEN=your_cloudflare_api_token CF_ZONE_ID=your_cloudflare_zone_id CF_TUNNEL_ID=your_cloudflared_tunnel_id CF_DOMAIN=changeme.org +CF_ACCOUNT_ID=your_cloudflare_account_id # Database Configuration (PostgreSQL for Listmonk) POSTGRES_USER=listmonk @@ -279,12 +290,14 @@ N8N_ENCRYPTION_KEY=changeMe GENERIC_TIMEZONE=UTC # Nocodb Configuration -NOCODB_PORT=${NOCODB_PORT:-8090} NOCODB_JWT_SECRET=changeMe NOCODB_DB_NAME=nocodb NOCODB_DB_USER=noco NOCODB_DB_PASSWORD=changeMe -HOMEPAGE_VAR_BASE_URL=https://changeme.org + +# Gitea Database Configuration +GITEA_DB_PASSWD=changeMe +GITEA_DB_ROOT_PASSWORD=changeMe EOL echo "New .env file created with conflict-free port assignments." @@ -302,6 +315,8 @@ EOL echo "Homepage: ${HOMEPAGE_PORT:-3010}" echo "Gitea Web: ${GITEA_WEB_PORT:-3030}" echo "Gitea SSH: ${GITEA_SSH_PORT:-2222}" + echo "Map: ${MAP_PORT:-3000}" + echo "Mini QR: ${MINI_QR_PORT:-8089}" echo "================================" } @@ -404,42 +419,55 @@ update_main_html() { return 0 } +# Function to get Cloudflare API Token and Zone ID +get_cloudflare_credentials() { + echo "" + echo "To use Cloudflare tunnels, you'll need to configure your credentials." + echo "" + echo "=== Cloudflare Configuration ===" + echo "" + echo "You'll need:" + echo "1. Your Cloudflare API Token (with Zone.DNS and Account.Cloudflare Tunnel permissions)" + echo "2. Your Cloudflare Zone ID (found in the Cloudflare dashboard overview)" + echo "3. Your Cloudflare Account ID (found in the Cloudflare dashboard)" + echo "" + echo "These credentials will be used for:" + echo "- Creating and configuring tunnels" + echo "- Managing DNS records" + echo "- Setting up access policies" + echo "" +} + # Function to update the tunnel configuration file with new domain update_tunnel_config() { local new_domain=$1 + local tunnel_id=${2:-"(insert-tunnel-id)"} + local creds_path=${3:-"/path/to/credentials/file"} - if [ ! -f "$TUNNEL_CONFIG_FILE" ]; then - echo "Warning: tunnel-config.yml not found at $TUNNEL_CONFIG_FILE" - echo "Creating new tunnel configuration file..." - - # Create the file if it doesn't exist - create_tunnel_config "$new_domain" "${CF_TUNNEL_ID:-\${CF_TUNNEL_ID}}" - return $? - fi + # Create directory if it doesn't exist + mkdir -p "$TUNNEL_CONFIG_DIR" echo "Updating tunnel configuration with new domain..." - # Create a backup of the tunnel-config.yml file - local timestamp=$(date +"%Y%m%d_%H%M%S") - local backup_file="${TUNNEL_CONFIG_FILE}.backup_${timestamp}" - cp "$TUNNEL_CONFIG_FILE" "$backup_file" - echo "Created backup of tunnel-config.yml at $backup_file" - - # Since the current file has all entries as cmlite.org pointing to port 3010, - # we need to recreate it with the proper configuration - echo "Regenerating tunnel configuration with correct service mappings..." + # Create a backup if file exists + if [ -f "$TUNNEL_CONFIG_FILE" ]; then + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_file="${TUNNEL_CONFIG_FILE}.backup_${timestamp}" + cp "$TUNNEL_CONFIG_FILE" "$backup_file" + echo "Created backup of tunnel-config.yml at $backup_file" + fi # Load current environment variables to get port numbers load_env_vars - # Recreate the tunnel configuration with proper mappings + # Create/update the tunnel configuration cat > "$TUNNEL_CONFIG_FILE" << EOL # filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml # Cloudflare Tunnel Configuration # Auto-generated by Changemaker Configuration Wizard -tunnel: ${CF_TUNNEL_ID:-\${CF_TUNNEL_ID}} # e.g. 1234567890abcdef -credentials-file: /home/coder/.cloudflared/${CF_TUNNEL_ID:-\${CF_TUNNEL_ID}}.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json +tunnel: $tunnel_id # e.g. 1234567890abcdef +credentials-file: $creds_path # e.g. /home/bunker-admin/.cloudflared/[insert tunnel number].json ingress: - hostname: homepage.$new_domain @@ -449,13 +477,13 @@ ingress: service: http://localhost:${CODE_SERVER_PORT:-8888} - hostname: listmonk.$new_domain - service: http://localhost:${LISTMONK_PORT:-9000} + service: http://localhost:${LISTMONK_PORT:-9001} - hostname: docs.$new_domain service: http://localhost:${MKDOCS_PORT:-4000} - hostname: $new_domain - service: http://localhost:${MKDOCS_SITE_SERVER_PORT:-4001} + service: http://localhost:${MKDOCS_SITE_SERVER_PORT:-4002} - hostname: n8n.$new_domain service: http://localhost:${N8N_PORT:-5678} @@ -466,64 +494,20 @@ ingress: - hostname: git.$new_domain service: http://localhost:${GITEA_WEB_PORT:-3030} + - hostname: map.$new_domain + service: http://localhost:${MAP_PORT:-3000} + + - hostname: qr.$new_domain + service: http://localhost:${MINI_QR_PORT:-8089} + # Catch-all rule (required) - service: http_status:404 EOL - echo "✅ Regenerated tunnel configuration with correct service mappings for domain: $new_domain" + echo "✅ Updated tunnel configuration for domain: $new_domain" return 0 } -# Function to get Cloudflare API Token and Zone ID -get_cloudflare_credentials() { - echo "" - echo "To use the DNS setup script, you'll need Cloudflare credentials." - echo "You can find these values in your Cloudflare dashboard:" - echo " - API Token: https://dash.cloudflare.com/profile/api-tokens" - echo " (Create a token with Zone:DNS:Edit and Access:Apps:Edit permissions)" - echo " - Zone ID: On your domain's overview page" - echo "" -} - -# Function to show tunnel setup instructions -show_tunnel_instructions() { - local domain=$1 - - echo "" - echo "=== Cloudflare Tunnel Setup Instructions ===" - echo "" - echo "To complete the tunnel setup:" - echo "" - echo "1. Install cloudflared on your server:" - echo " https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation" - echo "" - echo "2. Create a tunnel using the Cloudflare dashboard or run:" - echo " cloudflared tunnel create changemaker-tunnel" - echo "" - echo "3. Copy the credentials file to the correct location:" - echo " mkdir -p /home/coder/.cloudflared" - echo " cp ~/.cloudflared/[TUNNEL-ID].json /home/coder/.cloudflared/" - echo "" - echo "4. Update your .env with the correct CF_TUNNEL_ID if not already done" - echo "" - echo "5. Start the tunnel with your configuration file:" - echo " cloudflared tunnel --config $TUNNEL_CONFIG_FILE run" - echo "" - echo "6. After verifying it works, you can create a systemd service for automatic startup:" - echo " sudo cloudflared service install" - echo "" - echo "7. Your services will be available at the following URLs:" - echo " - Documentation: https://$domain" - echo " - Homepage: https://homepage.$domain" - echo " - Code Server: https://code.$domain" - echo " - Listmonk: https://listmonk.$domain" - echo " - N8N: https://n8n.$domain" - echo " - NocoDB: https://db.$domain" - echo " - MkDocs Dev: https://docs.$domain" - echo " - Gitea: https://git.$domain" - echo "" -} - # Function to load environment variables from .env file load_env_vars() { if [ -f "$ENV_FILE" ]; then @@ -536,6 +520,50 @@ load_env_vars() { fi } +# Function to update the map's .env file with domain settings +update_map_env() { + local new_domain=$1 + + if [ ! -f "$MAP_ENV_FILE" ]; then + echo "Warning: Map .env file not found at $MAP_ENV_FILE" + return 1 + fi + + echo "Updating map .env file with new domain settings..." + + # Create a backup of the map's .env file + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_file="${MAP_ENV_FILE}.backup_${timestamp}" + cp "$MAP_ENV_FILE" "$backup_file" + echo "Created backup of map .env at $backup_file" + + # Update COOKIE_DOMAIN + if grep -q "^COOKIE_DOMAIN=" "$MAP_ENV_FILE"; then + sed -i "s|^COOKIE_DOMAIN=.*|COOKIE_DOMAIN=.$new_domain|" "$MAP_ENV_FILE" + else + echo "COOKIE_DOMAIN=.$new_domain" >> "$MAP_ENV_FILE" + fi + + # Update ALLOWED_ORIGINS + local allowed_origins="https://map.$new_domain,http://localhost:3000" + if grep -q "^ALLOWED_ORIGINS=" "$MAP_ENV_FILE"; then + sed -i "s|^ALLOWED_ORIGINS=.*|ALLOWED_ORIGINS=$allowed_origins|" "$MAP_ENV_FILE" + else + echo "ALLOWED_ORIGINS=$allowed_origins" >> "$MAP_ENV_FILE" + fi + + # Also update the NOCODB URLs if they contain domain references + sed -i "s|example\.org|$new_domain|g" "$MAP_ENV_FILE" + sed -i "s|changeme\.org|$new_domain|g" "$MAP_ENV_FILE" + sed -i "s|albertademocracytaskforce\.org|$new_domain|g" "$MAP_ENV_FILE" + + echo "✅ Updated map .env file with:" + echo " - COOKIE_DOMAIN=.$new_domain" + echo " - ALLOWED_ORIGINS=$allowed_origins" + echo " - Updated all NocoDB URLs to use $new_domain" + return 0 +} + # Initialize a new .env file if it doesn't exist if [ ! -f "$ENV_FILE" ]; then echo "No .env file found. Creating a new one from scratch." @@ -579,8 +607,16 @@ update_env_var "CF_DOMAIN" "$domain_name" update_env_var "GITEA_DOMAIN" "git.$domain_name" update_env_var "GITEA_ROOT_URL" "https://git.$domain_name" +# Update cookie and CORS settings +update_env_var "COOKIE_DOMAIN" ".$domain_name" +update_env_var "ALLOWED_ORIGINS" "https://map.$domain_name,http://localhost:3000" + echo "Domain settings updated successfully!" +# Update the map's .env file +echo -e "\nUpdating map configuration..." +update_map_env "$domain_name" + # Cloudflare Configuration echo -e "\n---- Cloudflare Configuration ----" get_cloudflare_credentials @@ -588,35 +624,43 @@ get_cloudflare_credentials read -p "Do you want to configure Cloudflare settings now? [Y/n]: " configure_cf if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then - # Get Cloudflare API Token + # Get Cloudflare API token read -p "Enter your Cloudflare API Token: " cf_api_token - while [ -z "$cf_api_token" ] || [ "$cf_api_token" == "your_cloudflare_api_token" ]; do - echo "API Token is required. Please enter a valid token." - read -p "Enter your Cloudflare API Token: " cf_api_token - done - + if [ -z "$cf_api_token" ] || [ "$cf_api_token" = "your_cloudflare_api_token" ]; then + echo "Warning: API token is required for Cloudflare integration" + cf_api_token="your_cloudflare_api_token" + fi + update_env_var "CF_API_TOKEN" "$cf_api_token" + # Get Cloudflare Zone ID read -p "Enter your Cloudflare Zone ID: " cf_zone_id - while [ -z "$cf_zone_id" ] || [ "$cf_zone_id" == "your_cloudflare_zone_id" ]; do - echo "Zone ID is required. Please enter a valid Zone ID." - read -p "Enter your Cloudflare Zone ID: " cf_zone_id - done - - # Optional: Get Account ID now - read -p "Enter your Cloudflare Account ID (optional, can be added later): " cf_account_id - - # Update .env file with Cloudflare credentials - update_env_var "CF_API_TOKEN" "$cf_api_token" - update_env_var "CF_ZONE_ID" "$cf_zone_id" - update_env_var "CF_DOMAIN" "$domain_name" - - if [ ! -z "$cf_account_id" ]; then - update_env_var "CF_ACCOUNT_ID" "$cf_account_id" + if [ -z "$cf_zone_id" ] || [ "$cf_zone_id" = "your_cloudflare_zone_id" ]; then + echo "Warning: Zone ID is required for DNS configuration" + cf_zone_id="your_cloudflare_zone_id" fi + update_env_var "CF_ZONE_ID" "$cf_zone_id" + + # Get Cloudflare Account ID + read -p "Enter your Cloudflare Account ID: " cf_account_id + if [ -z "$cf_account_id" ] || [ "$cf_account_id" = "your_cloudflare_account_id" ]; then + echo "Warning: Account ID is required for tunnel operations" + cf_account_id="your_cloudflare_account_id" + fi + update_env_var "CF_ACCOUNT_ID" "$cf_account_id" - echo "Cloudflare credentials saved successfully!" + # Note: CF_TUNNEL_ID will be set by start-production.sh when the tunnel is created + update_env_var "CF_TUNNEL_ID" "will_be_set_by_start_production" + + echo "" + echo "Cloudflare configuration saved to .env file." + echo "" + echo "To complete the setup and deploy your services:" + echo "1. Start your services with: docker compose up -d" + echo "2. Run ./start-production.sh to configure the Cloudflare tunnel" + echo "" else - echo "Skipping Cloudflare configuration. You can configure it later by editing the .env file." + echo "Skipping Cloudflare configuration." + echo "You'll need to configure Cloudflare settings manually before running start-production.sh." fi # Update the site_url in mkdocs.yml @@ -631,10 +675,6 @@ update_services_yaml "$domain_name" echo -e "\nUpdating login URL in main.html..." update_main_html "$domain_name" -# Update the tunnel configuration file -echo -e "\nUpdating tunnel configuration..." -update_tunnel_config "$domain_name" - # Listmonk Admin Credentials configuration echo -e "\n---- Listmonk Admin Credentials ----" read -p "Enter Listmonk admin email/username [default: admin@example.com]: " listmonk_user @@ -692,7 +732,7 @@ update_env_var "NOCODB_JWT_SECRET" "$nocodb_jwt_secret" nocodb_db_password=$(generate_password 20) update_env_var "NOCODB_DB_PASSWORD" "$nocodb_db_password" -# Generate and update Gitea passwords +# Generate and update Gitea passwords (these should already exist in the file) gitea_db_password=$(generate_password 20) update_env_var "GITEA_DB_PASSWD" "$gitea_db_password" @@ -704,14 +744,16 @@ echo "Secure passwords generated and updated." echo -e "\n✅ Configuration completed successfully!" echo "Your .env file has been configured with:" echo "- Domain: $domain_name" +echo "- Cookie Domain: .$domain_name" +echo "- Allowed Origins: https://map.$domain_name,http://localhost:3000" +echo "- Map .env updated with domain settings" echo "- Listmonk Admin: $listmonk_user" echo "- N8N Admin Email: $n8n_email" echo "- Secure random passwords for database, encryption, and NocoDB" -if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then - echo "- Cloudflare credentials for DNS management" -fi +echo "- Tunnel configuration updated at: $TUNNEL_CONFIG_FILE" echo -e "\nYour .env file is located at: $ENV_FILE" echo "A backup of your original .env file was created before modifications." + echo "" echo "======================================" echo "Next Steps:" @@ -728,6 +770,8 @@ echo " - Documentation: http://localhost:${MKDOCS_PORT:-4000}" echo " - n8n: http://localhost:${N8N_PORT:-5678}" echo " - NocoDB: http://localhost:${NOCODB_PORT:-8090}" echo " - Gitea: http://localhost:${GITEA_WEB_PORT:-3030}" +echo " - Map: http://localhost:${MAP_PORT:-3000}" +echo " - Mini QR: http://localhost:${MINI_QR_PORT:-8089}" echo "" echo "3. When ready for production:" echo " ./start-production.sh" diff --git a/configs/cloudflare/843f83a4-247a-4c29-8a7e-cde50e2f4222.json b/configs/cloudflare/843f83a4-247a-4c29-8a7e-cde50e2f4222.json new file mode 100644 index 0000000..2142f0d --- /dev/null +++ b/configs/cloudflare/843f83a4-247a-4c29-8a7e-cde50e2f4222.json @@ -0,0 +1 @@ +{"AccountTag":"a421828402ca13fbcaad955f285f16f6","TunnelSecret":"949mtkdN202INtEohadCgnEe7QXykBL26dq2uQMt+HQ=","TunnelID":"843f83a4-247a-4c29-8a7e-cde50e2f4222","Endpoint":""} \ No newline at end of file diff --git a/configs/cloudflare/tunnel-config.yml b/configs/cloudflare/tunnel-config.yml index eb0639b..8e59c70 100644 --- a/configs/cloudflare/tunnel-config.yml +++ b/configs/cloudflare/tunnel-config.yml @@ -1,34 +1,27 @@ -# filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml -# Cloudflare Tunnel Configuration -# Auto-generated by Changemaker Configuration Wizard +# Cloudflare Tunnel Configuration for cmlite.org +# Generated by Changemaker.lite start-production.sh on Sun 29 Jun 2025 09:10:15 PM MDT -tunnel: your_cloudflared_tunnel_id} # e.g. 1234567890abcdef -credentials-file: /home/coder/.cloudflared/your_cloudflared_tunnel_id}.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json +tunnel: 843f83a4-247a-4c29-8a7e-cde50e2f4222 +credentials-file: /mnt/storagessd1tb/changemaker.lite.dev/changemaker.lite/configs/cloudflare/843f83a4-247a-4c29-8a7e-cde50e2f4222.json ingress: - - hostname: homepage.cmlite.org service: http://localhost:3010 - - hostname: code.cmlite.org service: http://localhost:8888 - - hostname: listmonk.cmlite.org service: http://localhost:9001 - - hostname: docs.cmlite.org service: http://localhost:4000 - - hostname: cmlite.org service: http://localhost:4002 - - hostname: n8n.cmlite.org service: http://localhost:5678 - - hostname: db.cmlite.org service: http://localhost:8090 - - hostname: git.cmlite.org service: http://localhost:3030 - - # Catch-all rule (required) + - hostname: map.cmlite.org + service: http://localhost:3000 + - hostname: qr.cmlite.org + service: http://localhost:8089 - service: http_status:404 diff --git a/configs/cloudflare/tunnel-config.yml.backup_20250629_154241 b/configs/cloudflare/tunnel-config.yml.backup_20250629_154241 deleted file mode 100644 index 4f28d65..0000000 --- a/configs/cloudflare/tunnel-config.yml.backup_20250629_154241 +++ /dev/null @@ -1,34 +0,0 @@ -# filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml -# Cloudflare Tunnel Configuration -# Auto-generated by Changemaker Configuration Wizard - -tunnel: ${CF_TUNNEL_ID} # e.g. 1234567890abcdef -credentials-file: /home/coder/.cloudflared/${CF_TUNNEL_ID}.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json -ingress: - - - hostname: homepage.test.com - service: http://localhost:3010 - - - hostname: code.test.com - service: http://localhost:8888 - - - hostname: listmonk.test.com - service: http://localhost:9001 - - - hostname: docs.test.com - service: http://localhost:4000 - - - hostname: test.com - service: http://localhost:4002 - - - hostname: n8n.test.com - service: http://localhost:5678 - - - hostname: db.test.com - service: http://localhost:8090 - - - hostname: git.test.com - service: http://localhost:3030 - - # Catch-all rule (required) - - service: http_status:404 diff --git a/configs/cloudflare/tunnel-config.yml.backup_20250629_154929 b/configs/cloudflare/tunnel-config.yml.backup_20250629_154929 deleted file mode 100644 index 1e29c9d..0000000 --- a/configs/cloudflare/tunnel-config.yml.backup_20250629_154929 +++ /dev/null @@ -1,34 +0,0 @@ -# filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml -# Cloudflare Tunnel Configuration -# Auto-generated by Changemaker Configuration Wizard - -tunnel: ${CF_TUNNEL_ID} # e.g. 1234567890abcdef -credentials-file: /home/coder/.cloudflared/${CF_TUNNEL_ID}.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json -ingress: - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - - hostname: cmlite.org - service: http://localhost:3010 - - # Catch-all rule (required) - - service: http_status:404 diff --git a/configs/homepage/logs/homepage.log b/configs/homepage/logs/homepage.log index 0de4c20..3caeca4 100644 --- a/configs/homepage/logs/homepage.log +++ b/configs/homepage/logs/homepage.log @@ -190,3 +190,514 @@ [2025-05-28T14:56:56.936Z] error: Error: connect EACCES /var/run/docker.sock at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-06-30T03:14:15.516Z] error: undefined +[2025-06-30T03:14:15.521Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:14:21.458Z] error: undefined +[2025-06-30T03:14:21.460Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:14:26.553Z] error: undefined +[2025-06-30T03:14:26.554Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:14:41.640Z] error: undefined +[2025-06-30T03:14:41.641Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:15:25.889Z] error: undefined +[2025-06-30T03:15:25.891Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:15:31.720Z] error: undefined +[2025-06-30T03:15:31.721Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:15:35.980Z] error: undefined +[2025-06-30T03:15:35.981Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:16:01.061Z] error: undefined +[2025-06-30T03:16:01.062Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:16:11.809Z] error: undefined +[2025-06-30T03:16:11.810Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:16:26.149Z] error: undefined +[2025-06-30T03:16:26.150Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:17:07.675Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:17:07.675Z] error: YAMLException: bad indentation of a sequence entry (15:5) + + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | showStats: false + 15 | - Listmonk: +----------^ + 16 | href: "http://localhost:9000" + 17 | # href: "https://cmlite.org ... +[2025-06-30T03:17:07.755Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:17:07.757Z] error: YAMLException: bad indentation of a sequence entry (15:5) + + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | showStats: false + 15 | - Listmonk: +----------^ + 16 | href: "http://localhost:9000" + 17 | # href: "https://cmlite.org ... + at generateError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at throwError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1277:9) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2105:7) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2562:45) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2098:5) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2530:12) + at readDocument (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2715:3) + at loadDocuments (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2778:5) +[2025-06-30T03:17:07.756Z] error: YAMLException: bad indentation of a sequence entry (15:5) + + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | showStats: false + 15 | - Listmonk: +----------^ + 16 | href: "http://localhost:9000" + 17 | # href: "https://cmlite.org ... +[2025-06-30T03:17:08.098Z] error: YAMLException: bad indentation of a sequence entry (15:5) + + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | showStats: false + 15 | - Listmonk: +----------^ + 16 | href: "http://localhost:9000" + 17 | # href: "https://cmlite.org ... + at generateError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at throwError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1277:9) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2105:7) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2562:45) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2098:5) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2530:12) + at readDocument (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2715:3) + at loadDocuments (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2778:5) +[2025-06-30T03:17:08.161Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:17:08.161Z] error: YAMLException: bad indentation of a sequence entry (15:5) + + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | showStats: false + 15 | - Listmonk: +----------^ + 16 | href: "http://localhost:9000" + 17 | # href: "https://cmlite.org ... +[2025-06-30T03:17:15.271Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:17:15.271Z] error: YAMLException: bad indentation of a sequence entry (14:5) + + 11 | type: docker + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | - Listmonk: +----------^ + 15 | href: "http://localhost:9000" + 16 | # href: "https://cmlite.org ... +[2025-06-30T03:17:15.511Z] error: YAMLException: bad indentation of a sequence entry (14:5) + + 11 | type: docker + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | - Listmonk: +----------^ + 15 | href: "http://localhost:9000" + 16 | # href: "https://cmlite.org ... + at generateError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at throwError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1277:9) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2105:7) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2562:45) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2098:5) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2530:12) + at readDocument (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2715:3) + at loadDocuments (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2778:5) +[2025-06-30T03:17:15.572Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:17:15.573Z] error: YAMLException: bad indentation of a sequence entry (14:5) + + 11 | type: docker + 12 | container: code-server-change ... + 13 | server: my-docker + 14 | - Listmonk: +----------^ + 15 | href: "http://localhost:9000" + 16 | # href: "https://cmlite.org ... +[2025-06-30T03:17:45.214Z] error: undefined +[2025-06-30T03:17:45.216Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:17:50.333Z] error: undefined +[2025-06-30T03:17:50.334Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:17:56.035Z] error: undefined +[2025-06-30T03:17:56.036Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:18:01.448Z] error: undefined +[2025-06-30T03:18:01.449Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:18:15.473Z] error: undefined +[2025-06-30T03:18:15.474Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:18:36.889Z] error: undefined +[2025-06-30T03:18:36.890Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:19:00.275Z] error: undefined +[2025-06-30T03:19:00.276Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:21:04.087Z] error: YAMLException: bad indentation of a mapping entry (10:20) + + 7 | # href: "https://cmlite.org" # Uncomment ... + 8 | description: VS Code in the browser + 9 | icon: mdi-code-braces + 10 | container: code-server-changemaker +-------------------------^ + 11 | server: my-docker + 12 | - Listmonk: + at generateError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at throwError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1277:9) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2272:7) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2098:5) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2530:12) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) +[2025-06-30T03:21:04.511Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:21:04.511Z] error: YAMLException: bad indentation of a mapping entry (10:20) + + 7 | # href: "https://cmlite.org" # Uncomment ... + 8 | description: VS Code in the browser + 9 | icon: mdi-code-braces + 10 | container: code-server-changemaker +-------------------------^ + 11 | server: my-docker + 12 | - Listmonk: +[2025-06-30T03:21:04.585Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:21:04.585Z] error: YAMLException: bad indentation of a mapping entry (10:20) + + 7 | # href: "https://cmlite.org" # Uncomment ... + 8 | description: VS Code in the browser + 9 | icon: mdi-code-braces + 10 | container: code-server-changemaker +-------------------------^ + 11 | server: my-docker + 12 | - Listmonk: +[2025-06-30T03:21:04.867Z] error: YAMLException: bad indentation of a mapping entry (10:20) + + 7 | # href: "https://cmlite.org" # Uncomment ... + 8 | description: VS Code in the browser + 9 | icon: mdi-code-braces + 10 | container: code-server-changemaker +-------------------------^ + 11 | server: my-docker + 12 | - Listmonk: + at generateError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at throwError (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:1277:9) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2272:7) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) + at readBlockSequence (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2098:5) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2530:12) + at readBlockMapping (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2254:11) + at composeNode (file:///app/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs:2531:12) +[2025-06-30T03:21:04.954Z] error: Failed to load services.yaml, please check for errors +[2025-06-30T03:21:04.954Z] error: YAMLException: bad indentation of a mapping entry (10:20) + + 7 | # href: "https://cmlite.org" # Uncomment ... + 8 | description: VS Code in the browser + 9 | icon: mdi-code-braces + 10 | container: code-server-changemaker +-------------------------^ + 11 | server: my-docker + 12 | - Listmonk: +[2025-06-30T03:21:12.819Z] error: undefined +[2025-06-30T03:21:12.820Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:21:13.108Z] error: undefined +[2025-06-30T03:21:13.110Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:21:18.234Z] error: undefined +[2025-06-30T03:21:18.236Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:21:23.680Z] error: undefined +[2025-06-30T03:21:23.681Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:21:38.468Z] error: undefined +[2025-06-30T03:21:38.469Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:22:04.123Z] error: undefined +[2025-06-30T03:22:04.125Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:22:09.222Z] error: undefined +[2025-06-30T03:22:09.223Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:22:31.975Z] error: undefined +[2025-06-30T03:22:31.976Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:22:34.309Z] error: undefined +[2025-06-30T03:22:34.310Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:22:56.905Z] error: undefined +[2025-06-30T03:22:56.907Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) +[2025-06-30T03:23:24.463Z] error: undefined +[2025-06-30T03:23:24.464Z] error: TypeError: Invalid URL + at new URL (node:internal/url:818:25) + at d (/app/.next/server/pages/api/releases.js:1:5196) + at h (/app/.next/server/pages/api/services/proxy.js:45:33437) + at async X (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:18441) + at async z.render (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/pages-api.runtime.prod.js:20:19237) + at async NextNodeServer.runApi (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:671:9) + at async NextNodeServer.handleCatchallRenderRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/next-server.js:302:37) + at async NextNodeServer.handleRequestImpl (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/base-server.js:899:17) + at async invokeRender (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:237:21) + at async handleRequest (/app/node_modules/.pnpm/next@15.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/server/lib/router-server.js:428:24) diff --git a/configs/homepage/services.yaml b/configs/homepage/services.yaml index 3c72435..845093b 100644 --- a/configs/homepage/services.yaml +++ b/configs/homepage/services.yaml @@ -2,86 +2,73 @@ # For public access, replace "http://localhost" with your subdomain URLs - Essential Tools: + - Code Server: - href: "http://localhost:8888" - # href: "https://cmlite.org" # Uncomment for public access - description: VS Code in the browser icon: mdi-code-braces - widget: - type: docker - container: code-server-changemaker - server: my-docker + href: "https://code.cmlite.org" + description: VS Code in the browser - Platform Editor + container: code-server-changemaker + - Listmonk: - href: "http://localhost:9000" - # href: "https://cmlite.org" # Uncomment for public access - description: Newsletter & mailing list manager icon: mdi-email-newsletter - widget: - type: docker - container: listmonk_app - server: my-docker + href: "https://listmonk.cmlite.org" + description: Newsletter & mailing list manager + container: listmonk_app + - NocoDB: - href: "http://localhost:8090" - # href: "https://cmlite.org" # Uncomment for public access - description: No-code database platform icon: mdi-database - widget: - type: docker - container: changemakerlite-nocodb-1 - server: my-docker - - Gitea: - href: "http://localhost:3030" - # href: "https://cmlite.org" # Uncomment for public access - description: Git repository hosting - icon: mdi-git - widget: - type: docker - container: gitea_changemaker - server: my-docker + href: "https://db.cmlite.org" + description: No-code database platform + container: changemakerlite-nocodb-1 + + - Map Server: + icon: mdi-map + href: "https://map.cmlite.org" + description: Map server for geospatial data + container: nocodb-map-viewer + - Content & Documentation: - - MkDocs (Live): - href: "http://localhost:4000" - # href: "https://cmlite.org" # Uncomment for public access - description: Live documentation server with hot reload - icon: mdi-book-open-page-variant - widget: - type: docker - container: mkdocs-changemaker - server: my-docker - - Static Site: - href: "http://localhost:4001" - # href: "https://cmlite.org" # Uncomment for public access - description: Built documentation hosting + - Main Site: icon: mdi-web - widget: - type: docker - container: mkdocs-site-server-changemaker - server: my-docker + href: "https://cmlite.org" + description: CM-lite campaign website + container: mkdocs-site-server-changemaker + + - MkDocs (Live): + icon: mdi-book-open-page-variant + href: "https://docs.cmlite.org" + description: Live documentation server with hot reload + container: mkdocs-changemaker + + - Mini QR: + icon: mdi-qrcode + href: "https://qr.cmlite.org" + description: QR code generator + container: mini-qr + - Automation & Infrastructure: - n8n: - href: "http://localhost:5678" - # href: "https://cmlite.org" # Uncomment for public access + icon: mdi-robot-industrial + href: "https://n8n.cmlite.org" description: Workflow automation platform - icon: mdi-workflow - widget: - type: docker - container: n8n-changemaker - server: my-docker + container: n8n-changemaker + - PostgreSQL (Listmonk): + icon: mdi-database-outline href: "#" description: Database for Listmonk - icon: mdi-database-outline - widget: - type: docker - container: listmonk_db - server: my-docker + container: listmonk_db + - PostgreSQL (NocoDB): + icon: mdi-database-outline href: "#" description: Database for NocoDB - icon: mdi-database-outline - widget: - type: docker - container: changemakerlite-root_db-1 - server: my-docker + container: changemakerlite-root_db-1 + + - Gitea: + icon: mdi-git + href: "https://git.cmlite.org" + description: Git repository hosting + container: gitea_changemaker \ No newline at end of file diff --git a/configs/homepage/services.yaml.backup_20250629_154241 b/configs/homepage/services.yaml.backup_20250629_154241 deleted file mode 100644 index ea833ac..0000000 --- a/configs/homepage/services.yaml.backup_20250629_154241 +++ /dev/null @@ -1,87 +0,0 @@ ---- -# For public access, replace "http://localhost" with your subdomain URLs - -- Essential Tools: - - Code Server: - href: "http://localhost:8888" - # href: "https://code.albertademocracytaskforce.org" # Uncomment for public access - description: VS Code in the browser - icon: mdi-code-braces - widget: - type: docker - container: code-server-changemaker - server: my-docker - - Listmonk: - href: "http://localhost:9000" - # href: "https://listmonk.albertademocracytaskforce.org" # Uncomment for public access - description: Newsletter & mailing list manager - icon: mdi-email-newsletter - widget: - type: docker - container: listmonk_app - server: my-docker - - NocoDB: - href: "http://localhost:8090" - # href: "https://db.albertademocracytaskforce.org" # Uncomment for public access - description: No-code database platform - icon: mdi-database - widget: - type: docker - container: changemakerlite-nocodb-1 - server: my-docker - - Gitea: - href: "http://localhost:3030" - # href: "https://git.albertademocracytaskforce.org" # Uncomment for public access - description: Git repository hosting - icon: mdi-git - widget: - type: docker - container: gitea_changemaker - server: my-docker - -- Content & Documentation: - - MkDocs (Live): - href: "http://localhost:4000" - # href: "https://docs.albertademocracytaskforce.org" # Uncomment for public access - description: Live documentation server with hot reload - icon: mdi-book-open-page-variant - widget: - type: docker - container: mkdocs-changemaker - server: my-docker - - Static Site: - href: "http://localhost:4001" - # href: "https://albertademocracytaskforce.org" # Uncomment for public access - description: Built documentation hosting - icon: mdi-web - widget: - type: docker - container: mkdocs-site-server-changemaker - server: my-docker - -- Automation & Infrastructure: - - n8n: - href: "http://localhost:5678" - # href: "https://n8n.albertademocracytaskforce.org" # Uncomment for public access - description: Workflow automation platform - icon: mdi-workflow - widget: - type: docker - container: n8n-changemaker - server: my-docker - - PostgreSQL (Listmonk): - href: "#" - description: Database for Listmonk - icon: mdi-database-outline - widget: - type: docker - container: listmonk_db - server: my-docker - - PostgreSQL (NocoDB): - href: "#" - description: Database for NocoDB - icon: mdi-database-outline - widget: - type: docker - container: changemakerlite-root_db-1 - server: my-docker diff --git a/configs/homepage/services.yaml.backup_20250629_154929 b/configs/homepage/services.yaml.backup_20250629_154929 deleted file mode 100644 index ea833ac..0000000 --- a/configs/homepage/services.yaml.backup_20250629_154929 +++ /dev/null @@ -1,87 +0,0 @@ ---- -# For public access, replace "http://localhost" with your subdomain URLs - -- Essential Tools: - - Code Server: - href: "http://localhost:8888" - # href: "https://code.albertademocracytaskforce.org" # Uncomment for public access - description: VS Code in the browser - icon: mdi-code-braces - widget: - type: docker - container: code-server-changemaker - server: my-docker - - Listmonk: - href: "http://localhost:9000" - # href: "https://listmonk.albertademocracytaskforce.org" # Uncomment for public access - description: Newsletter & mailing list manager - icon: mdi-email-newsletter - widget: - type: docker - container: listmonk_app - server: my-docker - - NocoDB: - href: "http://localhost:8090" - # href: "https://db.albertademocracytaskforce.org" # Uncomment for public access - description: No-code database platform - icon: mdi-database - widget: - type: docker - container: changemakerlite-nocodb-1 - server: my-docker - - Gitea: - href: "http://localhost:3030" - # href: "https://git.albertademocracytaskforce.org" # Uncomment for public access - description: Git repository hosting - icon: mdi-git - widget: - type: docker - container: gitea_changemaker - server: my-docker - -- Content & Documentation: - - MkDocs (Live): - href: "http://localhost:4000" - # href: "https://docs.albertademocracytaskforce.org" # Uncomment for public access - description: Live documentation server with hot reload - icon: mdi-book-open-page-variant - widget: - type: docker - container: mkdocs-changemaker - server: my-docker - - Static Site: - href: "http://localhost:4001" - # href: "https://albertademocracytaskforce.org" # Uncomment for public access - description: Built documentation hosting - icon: mdi-web - widget: - type: docker - container: mkdocs-site-server-changemaker - server: my-docker - -- Automation & Infrastructure: - - n8n: - href: "http://localhost:5678" - # href: "https://n8n.albertademocracytaskforce.org" # Uncomment for public access - description: Workflow automation platform - icon: mdi-workflow - widget: - type: docker - container: n8n-changemaker - server: my-docker - - PostgreSQL (Listmonk): - href: "#" - description: Database for Listmonk - icon: mdi-database-outline - widget: - type: docker - container: listmonk_db - server: my-docker - - PostgreSQL (NocoDB): - href: "#" - description: Database for NocoDB - icon: mdi-database-outline - widget: - type: docker - container: changemakerlite-root_db-1 - server: my-docker diff --git a/configs/homepage/settings.yaml b/configs/homepage/settings.yaml index 3ed96b3..034c9c9 100755 --- a/configs/homepage/settings.yaml +++ b/configs/homepage/settings.yaml @@ -21,6 +21,15 @@ layout: style: columns columns: 3 +docker: + widget: + # Set this to false to hide CPU, memory, and network stats + showStats: false + + # You can still keep health indicators + showHealth: true + + quicklaunch: searchDescriptions: true hideInternetSearch: true diff --git a/configs/homepage/widgets.yaml b/configs/homepage/widgets.yaml index 6b53a00..cfb5950 100644 --- a/configs/homepage/widgets.yaml +++ b/configs/homepage/widgets.yaml @@ -6,6 +6,16 @@ cpu: true memory: true disk: / + +- openmeteo: + label: Edmonton # optional + latitude: 53.5461 + longitude: -113.4938 + timezone: America/Edmonton # optional + units: metric # or imperial + cache: 5 # Time in minutes to cache API responses, to stay within limits + format: # optional, Intl.NumberFormat options + maximumFractionDigits: 1 - greeting: text_size: xl @@ -20,8 +30,4 @@ - search: provider: duckduckgo - target: _blank - -- unifi_console: - text_size: md - text: "Services Available: Code Server, Listmonk, NocoDB, MkDocs, n8n, Gitea" + target: _blank \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c0035b4..36054b4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -230,25 +230,14 @@ services: timeout: 5s retries: 5 - # Cloudflare Tunnel (uncommented by start-production.sh) - # cloudflared: - # image: cloudflare/cloudflared:latest - # container_name: cloudflared-changemaker - # restart: unless-stopped - # command: tunnel run - # environment: - # - TUNNEL_TOKEN=${CF_TUNNEL_TOKEN} - # networks: - # - changemaker-lite - # depends_on: - # - homepage-changemaker - # - code-server - # - listmonk-app - # - mkdocs - # - mkdocs-site-server - # - n8n - # - nocodb - # - gitea-app + mini-qr: + image: ghcr.io/lyqht/mini-qr:latest + container_name: mini-qr + ports: + - "${MINI_QR_PORT:-8089}:8080" + restart: unless-stopped + networks: + - changemaker-lite networks: changemaker-lite: diff --git a/docs-cloudflare.md b/docs-cloudflare.md new file mode 100644 index 0000000..a56e45d --- /dev/null +++ b/docs-cloudflare.md @@ -0,0 +1,40 @@ +# Cloudflare Configuration Guide for Changemaker.lite + +This guide will help you properly configure Cloudflare credentials for use with Changemaker.lite's production deployment. + +## Finding Your Zone ID + +The Zone ID is a unique identifier for your domain in Cloudflare. + +1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com) +2. Select your domain (e.g., cmlite.org) +3. On the right sidebar under "API", you'll see "Zone ID" +4. Copy this value - it should look something like: `1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p` + +## Creating an API Token + +You need an API token with proper permissions to manage DNS records and access policies. + +1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com) > Profile (top right) > API Tokens +2. Click "Create Token" +3. Use the "Edit zone DNS" template (or create a custom token) +4. For a custom token, ensure it has the following permissions: + - Zone - DNS - Edit + - Zone - Settings - Read + - Account - Cloudflare Tunnel - Read +5. Under "Zone Resources", select "Include - Specific zone" and choose your domain +6. Create the token and copy its value + +## Finding Your Account ID + +The Account ID is needed for some Cloudflare Tunnel operations. + +1. Go to the [Cloudflare Dashboard](https://dash.cloudflare.com) +2. Look at the URL when logged in - it will contain your account ID: + `https://dash.cloudflare.com/1a2b3c4d5e6f7g8h9i0j/your-domain.com` +3. The string after `/dash.cloudflare.com/` is your Account ID (e.g., `1a2b3c4d5e6f7g8h9i0j`) + +## Updating Your .env File + +Update your `.env` file with these values: + diff --git a/map/docker-compose.yml b/map/docker-compose.yml index ff49eba..71edb80 100644 --- a/map/docker-compose.yml +++ b/map/docker-compose.yml @@ -25,3 +25,9 @@ services: options: max-size: "10m" max-file: "3" + networks: + - changemakerlite_changemaker-lite + +networks: + changemakerlite_changemaker-lite: + external: true diff --git a/mkdocs/.cache/plugin/social/112206e21dc1efc3b2fdcaf783b198ac.png b/mkdocs/.cache/plugin/social/112206e21dc1efc3b2fdcaf783b198ac.png new file mode 100644 index 0000000000000000000000000000000000000000..ea7810f6d024f0686b094d4f7b05f6a6583d6ae0 GIT binary patch literal 30791 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 O8mc-M-=Dwn$A17yf4ATO literal 0 HcmV?d00001 diff --git a/mkdocs/docs/overrides/main.html.backup_20250629_154241 b/mkdocs/docs/overrides/main.html.backup_20250629_154241 deleted file mode 100644 index 732197a..0000000 --- a/mkdocs/docs/overrides/main.html.backup_20250629_154241 +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block extrahead %} -{% endblock %} - -{% block announce %} - -Changemaker Archive. Learn more -{% endblock %} diff --git a/mkdocs/docs/overrides/main.html.backup_20250629_154929 b/mkdocs/docs/overrides/main.html.backup_20250629_154929 deleted file mode 100644 index 732197a..0000000 --- a/mkdocs/docs/overrides/main.html.backup_20250629_154929 +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block extrahead %} -{% endblock %} - -{% block announce %} - -Changemaker Archive. Learn more -{% endblock %} diff --git a/mkdocs/mkdocs.yml.backup_20250629_154929 b/mkdocs/mkdocs.yml.backup_20250629_154929 deleted file mode 100644 index 5c2ec6c..0000000 --- a/mkdocs/mkdocs.yml.backup_20250629_154929 +++ /dev/null @@ -1,66 +0,0 @@ -site_name: Changemaker Lite Documentation -site_description: Self-hosted platform for documentation and development -site_url: https://changeme.org -site_author: Bunker Ops -docs_dir: docs -site_dir: site - -# Theme -theme: - name: material - custom_dir: docs/overrides - palette: - scheme: slate - primary: deep purple - accent: amber - features: - - navigation.tracking - - navigation.indexes - - navigation.collapse - - navigation.path - - content.code.copy - - navigation.top - - navigation.tabs # Added for top-level navigation tabs - -extra_css: - - stylesheets/extra.css -markdown_extensions: - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.superfences - - admonition - - pymdownx.details - - attr_list - - md_in_html - - pymdownx.emoji # Simplified emoji config - - footnotes - - toc: - permalink: true - # The specific slugify line was removed to avoid previous tool error, - # you may need to add back your preferred slugify option: - # slugify: !!python/name:pymdownx.slugs.uslugify - -copyright: Copyright © 2024 The Bunker Operations - Built with Change Maker - -# Plugins -plugins: - - social - - search - - blog - # - tags # Consider adding if you use tags for your blog or docs - -# Navigation -nav: - - Home: index.md - - Getting Started: getting-started.md - - Services: - - Overview: services/index.md - - Homepage: services/homepage.md - - Code Server: services/code-server.md - - MkDocs Material: services/mkdocs.md - - Static Site Server: services/static-server.md - - Listmonk: services/listmonk.md - - PostgreSQL: services/postgresql.md - - n8n: services/n8n.md - - NocoDB: services/nocodb.md - - Blog: blog/index.md diff --git a/start-production.sh b/start-production.sh old mode 100644 new mode 100755 index 02e1361..4b931e2 --- a/start-production.sh +++ b/start-production.sh @@ -2,13 +2,13 @@ echo "#############################################################" echo "# " -echo "# Changemaker.lite Production Setup " +echo "# Changemaker.lite Production Deployment " echo "# " echo "# This script will: " -echo "# 1. Create a Cloudflare tunnel " -echo "# 2. Configure DNS records " -echo "# 3. Set up access policies " -echo "# 4. Enable the cloudflared container " +echo "# 1. Install and configure cloudflared " +echo "# 2. Create a systemd service for the tunnel " +echo "# 3. Configure DNS records " +echo "# 4. Set up access policies " echo "# " echo "#############################################################" echo "" @@ -16,7 +16,9 @@ echo "" # Get script directory SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ENV_FILE="$SCRIPT_DIR/.env" -DOCKER_COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" +TUNNEL_CONFIG_FILE="$SCRIPT_DIR/configs/cloudflare/tunnel-config.yml" +TUNNEL_CREDS_DIR="$SCRIPT_DIR/configs/cloudflare" +SERVICE_NAME="cloudflared-changemaker" # Source environment variables if [ -f "$ENV_FILE" ]; then @@ -29,191 +31,332 @@ fi # Check if Cloudflare credentials are properly configured if [ -z "$CF_API_TOKEN" ] || [ "$CF_API_TOKEN" == "your_cloudflare_api_token" ] || \ [ -z "$CF_ZONE_ID" ] || [ "$CF_ZONE_ID" == "your_cloudflare_zone_id" ] || \ - [ -z "$CF_DOMAIN" ]; then - echo "Error: Cloudflare credentials not properly configured in .env file." + [ -z "$CF_DOMAIN" ] || [ -z "$CF_ACCOUNT_ID" ] || [ "$CF_ACCOUNT_ID" == "your_cloudflare_account_id" ]; then + echo "Error: Cloudflare configuration incomplete." echo "" echo "Current values:" echo " CF_API_TOKEN: ${CF_API_TOKEN:-not set}" echo " CF_ZONE_ID: ${CF_ZONE_ID:-not set}" echo " CF_DOMAIN: ${CF_DOMAIN:-not set}" + echo " CF_ACCOUNT_ID: ${CF_ACCOUNT_ID:-not set}" echo "" - echo "Please run ./config.sh and configure Cloudflare settings, or manually update your .env file." + echo "Please run ./config.sh and complete the Cloudflare configuration." exit 1 fi -# Check if CF_ACCOUNT_ID exists in .env, if not prompt for it -if [ -z "$CF_ACCOUNT_ID" ]; then - echo "Cloudflare Account ID is required for tunnel creation." - echo "You can find it in your Cloudflare dashboard at the top right." - read -p "Enter your Cloudflare Account ID: " CF_ACCOUNT_ID - - # Update .env with account ID - echo "" >> "$ENV_FILE" - echo "# Cloudflare Account ID (added by start-production.sh)" >> "$ENV_FILE" - echo "CF_ACCOUNT_ID=$CF_ACCOUNT_ID" >> "$ENV_FILE" - - # Re-export the variable - export CF_ACCOUNT_ID -fi - -# Check if required variables are set -if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ZONE_ID" ] || [ -z "$CF_DOMAIN" ]; then - echo "Error: Cloudflare credentials not found in .env file." - echo "Please run ./config.sh and configure Cloudflare settings." - exit 1 -fi - -# Check if services are running -echo "Checking if services are running..." -if ! docker compose ps | grep -q "Up"; then - echo "Error: No services are running. Please run 'docker compose up -d' first." - exit 1 -fi - -echo "✓ Services are running" -echo "" - -# Show current service URLs -echo "Current local service URLs:" -echo " - Homepage: http://localhost:${HOMEPAGE_PORT:-3010}" -echo " - Code Server: http://localhost:${CODE_SERVER_PORT:-8888}" -echo " - Listmonk: http://localhost:${LISTMONK_PORT:-9000}" -echo " - Documentation (Dev): http://localhost:${MKDOCS_PORT:-4000}" -echo " - Documentation (Built): http://localhost:${MKDOCS_SITE_SERVER_PORT:-4001}" -echo " - n8n: http://localhost:${N8N_PORT:-5678}" -echo " - NocoDB: http://localhost:${NOCODB_PORT:-8090}" -echo " - Gitea: http://localhost:${GITEA_WEB_PORT:-3030}" -echo "" - -read -p "Have you tested all services locally and are ready to go to production? (yes/no): " confirm -if [[ "$confirm" != "yes" ]]; then - echo "Please test all services locally before running production setup." - exit 0 -fi - -echo "" -echo "Starting production setup..." - -# Install jq if not present -if ! command -v jq &> /dev/null; then - echo "Installing jq..." - sudo apt-get update && sudo apt-get install -y jq -fi - -# Create Cloudflare tunnel -echo "" -echo "Creating Cloudflare tunnel..." - -TUNNEL_NAME="changemaker-${HOSTNAME}-$(date +%s)" - -# Create tunnel via API -TUNNEL_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/cfd_tunnel" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\"}") - -# Check if we need account ID -if echo "$TUNNEL_RESPONSE" | jq -e '.errors[] | select(.code == 0)' > /dev/null 2>&1; then - echo "" - echo "Account ID is required. You can find it in your Cloudflare dashboard." - read -p "Enter your Cloudflare Account ID: " CF_ACCOUNT_ID - - # Update .env with account ID - if grep -q "^CF_ACCOUNT_ID=" "$ENV_FILE"; then - sed -i "s/^CF_ACCOUNT_ID=.*/CF_ACCOUNT_ID=$CF_ACCOUNT_ID/" "$ENV_FILE" +# Check if cloudflared is installed +check_cloudflared() { + if ! command -v cloudflared &> /dev/null; then + echo "cloudflared not found. Installing..." + install_cloudflared else - echo "CF_ACCOUNT_ID=$CF_ACCOUNT_ID" >> "$ENV_FILE" + echo "✅ cloudflared is already installed: $(cloudflared --version | head -n1)" + fi +} + +# Install cloudflared +install_cloudflared() { + echo "Installing cloudflared..." + + # Check if we need to add the Cloudflare repository + if [ ! -f /usr/share/keyrings/cloudflare-main.gpg ]; then + # Add the cloudflare gpg key + curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg + + # Add the repository + echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list fi - # Retry with account ID - TUNNEL_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\"}") -fi - -TUNNEL_ID=$(echo "$TUNNEL_RESPONSE" | jq -r '.result.id') -TUNNEL_TOKEN=$(echo "$TUNNEL_RESPONSE" | jq -r '.result.token') - -if [ -z "$TUNNEL_ID" ] || [ "$TUNNEL_ID" == "null" ]; then - echo "Error: Failed to create tunnel" - echo "$TUNNEL_RESPONSE" | jq . - exit 1 -fi - -echo "✓ Created tunnel: $TUNNEL_NAME (ID: $TUNNEL_ID)" - -# Update .env with tunnel information -echo "CF_TUNNEL_ID=$TUNNEL_ID" >> "$ENV_FILE" -echo "CF_TUNNEL_TOKEN=$TUNNEL_TOKEN" >> "$ENV_FILE" -echo "CF_TUNNEL_NAME=$TUNNEL_NAME" >> "$ENV_FILE" - -# Configure tunnel routes -echo "" -echo "Configuring tunnel routes..." - -# Define services and their configurations -declare -A SERVICES=( - ["dashboard"]="homepage-changemaker:3000" - ["code"]="code-server:8080" - ["listmonk"]="listmonk-app:9000" - ["docs"]="mkdocs:8000" - ["n8n"]="n8n:5678" - ["db"]="nocodb:8080" - ["git"]="gitea-app:3000" -) - -# Configure root domain to mkdocs-site-server -echo "Configuring route for root domain..." -ROOT_CONFIG=$(cat < $service" + # Update and install + sudo apt-get update && sudo apt-get install -y cloudflared - CONFIG=$(cat < /dev/null; then + echo "Failed to install cloudflared. Please install it manually following the instructions at:" + echo "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" + exit 1 + else + echo "✅ cloudflared installed successfully: $(cloudflared --version | head -n1)" + fi } -EOF -) - - RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/tunnels/$TUNNEL_ID/configurations" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "$CONFIG") -done -# Create DNS records -echo "" -echo "Creating DNS records..." +# Authenticate with Cloudflare +authenticate_cloudflare() { + echo "Checking Cloudflare authentication..." + + # Remove --account-tag parameter as it's not supported in this version + if ! cloudflared tunnel list &> /dev/null; then + echo "Not authenticated with Cloudflare. Starting authentication process..." + echo "Log in to Cloudflare in the browser window that opens..." + + # Run login + if ! cloudflared tunnel login; then + echo "Authentication failed. Please try again or authenticate manually using:" + echo "cloudflared tunnel login" + exit 1 + fi + else + echo "✅ Already authenticated with Cloudflare" + fi +} + +# Create or get existing tunnel +setup_tunnel() { + echo "Setting up Cloudflare tunnel..." + + # Check if we have a tunnel ID in the environment + local tunnel_name="changemaker-lite" + local tunnel_id="" + + # Check if tunnel exists by name + echo "Checking for existing tunnel named '$tunnel_name'..." + local tunnel_list_output + # Remove --account-tag parameter as it's not supported in this version + tunnel_list_output=$(cloudflared tunnel list --output json 2>&1) + + # Check if the command was successful + if [ $? -ne 0 ]; then + echo "Error listing tunnels: $tunnel_list_output" + echo "Please ensure you're properly authenticated." + exit 1 + fi + + # Fix for invalid JSON output - try to clean the output if needed + if ! echo "$tunnel_list_output" | jq '.' &>/dev/null; then + echo "Warning: Invalid JSON returned from cloudflared. Attempting to clean output..." + # Try to extract valid JSON portion + tunnel_list_output=$(echo "$tunnel_list_output" | grep -o '\[.*\]' || echo "[]") + fi + + # Now try to extract the tunnel info + local tunnel_info + if echo "$tunnel_list_output" | jq '.' &>/dev/null; then + tunnel_info=$(echo "$tunnel_list_output" | jq -r '.[] | select(.name=="'$tunnel_name'")' 2>/dev/null || echo "") + else + echo "Failed to parse tunnel list. Cannot check for existing tunnels." + tunnel_info="" + fi + + if [ -n "$tunnel_info" ]; then + # Tunnel exists + tunnel_id=$(echo "$tunnel_info" | jq -r '.id' 2>/dev/null) + if [ -n "$tunnel_id" ] && [ "$tunnel_id" != "null" ]; then + echo "✅ Found existing tunnel: $tunnel_name (ID: $tunnel_id)" + + # Update the environment variable if needed + if [ "$CF_TUNNEL_ID" != "$tunnel_id" ]; then + echo "Updating CF_TUNNEL_ID in .env file..." + sed -i "s/CF_TUNNEL_ID=.*/CF_TUNNEL_ID=$tunnel_id/" "$ENV_FILE" + # Reload the variable + export CF_TUNNEL_ID="$tunnel_id" + fi + else + echo "Warning: Found tunnel but couldn't extract ID" + fi + else + # Create new tunnel + echo "Creating new tunnel: $tunnel_name" + local tunnel_create_output + # Remove --account-tag parameter as it's not supported in this version + tunnel_create_output=$(cloudflared tunnel create "$tunnel_name" 2>&1) + echo "Tunnel creation output:" + echo "$tunnel_create_output" + + if [ $? -ne 0 ]; then + echo "Failed to create tunnel. Please check your Cloudflare credentials." + exit 1 + fi + + # Try multiple methods to extract the tunnel ID from output + # Method 1: Try the original regex pattern + tunnel_id=$(echo "$tunnel_create_output" | grep -oP 'Created tunnel changemaker-lite with id \K[a-f0-9-]+' || echo "") + + # Method 2: Look for UUID pattern + if [ -z "$tunnel_id" ]; then + tunnel_id=$(echo "$tunnel_create_output" | grep -oP '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}' || echo "") + fi + + # Method 3: Look for hex string that might be the ID + if [ -z "$tunnel_id" ]; then + tunnel_id=$(echo "$tunnel_create_output" | grep -oP '\b[a-f0-9-]{20,}\b' || echo "") + fi + + # Interactive method as fallback + if [ -z "$tunnel_id" ]; then + echo "Could not automatically extract tunnel ID from output." + echo "Please check the output above and enter the tunnel ID manually:" + read -p "Tunnel ID: " tunnel_id + + if [ -z "$tunnel_id" ]; then + echo "No tunnel ID provided. Please create the tunnel manually and set CF_TUNNEL_ID in the .env file." + exit 1 + fi + fi + + echo "✅ Created new tunnel: $tunnel_name (ID: $tunnel_id)" + + # Update the environment variable + sed -i "s/CF_TUNNEL_ID=.*/CF_TUNNEL_ID=$tunnel_id/" "$ENV_FILE" + export CF_TUNNEL_ID="$tunnel_id" + fi + + # Make sure credentials directory exists + mkdir -p "$TUNNEL_CREDS_DIR" + + # Check if we need to copy the credentials file + local creds_file="$HOME/.cloudflared/$tunnel_id.json" + local target_creds_file="$TUNNEL_CREDS_DIR/$tunnel_id.json" + + if [ -f "$creds_file" ] && [ ! -f "$target_creds_file" ]; then + echo "Copying tunnel credentials to project directory..." + cp "$creds_file" "$target_creds_file" + echo "✅ Credentials copied to $target_creds_file" + elif [ ! -f "$creds_file" ]; then + echo "Warning: Tunnel credentials not found at $creds_file" + echo "Checking for credentials in alternative locations..." + + # Try to find credentials in ~/.cloudflared directory + local found_creds=false + for cred_file in "$HOME/.cloudflared/"*.json; do + if [ -f "$cred_file" ]; then + echo "Found credentials file: $cred_file" + echo "Checking if this file contains credentials for our tunnel..." + + # Check if the file contains the tunnel ID + if grep -q "$tunnel_id" "$cred_file"; then + echo "✅ This appears to be the correct credentials file!" + cp "$cred_file" "$target_creds_file" + echo "✅ Credentials copied to $target_creds_file" + found_creds=true + break + fi + fi + done + + if [ "$found_creds" = false ]; then + echo "Error: Could not find tunnel credentials." + echo "Please locate the credentials file for tunnel $tunnel_id and copy it to:" + echo "$target_creds_file" + echo "" + echo "You can also run this command manually to create a new tunnel with credentials:" + echo "cloudflared tunnel create --account-tag \"$CF_ACCOUNT_ID\" \"$tunnel_name\"" + exit 1 + fi + fi + + # Update tunnel configuration file + update_tunnel_config "$target_creds_file" + + return 0 +} + +# Update tunnel configuration +update_tunnel_config() { + local creds_file="$1" + + echo "Updating tunnel configuration..." + + # Create or update the config file + cat > "$TUNNEL_CONFIG_FILE" << EOL +# Cloudflare Tunnel Configuration for $CF_DOMAIN +# Generated by Changemaker.lite start-production.sh on $(date) + +tunnel: ${CF_TUNNEL_ID} +credentials-file: ${creds_file} +ingress: + - hostname: homepage.${CF_DOMAIN} + service: http://localhost:${HOMEPAGE_PORT:-3010} + - hostname: code.${CF_DOMAIN} + service: http://localhost:${CODE_SERVER_PORT:-8888} + - hostname: listmonk.${CF_DOMAIN} + service: http://localhost:${LISTMONK_PORT:-9000} + - hostname: docs.${CF_DOMAIN} + service: http://localhost:${MKDOCS_PORT:-4000} + - hostname: ${CF_DOMAIN} + service: http://localhost:${MKDOCS_SITE_SERVER_PORT:-4001} + - hostname: n8n.${CF_DOMAIN} + service: http://localhost:${N8N_PORT:-5678} + - hostname: db.${CF_DOMAIN} + service: http://localhost:${NOCODB_PORT:-8090} + - hostname: git.${CF_DOMAIN} + service: http://localhost:${GITEA_WEB_PORT:-3030} + - hostname: map.${CF_DOMAIN} + service: http://localhost:${MAP_PORT:-3000} + - hostname: qr.${CF_DOMAIN} + service: http://localhost:${MINI_QR_PORT:-8089} + - service: http_status:404 +EOL + + echo "✅ Tunnel configuration updated at $TUNNEL_CONFIG_FILE" +} + +# Create systemd service for cloudflared +create_systemd_service() { + echo "Creating systemd service for cloudflared tunnel..." + + local cloudflared_path=$(which cloudflared) + local username=$(whoami) + + # Create service file content + local service_content="[Unit] +Description=Cloudflare Tunnel for Changemaker.lite +After=network.target + +[Service] +User=$username +ExecStart=$cloudflared_path tunnel --config $TUNNEL_CONFIG_FILE run +Restart=always +RestartSec=5 +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target" + + # Write service file + echo "$service_content" | sudo tee /etc/systemd/system/$SERVICE_NAME.service > /dev/null + + # Reload systemd + sudo systemctl daemon-reload + + # Enable and start the service + sudo systemctl enable $SERVICE_NAME.service + sudo systemctl restart $SERVICE_NAME.service + + # Check service status + sleep 3 + if sudo systemctl is-active --quiet $SERVICE_NAME.service; then + echo "✅ Cloudflared service is running" + else + echo "⚠️ Cloudflared service failed to start. Checking logs..." + sudo systemctl status $SERVICE_NAME.service + echo "Viewing recent logs:" + sudo journalctl -u $SERVICE_NAME.service --no-pager -n 20 + + echo "Please check the configuration and try again." + exit 1 + fi +} # Function to create/update DNS record create_dns_record() { local name=$1 - local content="$TUNNEL_ID.cfargotunnel.com" + local full_name + local content="$CF_TUNNEL_ID.cfargotunnel.com" + + # Determine the full DNS name + if [ "$name" == "@" ]; then + full_name="$CF_DOMAIN" + else + full_name="$name.$CF_DOMAIN" + fi + + echo "Configuring DNS record for $full_name..." # Check if record exists - EXISTING=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$name.$CF_DOMAIN&type=CNAME" \ + EXISTING=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$full_name" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json") - RECORD_ID=$(echo "$EXISTING" | jq -r '.result[0].id') + RECORD_ID=$(echo "$EXISTING" | jq -r '.result[] | select(.name == "'$full_name'") | .id' | head -1) if [ ! -z "$RECORD_ID" ] && [ "$RECORD_ID" != "null" ]; then # Update existing record @@ -242,80 +385,215 @@ create_dns_record() { fi if echo "$RESPONSE" | jq -e '.success' > /dev/null; then - echo "✓ DNS record for $name.$CF_DOMAIN configured" + echo " ✅ DNS record configured" else - echo "✗ Failed to configure DNS record for $name.$CF_DOMAIN" + echo " ❌ Failed to configure DNS record" echo "$RESPONSE" | jq '.errors' fi } -# Create DNS records for all subdomains -for subdomain in "${!SERVICES[@]}"; do - create_dns_record "$subdomain" -done - -# Create root domain record -create_dns_record "@" - # Set up access policies -echo "" -echo "Setting up access policies..." -read -p "Enter admin email for protected services (dashboard, code): " ADMIN_EMAIL +setup_access_policies() { + echo "" + echo "Setting up Cloudflare Access policies..." + read -p "Enter email address for admin access to protected services: " ADMIN_EMAIL -if [[ "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then - # Create access applications for protected services - PROTECTED_SERVICES=("dashboard" "code") - - for service in "${PROTECTED_SERVICES[@]}"; do - echo "Creating access policy for $service.$CF_DOMAIN..." + if [[ "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + # Protected services + PROTECTED_SERVICES=("homepage" "code") - # Create access application - APP_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "{ - \"name\": \"$service.$CF_DOMAIN\", - \"domain\": \"$service.$CF_DOMAIN\", - \"type\": \"self_hosted\", - \"session_duration\": \"24h\" - }") - - APP_ID=$(echo "$APP_RESPONSE" | jq -r '.result.id') - - if [ ! -z "$APP_ID" ] && [ "$APP_ID" != "null" ]; then - # Create policy - POLICY_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps/$APP_ID/policies" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "{ - \"name\": \"Email Policy\", - \"decision\": \"allow\", - \"include\": [{ - \"email\": {\"email\": \"$ADMIN_EMAIL\"} - }] - }") + for service in "${PROTECTED_SERVICES[@]}"; do + echo "Creating access policy for $service.$CF_DOMAIN..." - echo "✓ Access policy created for $service.$CF_DOMAIN" + # Check if application already exists + EXISTING_APPS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json") + + APP_ID=$(echo "$EXISTING_APPS" | jq -r ".result[] | select(.domain == \"$service.$CF_DOMAIN\") | .id" | head -1) + + if [ -z "$APP_ID" ] || [ "$APP_ID" == "null" ]; then + # Create new access application + APP_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"$service.$CF_DOMAIN\", + \"domain\": \"$service.$CF_DOMAIN\", + \"type\": \"self_hosted\", + \"session_duration\": \"24h\", + \"auto_redirect_to_identity\": false + }") + + APP_ID=$(echo "$APP_RESPONSE" | jq -r '.result.id') + fi + + if [ ! -z "$APP_ID" ] && [ "$APP_ID" != "null" ]; then + # Create/update policy + POLICY_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps/$APP_ID/policies" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"Email Policy\", + \"decision\": \"allow\", + \"include\": [{ + \"email\": {\"email\": \"$ADMIN_EMAIL\"} + }] + }") + + if echo "$POLICY_RESPONSE" | jq -e '.success' > /dev/null; then + echo " ✅ Access policy configured" + else + echo " ⚠️ Policy may already exist or update failed" + fi + else + echo " ❌ Failed to create access application" + fi + done + else + echo "Invalid email format. Skipping access policy setup." + echo "You can configure access policies manually in the Cloudflare dashboard." + fi +} + +# Check if services are running +check_services() { + echo "Checking if services are running..." + + local all_running=true + + # Check common ports to see if services are running + if ! nc -z localhost ${HOMEPAGE_PORT:-3010} >/dev/null 2>&1; then + echo "❌ Homepage service not detected on port ${HOMEPAGE_PORT:-3010}" + all_running=false + fi + + if ! nc -z localhost ${CODE_SERVER_PORT:-8888} >/dev/null 2>&1; then + echo "❌ Code Server service not detected on port ${CODE_SERVER_PORT:-8888}" + all_running=false + fi + + if ! nc -z localhost ${LISTMONK_PORT:-9000} >/dev/null 2>&1; then + echo "❌ Listmonk service not detected on port ${LISTMONK_PORT:-9000}" + all_running=false + fi + + # At least check that some services are running + if [ "$all_running" = false ]; then + echo "Warning: Some services may not be running." + read -p "Continue anyway? (y/n): " continue_answer + if [[ ! "$continue_answer" =~ ^[Yy]$ ]]; then + echo "Please start your services with 'docker compose up -d' first." + exit 1 fi - done -else - echo "Invalid email format. Skipping access policy setup." + else + echo "✅ Services appear to be running" + fi +} + +# Add this function to verify Cloudflare API credentials +verify_cloudflare_credentials() { + echo "Verifying Cloudflare API credentials..." + + # Test the API token and Zone ID with a simple request + local TEST_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json") + + if echo "$TEST_RESPONSE" | jq -e '.success' > /dev/null 2>&1; then + echo "✅ Cloudflare API credentials verified successfully" + return 0 + else + echo "❌ Cloudflare API credentials verification failed" + echo "Error details:" + echo "$TEST_RESPONSE" | jq '.errors' + echo "" + echo "Common issues:" + echo "1. Zone ID may be incorrect - make sure you're using the correct Zone ID from the Cloudflare dashboard" + echo "2. API Token may not have the required permissions - make sure it has Zone:DNS and Zone:Settings permissions" + echo "" + echo "Your current Zone ID: $CF_ZONE_ID" + echo "To continue without configuring DNS records, enter 'continue'. To quit and fix the credentials, enter 'quit'." + read -p "Continue or quit? [continue/quit]: " cf_choice + + if [[ "$cf_choice" == "quit" ]]; then + echo "Exiting. Please update your credentials in .env and try again." + exit 1 + else + echo "Continuing without configuring DNS records..." + return 1 + fi + fi +} + +# Main execution flow +echo "Starting Changemaker.lite production deployment..." + +# Check if services are running +check_services + +# Check if cloudflared is installed, install if needed +check_cloudflared + +# Authenticate with Cloudflare +authenticate_cloudflare + +# Set up tunnel +setup_tunnel + +# Create systemd service +create_systemd_service + +# Verify Cloudflare credentials before proceeding with DNS configuration +CF_CREDS_VALID=true +if ! verify_cloudflare_credentials; then + CF_CREDS_VALID=false + echo "Skipping DNS record creation and access policy setup due to invalid credentials" fi -# Enable cloudflared in docker-compose -echo "" -echo "Enabling cloudflared container..." +# Only proceed with DNS configuration if credentials are valid +if [ "$CF_CREDS_VALID" = true ]; then + # Create/update DNS records + echo "" + echo "Creating/updating DNS records..." -# Uncomment cloudflared service in docker-compose.yml -sed -i '/# cloudflared:/,/# gitea-app/s/^ # / /' "$DOCKER_COMPOSE_FILE" + # Define subdomains + declare -A SUBDOMAINS=( + ["homepage"]="Homepage" + ["code"]="Code Server" + ["listmonk"]="Listmonk" + ["docs"]="Documentation" + ["n8n"]="n8n" + ["db"]="NocoDB" + ["git"]="Gitea" + ["map"]="Map" + ["qr"]="Mini QR" + ) -# Start cloudflared container -docker compose up -d cloudflared + for subdomain in "${!SUBDOMAINS[@]}"; do + create_dns_record "$subdomain" + done + + # Create root domain record + create_dns_record "@" + + echo "" + echo "✅ All DNS records configured" + + # Set up access policies + setup_access_policies +else + echo "" + echo "⚠️ DNS records and access policies were not configured." + echo "To manually configure DNS records, you can use the Cloudflare dashboard." + echo "Add CNAME records for each subdomain pointing to: $CF_TUNNEL_ID.cfargotunnel.com" + echo "" +fi echo "" echo "#############################################################" echo "# " -echo "# Production Setup Complete! " +echo "# Production Deployment Complete! " echo "# " echo "#############################################################" echo "" @@ -328,21 +606,13 @@ echo " - Documentation: https://docs.$CF_DOMAIN" echo " - n8n: https://n8n.$CF_DOMAIN" echo " - NocoDB: https://db.$CF_DOMAIN" echo " - Gitea: https://git.$CF_DOMAIN" +echo " - Map: https://map.$CF_DOMAIN" +echo " - Mini QR: https://qr.$CF_DOMAIN" echo "" -if [ ! -z "$ADMIN_EMAIL" ]; then - echo "Protected services (login required with $ADMIN_EMAIL):" - echo " - Dashboard: https://dashboard.$CF_DOMAIN" - echo " - Code Server: https://code.$CF_DOMAIN" -else - echo "Admin services (no protection configured):" - echo " - Dashboard: https://dashboard.$CF_DOMAIN" - echo " - Code Server: https://code.$CF_DOMAIN" -fi +echo "Protected services (requires login with $ADMIN_EMAIL):" +echo " - Dashboard: https://homepage.$CF_DOMAIN" +echo " - Code Server: https://code.$CF_DOMAIN" echo "" -echo "Note: DNS propagation may take a few minutes." -echo "" -echo "To stop production mode and return to local-only:" -echo " docker compose stop cloudflared" -echo "" -echo "To fully remove production setup:" -echo " docker compose down cloudflared" \ No newline at end of file +echo "Cloudflared service status: sudo systemctl status $SERVICE_NAME" +echo "View tunnel logs: sudo journalctl -u $SERVICE_NAME -f" +echo "" \ No newline at end of file