Updates to online workflow (moved cloudflare back to system service), full rework of homepage, updated a bunch of stuff in general

This commit is contained in:
admin 2025-06-29 22:03:24 -06:00
parent 5a7d38ec71
commit c658f4bd00
19 changed files with 1304 additions and 774 deletions

254
config.sh
View File

@ -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"
echo "Cloudflare credentials saved successfully!"
# 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"
# 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"

View File

@ -0,0 +1 @@
{"AccountTag":"a421828402ca13fbcaad955f285f16f6","TunnelSecret":"949mtkdN202INtEohadCgnEe7QXykBL26dq2uQMt+HQ=","TunnelID":"843f83a4-247a-4c29-8a7e-cde50e2f4222","Endpoint":""}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -190,3 +190,514 @@
[2025-05-28T14:56:56.936Z] error: <dockerStatsService> 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: <servicesProxy> 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: <servicesProxy> 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: <servicesProxy> 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: <servicesProxy> 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: <servicesProxy> 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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,6 +7,16 @@
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
text: "Welcome to Changemaker Lite"
@ -21,7 +31,3 @@
- search:
provider: duckduckgo
target: _blank
- unifi_console:
text_size: md
text: "Services Available: Code Server, Listmonk, NocoDB, MkDocs, n8n, Gitea"

View File

@ -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:

40
docs-cloudflare.md Normal file
View File

@ -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:

View File

@ -25,3 +25,9 @@ services:
options:
max-size: "10m"
max-file: "3"
networks:
- changemakerlite_changemaker-lite
networks:
changemakerlite_changemaker-lite:
external: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,9 +0,0 @@
{% extends "base.html" %}
{% block extrahead %}
{% endblock %}
{% block announce %}
<a href="https://homepage.albertademocracytaskforce.org" class="login-button">Login</a>
Changemaker Archive. <a href="https://docs.bnkops.com">Learn more</a>
{% endblock %}

View File

@ -1,9 +0,0 @@
{% extends "base.html" %}
{% block extrahead %}
{% endblock %}
{% block announce %}
<a href="https://homepage.albertademocracytaskforce.org" class="login-button">Login</a>
Changemaker Archive. <a href="https://docs.bnkops.com">Learn more</a>
{% endblock %}

View File

@ -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 &copy; 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

732
start-production.sh Normal file → Executable file
View File

@ -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
# Update and install
sudo apt-get update && sudo apt-get install -y cloudflared
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 <<EOF
{
"tunnel_id": "$TUNNEL_ID",
"hostname": "$CF_DOMAIN",
"service": "http://mkdocs-site-server:80"
if ! command -v cloudflared &> /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
)
ROOT_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 "$ROOT_CONFIG")
# Authenticate with Cloudflare
authenticate_cloudflare() {
echo "Checking Cloudflare authentication..."
# Configure subdomain routes
for subdomain in "${!SERVICES[@]}"; do
service="${SERVICES[$subdomain]}"
echo "Configuring route for $subdomain.$CF_DOMAIN -> $service"
# 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..."
CONFIG=$(cat <<EOF
{
"tunnel_id": "$TUNNEL_ID",
"hostname": "$subdomain.$CF_DOMAIN",
"service": "http://$service"
# 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
}
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 or get existing tunnel
setup_tunnel() {
echo "Setting up Cloudflare tunnel..."
# Create DNS records
echo ""
echo "Creating DNS records..."
# 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")
if [[ "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
# Protected services
PROTECTED_SERVICES=("homepage" "code")
for service in "${PROTECTED_SERVICES[@]}"; do
echo "Creating access policy for $service.$CF_DOMAIN..."
for service in "${PROTECTED_SERVICES[@]}"; do
echo "Creating access policy for $service.$CF_DOMAIN..."
# 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" \
# 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" \
--data "{
\"name\": \"Email Policy\",
\"decision\": \"allow\",
\"include\": [{
\"email\": {\"email\": \"$ADMIN_EMAIL\"}
}]
}")
-H "Content-Type: application/json")
echo "✓ Access policy created for $service.$CF_DOMAIN"
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 "Cloudflared service status: sudo systemctl status $SERVICE_NAME"
echo "View tunnel logs: sudo journalctl -u $SERVICE_NAME -f"
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"