inital commit

This commit is contained in:
admin 2025-05-30 12:43:55 -06:00
commit 57684a8a83
391 changed files with 145868 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/configs/code-server/.local/*
!/configs/code-server/.local/.gitkeep
/configs/code-server/.config/*
!/configs/code-server/.config/.gitkeep
.env
.env*

84
Dockerfile.code-server Normal file
View File

@ -0,0 +1,84 @@
FROM codercom/code-server:latest
USER root
# Install Python and dependencies
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
python3-venv \
python3-full \
pipx \
# Dependencies for CairoSVG and Pillow (PIL)
libcairo2-dev \
libfreetype6-dev \
libffi-dev \
libjpeg-dev \
libpng-dev \
libz-dev \
python3-dev \
pkg-config \
# Additional dependencies for advanced image processing
libwebp-dev \
libtiff5-dev \
libopenjp2-7-dev \
liblcms2-dev \
libxml2-dev \
libxslt1-dev \
# PDF generation dependencies
weasyprint \
fonts-roboto \
# Git for git-based plugins
git \
# For lxml
zlib1g-dev \
# Required for some plugins
build-essential \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Switch to non-root user (coder)
USER coder
# Set up a virtual environment for mkdocs
RUN mkdir -p /home/coder/.venv
RUN python3 -m venv /home/coder/.venv/mkdocs
# Install mkdocs-material in the virtual environment with all extras
RUN /home/coder/.venv/mkdocs/bin/pip install "mkdocs-material[imaging,recommended,git]"
# Install additional useful MkDocs plugins
RUN /home/coder/.venv/mkdocs/bin/pip install \
mkdocs-minify-plugin \
mkdocs-git-revision-date-localized-plugin \
mkdocs-glightbox \
mkdocs-redirects \
mkdocs-awesome-pages-plugin \
mkdocs-blog-plugin \
mkdocs-rss-plugin \
mkdocs-meta-descriptions-plugin \
mkdocs-swagger-ui-tag \
mkdocs-macros-plugin \
mkdocs-material-extensions \
mkdocs-section-index \
mkdocs-table-reader-plugin \
mkdocs-pdf-export-plugin \
mkdocs-mermaid2-plugin \
pymdown-extensions \
pygments \
pillow \
cairosvg
# Add the virtual environment bin to PATH
ENV PATH="/home/coder/.venv/mkdocs/bin:${PATH}"
# Add shell configuration to activate the virtual environment in .bashrc
RUN echo 'export PATH="/home/coder/.venv/mkdocs/bin:$PATH"' >> ~/.bashrc
RUN echo 'export PATH="/home/coder/.local/bin:$PATH"' >> ~/.bashrc
# Create a convenience script to simplify running mkdocs commands
RUN mkdir -p /home/coder/.local/bin \
&& echo '#!/bin/bash\ncd /home/coder/mkdocs\nmkdocs "$@"' > /home/coder/.local/bin/run-mkdocs \
&& chmod +x /home/coder/.local/bin/run-mkdocs
WORKDIR /home/coder

51
README.md Normal file
View File

@ -0,0 +1,51 @@
# Changemaker Lite
Changemaker Lite is a streamlined documentation and development platform featuring essential self-hosted services for creating, managing, and automating content workflows.
## Features
- **Homepage**: Modern dashboard for accessing all services
- **Code Server**: VS Code in your browser for remote development
- **MkDocs Material**: Beautiful documentation with live preview
- **Static Site Server**: High-performance hosting for built sites
- **Listmonk**: Self-hosted newsletter and email campaign management
- **PostgreSQL**: Reliable database backend
- **n8n**: Workflow automation and service integration
- **NocoDB**: No-code database platform and smart spreadsheet interface
## Quick Start
```bash
# Clone the repository
git clone https://git.albertademocracytaskforce.org/admin/ab.dem.tf.changemaker.git
cd changemaker.lite
# Configure environment (creates .env file)
./config.sh
# Start all services
docker compose up -d
```
## Service Access
After starting, access services at:
- **Homepage Dashboard**: http://localhost:3010
- **Documentation (Dev)**: http://localhost:4000
- **Documentation (Built)**: http://localhost:4001
- **Code Server**: http://localhost:8888
- **Listmonk**: http://localhost:9000
- **n8n**: http://localhost:5678
- **NocoDB**: http://localhost:8090
## Documentation
Complete documentation is available in the MkDocs site, including:
- Service configuration guides
- Integration examples
- Workflow automation tutorials
- Troubleshooting guides
Visit http://localhost:4000 after starting services to access the full documentation.

290
add-cname-records.sh Executable file
View File

@ -0,0 +1,290 @@
#!/bin/bash
echo "#############################################################"
echo "# "
echo "# DNS Setup for Changemaker.lite Services "
echo "# "
echo "# This script will ADD DNS records for your services. "
echo "# Existing DNS records will NOT be deleted. "
echo "# "
echo "#############################################################"
echo ""
echo "-------------------------------------------------------------"
echo "Cloudflare Credentials Required"
echo "Please ensure your .env file contains the following variables:"
echo " CF_API_TOKEN=your_cloudflare_api_token"
echo " CF_ZONE_ID=your_cloudflare_zone_id"
echo " CF_TUNNEL_ID=your_cloudflared_tunnel_id"
echo " CF_DOMAIN=yourdomain.com"
echo ""
echo "You can find these values in your Cloudflare dashboard:"
echo " - API Token: https://dash.cloudflare.com/profile/api-tokens (Create a token with Zone:DNS:Edit and Access:Apps:Edit permissions for your domain)"
echo " - Zone ID: On your domain's overview page"
echo " - Tunnel ID: In the Zero Trust dashboard under Access > Tunnels"
echo " - Domain: The domain you want to use for your services"
echo ""
echo "-------------------------------------------------------------"
echo ""
read -p "Type 'y' to continue or any other key to abort: " consent
if [[ "$consent" != "y" && "$consent" != "Y" ]]; then
echo "Aborted by user."
exit 1
fi
# Source environment variables from the .env file in the same directory
ENV_FILE="$(dirname "$0")/.env"
if [ -f "$ENV_FILE" ]; then
export $(grep -v '^#' "$ENV_FILE" | xargs)
else
echo "Error: .env file not found at $ENV_FILE"
exit 1
fi
# Check if required Cloudflare variables are set
if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ZONE_ID" ] || [ -z "$CF_TUNNEL_ID" ] || [ -z "$CF_DOMAIN" ]; then
echo "Error: One or more required Cloudflare environment variables (CF_API_TOKEN, CF_ZONE_ID, CF_TUNNEL_ID, CF_DOMAIN) are not set in $ENV_FILE."
exit 1
fi
# Check if jq is installed
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed. Please install jq to continue."
echo "On Debian/Ubuntu: sudo apt-get install jq"
echo "On RHEL/CentOS: sudo yum install jq"
exit 1
fi
# Array of subdomains that need DNS records - updated to match our active services
SUBDOMAINS=(
"dashboard"
"code"
"listmonk"
"docs"
"n8n"
"db"
"git"
)
# Function to check if DNS record already exists
record_exists() {
local subdomain=$1
local records=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$subdomain.$CF_DOMAIN" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json")
local count=$(echo $records | jq -r '.result | length')
[ "$count" -gt 0 ]
}
# Add CNAME records for each subdomain (only if they don't exist)
echo "Adding DNS records for services..."
for subdomain in "${SUBDOMAINS[@]}"; do
if record_exists "$subdomain"; then
echo "DNS record for $subdomain.$CF_DOMAIN already exists, skipping..."
else
echo "Adding CNAME record for $subdomain.$CF_DOMAIN..."
response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"$subdomain\",
\"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\",
\"ttl\": 1,
\"proxied\": true
}")
success=$(echo $response | jq -r '.success')
if [ "$success" == "true" ]; then
echo "✓ Successfully added CNAME record for $subdomain.$CF_DOMAIN"
else
echo "✗ Failed to add CNAME record for $subdomain.$CF_DOMAIN"
echo "Error: $(echo $response | jq -r '.errors[0].message')"
fi
fi
done
# Add root domain record if it doesn't exist
if record_exists "@"; then
echo "Root domain DNS record already exists, skipping..."
else
echo "Adding root domain CNAME record..."
response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"@\",
\"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\",
\"ttl\": 1,
\"proxied\": true
}")
success=$(echo $response | jq -r '.success')
if [ "$success" == "true" ]; then
echo "✓ Successfully added root domain CNAME record"
else
echo "✗ Failed to add root domain CNAME record"
echo "Error: $(echo $response | jq -r '.errors[0].message')"
fi
fi
echo ""
echo "DNS records setup complete!"
echo ""
# Prompt for admin email for secured services
echo "-------------------------------------------------------------"
echo "Setting up Cloudflare Access Protection"
echo "-------------------------------------------------------------"
echo ""
echo "The following services will be protected with authentication:"
echo " - dashboard.$CF_DOMAIN"
echo " - code.$CF_DOMAIN"
echo ""
echo "Please enter the admin email address that should have access:"
read ADMIN_EMAIL
# Validate email format
if [[ ! "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Error: Invalid email format. Please provide a valid email address."
exit 1
fi
# Services that require authentication - updated for our use case
PROTECTED_SERVICES=("dashboard" "code")
# Services that should have bypass policies (public access) - updated for our use case
BYPASS_SERVICES=("listmonk" "docs" "n8n" "db" "git")
# Function to create access application with email authentication
create_protected_app() {
local service=$1
echo "Setting up authentication for $service.$CF_DOMAIN..."
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_launcher_visible\": true,
\"skip_interstitial\": true
}")
app_id=$(echo $app_response | jq -r '.result.id')
if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then
echo "✗ Error creating access application for $service"
return 1
fi
echo "✓ Created access application for $service (ID: $app_id)"
# Create authentication 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\": \"Admin Access\",
\"decision\": \"allow\",
\"include\": [{
\"email\": {
\"email\": \"$ADMIN_EMAIL\"
}
}],
\"require\": [],
\"exclude\": []
}")
policy_success=$(echo $policy_response | jq -r '.success')
if [ "$policy_success" == "true" ]; then
echo "✓ Created authentication policy for $service"
else
echo "✗ Failed to create authentication policy for $service"
fi
}
# Function to create bypass application (public access)
create_bypass_app() {
local service=$1
echo "Setting up public access for $service.$CF_DOMAIN..."
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_launcher_visible\": false,
\"skip_interstitial\": true
}")
app_id=$(echo $app_response | jq -r '.result.id')
if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then
echo "✗ Error creating access application for $service"
return 1
fi
echo "✓ Created access application for $service (ID: $app_id)"
# Create bypass 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\": \"Public Access\",
\"decision\": \"bypass\",
\"include\": [{
\"everyone\": {}
}],
\"require\": [],
\"exclude\": []
}")
policy_success=$(echo $policy_response | jq -r '.success')
if [ "$policy_success" == "true" ]; then
echo "✓ Created public access policy for $service"
else
echo "✗ Failed to create public access policy for $service"
fi
}
echo "Creating Cloudflare Access applications..."
echo ""
# Create protected applications
for service in "${PROTECTED_SERVICES[@]}"; do
create_protected_app "$service"
echo ""
done
# Create bypass applications for public services
for service in "${BYPASS_SERVICES[@]}"; do
create_bypass_app "$service"
echo ""
done
echo "-------------------------------------------------------------"
echo "Setup Complete!"
echo "-------------------------------------------------------------"
echo ""
echo "Protected services (require authentication with $ADMIN_EMAIL):"
for service in "${PROTECTED_SERVICES[@]}"; do
echo " - https://$service.$CF_DOMAIN"
done
echo ""
echo "Public services (no authentication required):"
for service in "${BYPASS_SERVICES[@]}"; do
echo " - https://$service.$CF_DOMAIN"
done
echo ""
echo "All services should be accessible through your Cloudflare tunnel."

724
config.sh Executable file
View File

@ -0,0 +1,724 @@
#!/bin/bash
cat << "EOF"
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
Configuration Wizard
EOF
# Get the absolute path of the script directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ENV_FILE="$SCRIPT_DIR/.env"
MKDOCS_YML="$SCRIPT_DIR/mkdocs/docs/mkdocs.yml"
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"
echo "Looking for .env file at: $ENV_FILE"
# Function to generate a random secure password
generate_password() {
local length=${1:-16}
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()-_=+' | head -c "$length"
}
# Function to safely update environment variables in .env file
update_env_var() {
local key=$1
local value=$2
# More robust method to handle special characters in passwords
if grep -q "^$key=" "$ENV_FILE"; then
# Create a temporary file
local tmpfile=$(mktemp)
# Process the .env file line by line
while IFS= read -r line; do
if [[ "$line" =~ ^$key= ]]; then
echo "$key=$value" >> "$tmpfile"
else
echo "$line" >> "$tmpfile"
fi
done < "$ENV_FILE"
# Replace the original file with the temporary file
mv "$tmpfile" "$ENV_FILE"
echo "Updated $key in .env file"
else
# Add new key-value pair if it doesn't exist
echo "$key=$value" >> "$ENV_FILE"
echo "Added $key to .env file"
fi
}
# Function to create a timestamped backup of the .env file
backup_env_file() {
if [ -f "$ENV_FILE" ]; then
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="$ENV_FILE.backup_$timestamp"
echo "Creating backup of current .env file to: $backup_file"
if cp "$ENV_FILE" "$backup_file"; then
echo "Backup created successfully!"
return 0
else
echo "Failed to create backup file. Proceeding with caution..."
return 1
fi
fi
}
# Function to initialize the .env file with default values
initialize_env_file() {
echo "Initializing new .env file with default values..."
cat > "$ENV_FILE" << EOL
# Never share this file publicly. It contains sensitive information.
# This file is used to configure various applications and services.
# Generated by Changemaker Config Wizard on $(date)
# User and Group Configuration
USER_NAME=coder
USER_ID=1000
GROUP_ID=1000
# Port Configuration
CODE_SERVER_PORT=8888
LISTMONK_PORT=9000
LISTMONK_DB_PORT=5432
MKDOCS_PORT=4000
MKDOCS_SITE_SERVER_PORT=4001
N8N_PORT=5678
NOCODB_PORT=8090
HOMEPAGE_PORT=3010
GITEA_WEB_PORT=3030
GITEA_SSH_PORT=2222
# Domain Configuration
BASE_DOMAIN=https://changeme.org
DOMAIN=changeme.org
LISTMONK_HOSTNAME=listmonk.changeme.org
N8N_HOST=n8n.changeme.org
GITEA_DOMAIN=git.changeme.org
GITEA_ROOT_URL=https://git.changeme.org
# 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
# Database Configuration (PostgreSQL for Listmonk)
POSTGRES_USER=listmonk
POSTGRES_PASSWORD=changeMe
POSTGRES_DB=listmonk
# Listmonk Database Performance Settings
LISTMONK_DB_MAX_OPEN=25
LISTMONK_DB_MAX_IDLE=25
LISTMONK_DB_MAX_LIFETIME=300s
# Listmonk Admin Configuration
LISTMONK_ADMIN_USER=admin
LISTMONK_ADMIN_PASSWORD=changeMe
# N8N Configuration
N8N_USER_EMAIL=admin@example.com
N8N_USER_PASSWORD=changeMe
N8N_ENCRYPTION_KEY=changeMe
GENERIC_TIMEZONE=UTC
# Nocodb Configuration
NOCODB_JWT_SECRET=changeMe
NOCODB_DB_NAME=nocodb
NOCODB_DB_USER=noco
NOCODB_DB_PASSWORD=changeMe
HOMEPAGE_VAR_BASE_URL=https://changeme.org
EOL
echo "New .env file created with default values."
}
# Function to update the site_url in mkdocs.yml
update_mkdocs_yml() {
local new_domain=$1
if [ ! -f "$MKDOCS_YML" ]; then
echo "Warning: mkdocs.yml not found at $MKDOCS_YML"
return 1
fi
echo "Updating site_url in mkdocs.yml..."
# Create a backup of the mkdocs.yml file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${MKDOCS_YML}.backup_${timestamp}"
cp "$MKDOCS_YML" "$backup_file"
echo "Created backup of mkdocs.yml at $backup_file"
# Update the site_url value
sed -i "s|^site_url:.*|site_url: https://$new_domain|" "$MKDOCS_YML"
if grep -q "site_url: https://$new_domain" "$MKDOCS_YML"; then
echo "Updated site_url in mkdocs.yml to: https://$new_domain"
return 0
else
echo "Warning: Failed to update site_url in mkdocs.yml"
return 1
fi
}
# Function to update service URLs in services.yaml
update_services_yaml() {
local new_domain=$1
if [ ! -f "$SERVICES_YAML" ]; then
echo "Warning: services.yaml not found at $SERVICES_YAML"
return 1
fi
echo "Updating service URLs in services.yaml..."
# Create a backup of the services.yaml file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${SERVICES_YAML}.backup_${timestamp}"
cp "$SERVICES_YAML" "$backup_file"
echo "Created backup of services.yaml at $backup_file"
# Update the commented URLs to use the new domain
sed -i "s|# href: \"https://code\.changeme\.org\"|# href: \"https://code.$new_domain\"|g" "$SERVICES_YAML"
sed -i "s|# href: \"https://listmonk\.changeme\.org\"|# href: \"https://listmonk.$new_domain\"|g" "$SERVICES_YAML"
sed -i "s|# href: \"https://db\.changeme\.org\"|# href: \"https://db.$new_domain\"|g" "$SERVICES_YAML"
sed -i "s|# href: \"https://docs\.changeme\.org\"|# href: \"https://docs.$new_domain\"|g" "$SERVICES_YAML"
sed -i "s|# href: \"https://n8n\.changeme\.org\"|# href: \"https://n8n.$new_domain\"|g" "$SERVICES_YAML"
sed -i "s|# href: \"https://test\.com\"|# href: \"https://$new_domain\"|g" "$SERVICES_YAML"
# Also update any remaining changeme.org references
sed -i "s|changeme\.org|$new_domain|g" "$SERVICES_YAML"
echo "Updated service URLs in services.yaml to use domain: $new_domain"
return 0
}
# Function to update the login URL in main.html
update_main_html() {
local new_domain=$1
if [ ! -f "$MAIN_HTML" ]; then
echo "Warning: main.html not found at $MAIN_HTML"
return 1
fi
echo "Updating login URL in main.html..."
# Create a backup of the main.html file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${MAIN_HTML}.backup_${timestamp}"
cp "$MAIN_HTML" "$backup_file"
echo "Created backup of main.html at $backup_file"
# Update the login button href to use the new domain
sed -i "s|href=\"https://homepage\.test\.com\"|href=\"https://homepage.$new_domain\"|g" "$MAIN_HTML"
# Also update any other test.com references
sed -i "s|homepage\.test\.com|homepage.$new_domain|g" "$MAIN_HTML"
echo "Updated login URL in main.html to: https://homepage.$new_domain"
return 0
}
# Function to check if a port is in use
check_port() {
local port=$1
if command -v ss >/dev/null 2>&1; then
ss -tuln | grep -q ":$port "
elif command -v netstat >/dev/null 2>&1; then
netstat -tuln | grep -q ":$port "
else
# Fallback to lsof if available
if command -v lsof >/dev/null 2>&1; then
lsof -i ":$port" >/dev/null 2>&1
else
echo "Warning: Cannot check port availability. Please ensure ports are free manually."
return 1
fi
fi
}
# Function to check all service ports for conflicts
check_port_conflicts() {
echo "Checking for port conflicts..."
local ports_to_check=(
"${CODE_SERVER_PORT:-8888}:Code Server"
"${LISTMONK_PORT:-9000}:Listmonk"
"${LISTMONK_DB_PORT:-5432}:Listmonk Database"
"${MKDOCS_PORT:-4000}:MkDocs"
"${MKDOCS_SITE_SERVER_PORT:-4001}:MkDocs Site Server"
"${N8N_PORT:-5678}:N8N"
"${NOCODB_PORT:-8090}:NocoDB"
"${HOMEPAGE_PORT:-3010}:Homepage"
"${GITEA_WEB_PORT:-3030}:Gitea Web"
"${GITEA_SSH_PORT:-2222}:Gitea SSH"
)
local conflicts_found=false
for port_info in "${ports_to_check[@]}"; do
local port=$(echo "$port_info" | cut -d: -f1)
local service=$(echo "$port_info" | cut -d: -f2)
if check_port "$port"; then
echo "⚠️ Port conflict detected: $port is already in use (assigned to $service)"
conflicts_found=true
else
echo "✅ Port $port is available for $service"
fi
done
if [ "$conflicts_found" = true ]; then
echo ""
echo "Port conflicts detected! Please choose alternative ports or stop conflicting services."
read -p "Do you want to configure alternative ports? [Y/n]: " configure_ports
if [[ "$configure_ports" =~ ^[Nn]$ ]]; then
echo "Configuration cancelled. Please resolve port conflicts and try again."
exit 1
else
configure_alternative_ports
fi
else
echo "✅ All ports are available!"
fi
}
# Function to configure alternative ports
configure_alternative_ports() {
echo ""
echo "---- Port Configuration ----"
# Code Server
if check_port "${CODE_SERVER_PORT:-8888}"; then
read -p "Enter alternative port for Code Server [current: ${CODE_SERVER_PORT:-8888}]: " new_code_port
if [ ! -z "$new_code_port" ]; then
update_env_var "CODE_SERVER_PORT" "$new_code_port"
fi
fi
# Listmonk
if check_port "${LISTMONK_PORT:-9000}"; then
read -p "Enter alternative port for Listmonk [current: ${LISTMONK_PORT:-9000}]: " new_listmonk_port
if [ ! -z "$new_listmonk_port" ]; then
update_env_var "LISTMONK_PORT" "$new_listmonk_port"
fi
fi
# Listmonk DB
if check_port "${LISTMONK_DB_PORT:-5432}"; then
read -p "Enter alternative port for Listmonk Database [current: ${LISTMONK_DB_PORT:-5432}]: " new_db_port
if [ ! -z "$new_db_port" ]; then
update_env_var "LISTMONK_DB_PORT" "$new_db_port"
fi
fi
# MkDocs
if check_port "${MKDOCS_PORT:-4000}"; then
read -p "Enter alternative port for MkDocs [current: ${MKDOCS_PORT:-4000}]: " new_mkdocs_port
if [ ! -z "$new_mkdocs_port" ]; then
update_env_var "MKDOCS_PORT" "$new_mkdocs_port"
fi
fi
# MkDocs Site Server
if check_port "${MKDOCS_SITE_SERVER_PORT:-4001}"; then
read -p "Enter alternative port for MkDocs Site Server [current: ${MKDOCS_SITE_SERVER_PORT:-4001}]: " new_site_port
if [ ! -z "$new_site_port" ]; then
update_env_var "MKDOCS_SITE_SERVER_PORT" "$new_site_port"
fi
fi
# N8N
if check_port "${N8N_PORT:-5678}"; then
read -p "Enter alternative port for N8N [current: ${N8N_PORT:-5678}]: " new_n8n_port
if [ ! -z "$new_n8n_port" ]; then
update_env_var "N8N_PORT" "$new_n8n_port"
fi
fi
# NocoDB
if check_port "${NOCODB_PORT:-8090}"; then
read -p "Enter alternative port for NocoDB [current: ${NOCODB_PORT:-8090}]: " new_nocodb_port
if [ ! -z "$new_nocodb_port" ]; then
update_env_var "NOCODB_PORT" "$new_nocodb_port"
fi
fi
# Homepage
if check_port "${HOMEPAGE_PORT:-3010}"; then
read -p "Enter alternative port for Homepage [current: ${HOMEPAGE_PORT:-3010}]: " new_homepage_port
if [ ! -z "$new_homepage_port" ]; then
update_env_var "HOMEPAGE_PORT" "$new_homepage_port"
fi
fi
# Gitea Web
if check_port "${GITEA_WEB_PORT:-3030}"; then
read -p "Enter alternative port for Gitea Web [current: ${GITEA_WEB_PORT:-3030}]: " new_gitea_web_port
if [ ! -z "$new_gitea_web_port" ]; then
update_env_var "GITEA_WEB_PORT" "$new_gitea_web_port"
fi
fi
# Gitea SSH
if check_port "${GITEA_SSH_PORT:-2222}"; then
read -p "Enter alternative port for Gitea SSH [current: ${GITEA_SSH_PORT:-2222}]: " new_gitea_ssh_port
if [ ! -z "$new_gitea_ssh_port" ]; then
update_env_var "GITEA_SSH_PORT" "$new_gitea_ssh_port"
fi
fi
echo "Port configuration completed."
}
# Function to create Cloudflare tunnel configuration file
create_tunnel_config() {
local domain=$1
local tunnel_id=$2
# Ensure the tunnel config directory exists
if [ ! -d "$TUNNEL_CONFIG_DIR" ]; then
echo "Creating Cloudflare tunnel config directory at $TUNNEL_CONFIG_DIR"
mkdir -p "$TUNNEL_CONFIG_DIR"
fi
echo "Creating Cloudflare tunnel configuration file..."
# Generate the tunnel configuration file with simpler format
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: $tunnel_id # e.g. 1234567890abcdef
credentials-file: /home/coder/.cloudflared/$tunnel_id.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json
ingress:
- hostname: homepage.$domain
service: http://localhost:${HOMEPAGE_PORT:-3010}
- hostname: code.$domain
service: http://localhost:${CODE_SERVER_PORT:-8888}
- hostname: listmonk.$domain
service: http://localhost:${LISTMONK_PORT:-9000}
- hostname: docs.$domain
service: http://localhost:${MKDOCS_PORT:-4000}
- hostname: $domain
service: http://localhost:${MKDOCS_SITE_SERVER_PORT:-4001}
- hostname: n8n.$domain
service: http://localhost:${N8N_PORT:-5678}
- hostname: db.$domain
service: http://localhost:${NOCODB_PORT:-8090}
- hostname: git.$domain
service: http://localhost:${GITEA_WEB_PORT:-3030}
# Catch-all rule (required)
- service: http_status:404
EOL
echo "✅ Tunnel configuration file created at: $TUNNEL_CONFIG_FILE"
# If the tunnel ID is a placeholder, provide instructions
if [[ "$tunnel_id" == "\${CF_TUNNEL_ID}" ]]; then
echo "NOTE: You need to replace ${CF_TUNNEL_ID} with your actual Cloudflare Tunnel ID"
echo " once you create a tunnel in the Cloudflare Zero Trust dashboard."
fi
}
# 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
# Load variables from .env file, ignoring comments and empty lines
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
export "$line"
fi
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE")
fi
}
# 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."
touch "$ENV_FILE"
initialize_env_file
else
echo "Found existing .env file. Will update values."
backup_env_file
fi
# Load existing environment variables
load_env_vars
echo -e "\n\nWelcome to Changemaker Config!\n"
echo "This script will help you configure your .env file for Changemaker."
echo "Please provide the following information:"
# Domain configuration
read -p "Enter your domain name (without protocol, e.g., example.com): " domain_name
if [ -z "$domain_name" ]; then
echo "Domain name cannot be empty. Using default: changeme.org"
domain_name="changeme.org"
fi
echo -e "\nUpdating domain settings in .env file..."
# Update main domain settings
update_env_var "DOMAIN" "$domain_name"
update_env_var "BASE_DOMAIN" "https://$domain_name"
update_env_var "HOMEPAGE_VAR_BASE_URL" "https://$domain_name"
update_env_var "LISTMONK_HOSTNAME" "listmonk.$domain_name"
update_env_var "N8N_HOST" "n8n.$domain_name"
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"
echo "Domain settings updated successfully!"
# Cloudflare Configuration
echo -e "\n---- Cloudflare Configuration ----"
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 " - Tunnel ID: In the Zero Trust dashboard under Access > Tunnels"
echo ""
read -p "Do you want to configure Cloudflare settings now? [Y/n]: " configure_cf
if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then
echo ""
echo "Please enter your Cloudflare credentials:"
# CF API Token
read -p "Enter your Cloudflare API Token: " cf_api_token
if [ ! -z "$cf_api_token" ]; then
# Basic validation for API token format
if [[ "$cf_api_token" =~ ^[A-Za-z0-9_-]{40}$ ]]; then
update_env_var "CF_API_TOKEN" "$cf_api_token"
echo "✅ Cloudflare API Token updated"
else
echo "⚠️ Warning: API Token format seems incorrect (should be 40 characters)"
update_env_var "CF_API_TOKEN" "$cf_api_token"
fi
else
echo "⚠️ Cloudflare API Token left unchanged"
fi
# CF Zone ID
read -p "Enter your Cloudflare Zone ID: " cf_zone_id
if [ ! -z "$cf_zone_id" ]; then
# Basic validation for Zone ID format
if [[ "$cf_zone_id" =~ ^[a-f0-9]{32}$ ]]; then
update_env_var "CF_ZONE_ID" "$cf_zone_id"
echo "✅ Cloudflare Zone ID updated"
else
echo "⚠️ Warning: Zone ID format seems incorrect (should be 32 hex characters)"
update_env_var "CF_ZONE_ID" "$cf_zone_id"
fi
else
echo "⚠️ Cloudflare Zone ID left unchanged"
fi
# CF Tunnel ID
read -p "Enter your Cloudflare Tunnel ID: " cf_tunnel_id
if [ ! -z "$cf_tunnel_id" ]; then
# Basic validation for Tunnel ID format (UUID)
if [[ "$cf_tunnel_id" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]]; then
update_env_var "CF_TUNNEL_ID" "$cf_tunnel_id"
echo "✅ Cloudflare Tunnel ID updated"
# Create tunnel configuration with the provided tunnel ID
create_tunnel_config "$domain_name" "$cf_tunnel_id"
else
echo "⚠️ Warning: Tunnel ID format seems incorrect (should be UUID format)"
update_env_var "CF_TUNNEL_ID" "$cf_tunnel_id"
# Still create the config file even with potentially incorrect format
create_tunnel_config "$domain_name" "$cf_tunnel_id"
fi
else
echo "⚠️ Cloudflare Tunnel ID left unchanged"
# Create template config without tunnel ID
create_tunnel_config "$domain_name" "\${CF_TUNNEL_ID}"
fi
echo ""
echo "Cloudflare configuration completed!"
echo "You can now run './add-cname-records.sh' to set up DNS records."
# Show tunnel setup instructions
show_tunnel_instructions "$domain_name"
else
echo "Skipping Cloudflare configuration. You can run this script again later to configure it."
# Still create a template tunnel config
echo ""
read -p "Do you want to create a template tunnel configuration anyway? [Y/n]: " create_template
if [[ ! "$create_template" =~ ^[Nn]$ ]]; then
create_tunnel_config "$domain_name" "\${CF_TUNNEL_ID}"
echo "Template tunnel configuration created. Update CF_TUNNEL_ID in .env and regenerate if needed."
fi
fi
# Update the site_url in mkdocs.yml
echo -e "\nUpdating site_url in mkdocs.yml..."
update_mkdocs_yml "$domain_name"
# Update service URLs in services.yaml
echo -e "\nUpdating service URLs in services.yaml..."
update_services_yaml "$domain_name"
# Update the login URL in main.html
echo -e "\nUpdating login URL in main.html..."
update_main_html "$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
read -sp "Enter Listmonk admin password [default: changeMe]: " listmonk_password
echo # Add new line after password input
if [ -z "$listmonk_user" ]; then
listmonk_user="admin@example.com"
fi
if [ -z "$listmonk_password" ]; then
listmonk_password="changeMe"
fi
update_env_var "LISTMONK_ADMIN_USER" "$listmonk_user"
update_env_var "LISTMONK_ADMIN_PASSWORD" "$listmonk_password"
echo "Listmonk admin credentials updated."
# N8N User Credentials configuration
echo -e "\n---- N8N Admin Credentials ----"
read -p "Enter N8N admin email [default: admin@example.com]: " n8n_email
read -sp "Enter N8N admin password [default: changeMe]: " n8n_password
echo # Add new line after password input
if [ -z "$n8n_email" ]; then
n8n_email="admin@example.com"
fi
if [ -z "$n8n_password" ]; then
n8n_password="changeMe"
fi
update_env_var "N8N_USER_EMAIL" "$n8n_email"
update_env_var "N8N_USER_PASSWORD" "$n8n_password"
echo "N8N admin credentials updated."
# Generate secure passwords for database and encryption
echo -e "\n---- Generating Secure Passwords ----"
echo "Generating secure passwords for database and encryption keys..."
# Generate and update database password
postgres_password=$(generate_password 20)
update_env_var "POSTGRES_PASSWORD" "$postgres_password"
# Generate and update N8N encryption key
n8n_encryption_key=$(generate_password 32)
update_env_var "N8N_ENCRYPTION_KEY" "$n8n_encryption_key"
# Generate and update NocoDB passwords
nocodb_jwt_secret=$(generate_password 32)
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
gitea_db_password=$(generate_password 20)
update_env_var "GITEA_DB_PASSWD" "$gitea_db_password"
gitea_db_root_password=$(generate_password 20)
update_env_var "GITEA_DB_ROOT_PASSWORD" "$gitea_db_root_password"
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 "- 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 -e "\nYour .env file is located at: $ENV_FILE"
echo "A backup of your original .env file was created before modifications."
echo -e "\nNext steps:"
echo "1. Run 'docker-compose up -d' to start your services"
if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then
echo "2. Run './add-cname-records.sh' to set up DNS records and access policies"
fi

View File

@ -0,0 +1,35 @@
# filepath: /home/bunker-admin/ab.dem.tf.changemaker/configs/cloudflare/tunnel-config.yml
# filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml
# Cloudflare Tunnel Configuration
# Auto-generated by Changemaker Configuration Wizard
tunnel: dce04dd0-1165-4d35-ad0a-880322e36c83 # e.g. 1234567890abcdef
credentials-file: /home/bunker-admin/.cloudflared/dce04dd0-1165-4d35-ad0a-880322e36c83.json # e.g. /home/coder/.cloudflared/[insert tunnel number].json
ingress:
- hostname: homepage.albertademocracytaskforce.org
service: http://localhost:3021
- hostname: code.albertademocracytaskforce.org
service: http://localhost:8889
- hostname: listmonk.albertademocracytaskforce.org
service: http://localhost:9001
- hostname: docs.albertademocracytaskforce.org
service: http://localhost:4050
- hostname: albertademocracytaskforce.org
service: http://localhost:4051
- hostname: n8n.albertademocracytaskforce.org
service: http://localhost:5679
- hostname: db.albertademocracytaskforce.org
service: http://localhost:8091
- hostname: git.albertademocracytaskforce.org
service: http://localhost:3034
# Catch-all rule (required)
- service: http_status:404

View File

View File

53
configs/homepage/bookmarks.yaml Executable file
View File

@ -0,0 +1,53 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/bookmarks
- Documentation:
- Code Server:
- abbr: CS
href: https://github.com/coder/code-server
- MkDocs Material:
- abbr: MD
href: https://squidfunk.github.io/mkdocs-material/
- Homepage:
- abbr: HP
href: https://gethomepage.dev/
- Gitea:
- abbr: GT
href: https://docs.gitea.io/
- Services:
- Listmonk:
- abbr: LM
href: https://listmonk.app/docs/
- NocoDB:
- abbr: NC
href: https://docs.nocodb.com/
- n8n:
- abbr: N8
href: https://docs.n8n.io/
- PostgreSQL:
- abbr: PG
href: https://www.postgresql.org/docs/
- Gitea:
- abbr: GT
href: https://gitea.io/
- Resources:
- Docker:
- abbr: DC
href: https://docs.docker.com/
- Docker Compose:
- abbr: DCC
href: https://docs.docker.com/compose/
- Nginx:
- abbr: NG
href: https://nginx.org/en/docs/
- Development:
- GitHub:
- abbr: GH
href: https://github.com/
- Stack Overflow:
- abbr: SO
href: https://stackoverflow.com/

0
configs/homepage/custom.css Executable file
View File

0
configs/homepage/custom.js Executable file
View File

6
configs/homepage/docker.yaml Executable file
View File

@ -0,0 +1,6 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/docker/
my-docker:
socket: /var/run/docker.sock

View File

@ -0,0 +1,2 @@
---
# sample kubernetes config

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
---
# For public access, replace "http://localhost" with your subdomain URLs
- Essential Tools:
- Code Server:
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-lite
server: my-docker
- Listmonk:
href: "https://listmonk.albertademocracytaskforce.org" # Uncomment for public access
description: Newsletter & mailing list manager
icon: mdi-email-newsletter
widget:
type: docker
container: listmonk_app-lite
server: my-docker
- NocoDB:
href: "https://db.albertademocracytaskforce.org" # Uncomment for public access
description: No-code database platform
icon: mdi-database
widget:
type: docker
container: ab.dem.tf.changemaker-nocodb-1
server: my-docker
- Gitea:
href: "https://git.albertademocracytaskforce.org" # Uncomment for public access
description: Git repository hosting
icon: mdi-git
widget:
type: docker
container: gitea_changemaker-lite
server: my-docker
- Content & Documentation:
- MkDocs (Live):
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-lite
server: my-docker
- Static Site:
href: "https://albertademocracytaskforce.org" # Uncomment for public access
description: Built documentation hosting
icon: mdi-web
widget:
type: docker
container: mkdocs-site-server-changemaker-lite
server: my-docker
- Automation & Infrastructure:
- n8n:
href: "https://n8n.albertademocracytaskforce.org" # Uncomment for public access
description: Workflow automation platform
icon: mdi-workflow
widget:
type: docker
container: n8n-changemaker-lite
server: my-docker
- PostgreSQL (Listmonk):
href: "#"
description: Database for Listmonk
icon: mdi-database-outline
widget:
type: docker
container: listmonk_db-lite
server: my-docker
- PostgreSQL (NocoDB):
href: "#"
description: Database for NocoDB
icon: mdi-database-outline
widget:
type: docker
container: ab.dem.tf.changemaker-root_db-1
server: my-docker

41
configs/homepage/settings.yaml Executable file
View File

@ -0,0 +1,41 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/settings/
title: Changemaker Lite
description: Self-hosted platform for documentation, development, and automation
theme: dark # or light
color: purple
background:
image: /images/background.png
blur: xs # sm, "", md, xl... see https://tailwindcss.com/docs/backdrop-blur
saturate: 100 # 0, 50, 100... see https://tailwindcss.com/docs/backdrop-saturate
brightness: 75 # 0, 50, 75... see https://tailwindcss.com/docs/backdrop-brightness
opacity: 50 # 0-100
cardBlur: xl # xs, md,
headerStyle: boxed
layout:
style: columns
columns: 3
quicklaunch:
searchDescriptions: true
hideInternetSearch: true
showSearchSuggestions: true
hideVisitURL: true
provider: duckduckgo
showStats: true
bookmarks:
showCategories: true
showIcons: true
target: _blank
columns: 3
pinned:
- Essential Tools:Code Server
- Content & Documentation:MkDocs (Live)
- Automation & Infrastructure:n8n

View File

@ -0,0 +1,24 @@
---
# For configuration options and examples, please see:
# https://gethomepage.dev/configs/info-widgets/
- resources:
cpu: true
memory: true
disk: /
- greeting:
text_size: xl
text: "Welcome to Alberta Democracy Taskforce Changemaker Lite"
- datetime:
text_size: xl
format:
dateStyle: short
timeStyle: short
hour12: true
- search:
provider: duckduckgo
target: _blank

243
docker-compose.yml Normal file
View File

@ -0,0 +1,243 @@
x-db-credentials: &db-credentials
POSTGRES_USER: &db-user ${POSTGRES_USER}
POSTGRES_PASSWORD: &db-password ${POSTGRES_PASSWORD}
POSTGRES_DB: &db-name ${POSTGRES_DB}
services:
code-server:
build:
context: .
dockerfile: Dockerfile.code-server
container_name: code-server-changemaker-lite
environment:
- DOCKER_USER=${USER_NAME:-coder}
- DEFAULT_WORKSPACE=/home/coder/mkdocs/
user: "${USER_ID:-1000}:${GROUP_ID:-1000}"
volumes:
- ./configs/code-server/.config:/home/coder/.config
- ./configs/code-server/.local:/home/coder/.local
- ./mkdocs:/home/coder/mkdocs/
ports:
- "${CODE_SERVER_PORT:-8888}:8080"
restart: unless-stopped
networks:
- changemaker-lite
listmonk-app:
image: listmonk/listmonk:latest
container_name: listmonk_app-lite
restart: unless-stopped
ports:
- "${LISTMONK_PORT:-9000}:9000"
networks:
- changemaker-lite
hostname: ${LISTMONK_HOSTNAME}
depends_on:
- listmonk-db
command: [sh, -c, "./listmonk --install --idempotent --yes --config '' && ./listmonk --upgrade --yes --config '' && ./listmonk --config ''"]
environment:
LISTMONK_app__address: 0.0.0.0:9000
LISTMONK_db__user: *db-user
LISTMONK_db__password: *db-password
LISTMONK_db__database: *db-name
LISTMONK_db__host: listmonk-db
LISTMONK_db__port: 5432
LISTMONK_db__ssl_mode: disable
LISTMONK_db__max_open: 25
LISTMONK_db__max_idle: 25
LISTMONK_db__max_lifetime: 300s
TZ: Etc/UTC
LISTMONK_ADMIN_USER: ${LISTMONK_ADMIN_USER:-}
LISTMONK_ADMIN_PASSWORD: ${LISTMONK_ADMIN_PASSWORD:-}
volumes:
- ./assets/uploads:/listmonk/uploads:rw
listmonk-db:
image: postgres:17-alpine
container_name: listmonk_db-lite
restart: unless-stopped
ports:
- "127.0.0.1:${LISTMONK_DB_PORT:-5432}:5432"
networks:
- changemaker-lite
environment:
<<: *db-credentials
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 6
volumes:
- type: volume
source: listmonk-data
target: /var/lib/postgresql/data
mkdocs:
image: squidfunk/mkdocs-material
container_name: mkdocs-changemaker-lite
volumes:
- ./mkdocs:/docs:rw
- ./assets/images:/docs/assets/images:rw
user: "${USER_ID:-1000}:${GROUP_ID:-1000}"
ports:
- "${MKDOCS_PORT:-4000}:8000"
environment:
- SITE_URL=${BASE_DOMAIN:-https://changeme.org}
command: serve --dev-addr=0.0.0.0:8000 --watch-theme --livereload
networks:
- changemaker-lite
restart: unless-stopped
mkdocs-site-server:
image: lscr.io/linuxserver/nginx:latest
container_name: mkdocs-site-server-changemaker-lite
environment:
- PUID=${USER_ID:-1000} # Uses USER_ID from your .env file, defaults to 1000
- PGID=${GROUP_ID:-1000} # Uses GROUP_ID from your .env file, defaults to 1000
- TZ=Etc/UTC
volumes:
- ./mkdocs/site:/config/www # Mounts your static site to Nginx's web root
ports:
- "${MKDOCS_SITE_SERVER_PORT:-4001}:80" # Exposes Nginx's port 80 to host port 4001
restart: unless-stopped
networks:
- changemaker-lite
n8n:
image: docker.n8n.io/n8nio/n8n
container_name: n8n-changemaker-lite
restart: unless-stopped
ports:
- "${N8N_PORT:-5678}:5678"
environment:
- N8N_HOST=${N8N_HOST:-n8n.${DOMAIN}}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://${N8N_HOST:-n8n.${DOMAIN}}/
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE:-UTC}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:-changeMe}
- N8N_USER_MANAGEMENT_DISABLED=false
- N8N_DEFAULT_USER_EMAIL=${N8N_USER_EMAIL:-admin@example.com}
- N8N_DEFAULT_USER_PASSWORD=${N8N_USER_PASSWORD:-changeMe}
volumes:
- n8n_data:/home/node/.n8n
- ./local-files:/files
networks:
- changemaker-lite
nocodb:
depends_on:
root_db:
condition: service_healthy
environment:
NC_DB: "pg://root_db:5432?u=postgres&p=password&d=root_db"
image: "nocodb/nocodb:latest"
ports:
- "${NOCODB_PORT:-8090}:8080"
restart: always
volumes:
- "nc_data:/usr/app/data"
networks:
- changemaker-lite
root_db:
environment:
POSTGRES_DB: root_db
POSTGRES_PASSWORD: password
POSTGRES_USER: postgres
healthcheck:
interval: 10s
retries: 10
test: "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""
timeout: 2s
image: postgres:16.6
restart: always
volumes:
- "db_data:/var/lib/postgresql/data"
networks:
- changemaker-lite
# Homepage App
homepage-changemaker:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage-changemaker-lite
ports:
- "${HOMEPAGE_PORT:-3010}:3000"
volumes:
- ./configs/homepage:/app/config
- ./assets/icons:/app/public/icons
- ./assets/images:/app/public/images
- /var/run/docker.sock:/var/run/docker.sock
environment:
- PUID=${USER_ID:-1000}
- PGID=${DOCKER_GROUP_ID:-984}
- TZ=Etc/UTC
- HOMEPAGE_ALLOWED_HOSTS=*
- HOMEPAGE_VAR_BASE_URL=${HOMEPAGE_VAR_BASE_URL:-http://localhost}
restart: unless-stopped
networks:
- changemaker-lite
# Gitea - Git service
gitea-app:
image: gitea/gitea:1.23.7
container_name: gitea_changemaker-lite
environment:
- USER_UID=${USER_ID:-1000}
- USER_GID=${GROUP_ID:-1000}
- GITEA__database__DB_TYPE=${GITEA_DB_TYPE:-mysql}
- GITEA__database__HOST=${GITEA_DB_HOST:-gitea-db:3306}
- GITEA__database__NAME=${GITEA_DB_NAME:-gitea}
- GITEA__database__USER=${GITEA_DB_USER:-gitea}
- GITEA__database__PASSWD=${GITEA_DB_PASSWD}
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
- GITEA__server__HTTP_PORT=3000
- GITEA__server__PROTOCOL=http
- GITEA__server__DOMAIN=${GITEA_DOMAIN}
- GITEA__server__ENABLE_GZIP=true
- GITEA__server__PROXY_PROTOCOL=true
- GITEA__server__PROXY_PROXY_PROTOCOL_TLS=true
- GITEA__server__PROXY_ALLOW_SUBNET=0.0.0.0/0
restart: unless-stopped
networks:
- changemaker-lite
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GITEA_WEB_PORT:-3030}:3000"
- "${GITEA_SSH_PORT:-2222}:22"
depends_on:
- gitea-db
gitea-db:
image: mysql:8
container_name: gitea_mysql_changemaker-lite
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=${GITEA_DB_ROOT_PASSWORD}
- MYSQL_USER=${GITEA_DB_USER:-gitea}
- MYSQL_PASSWORD=${GITEA_DB_PASSWD}
- MYSQL_DATABASE=${GITEA_DB_NAME:-gitea}
networks:
- changemaker-lite
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "${GITEA_DB_USER:-gitea}", "-p${GITEA_DB_PASSWD}"]
interval: 10s
timeout: 5s
retries: 5
networks:
changemaker-lite:
driver: bridge
volumes:
listmonk-data:
n8n_data:
nc_data:
db_data:
gitea_data:
mysql_data:

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show More