mirror of
https://github.com/kossakovsky/n8n-install.git
synced 2026-03-09 15:25:33 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
277466f144 | ||
|
|
58c485e49a | ||
|
|
b34e1468aa | ||
|
|
6a1301bfc0 | ||
|
|
19325191c3 | ||
|
|
107f18296a | ||
|
|
059e141daa | ||
|
|
6505c5cdf4 | ||
|
|
f8e665f85f | ||
|
|
f2f51c6e13 | ||
|
|
ceaa970273 | ||
|
|
6f1aaa0555 | ||
|
|
0dec31539e | ||
|
|
b990b09681 | ||
|
|
de8df8a0b7 | ||
|
|
543593de36 | ||
|
|
50bd817b56 | ||
|
|
611591dc0f | ||
|
|
ad9c7aa57d | ||
|
|
6e283c508c | ||
|
|
adc5b94f1c |
22
.env.example
22
.env.example
@@ -99,6 +99,15 @@ NEO4J_AUTH_PASSWORD=
|
|||||||
NOCODB_JWT_SECRET=
|
NOCODB_JWT_SECRET=
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# [required]
|
||||||
|
# Appsmith encryption credentials (auto-generated)
|
||||||
|
############
|
||||||
|
|
||||||
|
APPSMITH_ENCRYPTION_PASSWORD=
|
||||||
|
APPSMITH_ENCRYPTION_SALT=
|
||||||
|
|
||||||
|
|
||||||
############
|
############
|
||||||
# [required]
|
# [required]
|
||||||
# Langfuse credentials
|
# Langfuse credentials
|
||||||
@@ -148,6 +157,7 @@ LT_PASSWORD_HASH=
|
|||||||
|
|
||||||
USER_DOMAIN_NAME=
|
USER_DOMAIN_NAME=
|
||||||
LETSENCRYPT_EMAIL=
|
LETSENCRYPT_EMAIL=
|
||||||
|
APPSMITH_HOSTNAME=appsmith.yourdomain.com
|
||||||
COMFYUI_HOSTNAME=comfyui.yourdomain.com
|
COMFYUI_HOSTNAME=comfyui.yourdomain.com
|
||||||
DATABASUS_HOSTNAME=databasus.yourdomain.com
|
DATABASUS_HOSTNAME=databasus.yourdomain.com
|
||||||
DIFY_HOSTNAME=dify.yourdomain.com
|
DIFY_HOSTNAME=dify.yourdomain.com
|
||||||
@@ -430,11 +440,13 @@ GOST_PROXY_URL=
|
|||||||
|
|
||||||
# External upstream proxy (REQUIRED - asked during wizard if gost is selected)
|
# External upstream proxy (REQUIRED - asked during wizard if gost is selected)
|
||||||
# Examples: socks5://user:pass@proxy.com:1080, http://user:pass@proxy.com:8080
|
# Examples: socks5://user:pass@proxy.com:1080, http://user:pass@proxy.com:8080
|
||||||
|
# IMPORTANT: For HTTP proxies use http://, NOT https://
|
||||||
|
# The protocol refers to proxy type, not connection security.
|
||||||
GOST_UPSTREAM_PROXY=
|
GOST_UPSTREAM_PROXY=
|
||||||
|
|
||||||
# Internal services bypass list (prevents internal Docker traffic from going through proxy)
|
# Internal services bypass list (prevents internal Docker traffic from going through proxy)
|
||||||
# Includes: Docker internal networks (172.16-31.*, 10.*), Docker DNS (127.0.0.11), and all service hostnames
|
# Includes: Docker internal networks (172.16-31.*, 10.*), Docker DNS (127.0.0.11), and all service hostnames
|
||||||
GOST_NO_PROXY=localhost,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.local,postgres,postgres:5432,redis,redis:6379,caddy,ollama,neo4j,qdrant,weaviate,clickhouse,minio,searxng,crawl4ai,gotenberg,langfuse-web,langfuse-worker,flowise,n8n,n8n-import,n8n-worker-1,n8n-worker-2,n8n-worker-3,n8n-worker-4,n8n-worker-5,n8n-worker-6,n8n-worker-7,n8n-worker-8,n8n-worker-9,n8n-worker-10,n8n-runner-1,n8n-runner-2,n8n-runner-3,n8n-runner-4,n8n-runner-5,n8n-runner-6,n8n-runner-7,n8n-runner-8,n8n-runner-9,n8n-runner-10,letta,lightrag,docling,postiz,temporal,temporal-ui,ragflow,ragflow-mysql,ragflow-minio,ragflow-redis,ragflow-elasticsearch,ragapp,open-webui,comfyui,waha,libretranslate,paddleocr,nocodb,db,studio,kong,auth,rest,realtime,storage,imgproxy,meta,functions,analytics,vector,supavisor,gost
|
GOST_NO_PROXY=localhost,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.local,appsmith,postgres,postgres:5432,redis,redis:6379,caddy,ollama,neo4j,qdrant,weaviate,clickhouse,minio,searxng,crawl4ai,gotenberg,langfuse-web,langfuse-worker,flowise,n8n,n8n-import,n8n-worker-1,n8n-worker-2,n8n-worker-3,n8n-worker-4,n8n-worker-5,n8n-worker-6,n8n-worker-7,n8n-worker-8,n8n-worker-9,n8n-worker-10,n8n-runner-1,n8n-runner-2,n8n-runner-3,n8n-runner-4,n8n-runner-5,n8n-runner-6,n8n-runner-7,n8n-runner-8,n8n-runner-9,n8n-runner-10,letta,lightrag,docling,postiz,temporal,temporal-ui,ragflow,ragflow-mysql,ragflow-minio,ragflow-redis,ragflow-elasticsearch,ragapp,open-webui,comfyui,waha,libretranslate,paddleocr,nocodb,db,studio,kong,auth,rest,realtime,storage,imgproxy,meta,functions,analytics,vector,supavisor,gost,api.telegram.org,telegram.org,t.me,core.telegram.org
|
||||||
|
|
||||||
############
|
############
|
||||||
# Functions - Configuration for Functions
|
# Functions - Configuration for Functions
|
||||||
@@ -475,6 +487,14 @@ DIFY_SECRET_KEY=
|
|||||||
DIFY_EXPOSE_NGINX_PORT=8080
|
DIFY_EXPOSE_NGINX_PORT=8080
|
||||||
DIFY_EXPOSE_NGINX_SSL_PORT=9443
|
DIFY_EXPOSE_NGINX_SSL_PORT=9443
|
||||||
|
|
||||||
|
############
|
||||||
|
# Docker Compose parallel limit
|
||||||
|
# Limits the number of simultaneous Docker image pulls to prevent
|
||||||
|
# "net/http: TLS handshake timeout" errors when many services are selected.
|
||||||
|
# Increase this value if you have a fast network connection.
|
||||||
|
############
|
||||||
|
COMPOSE_PARALLEL_LIMIT=3
|
||||||
|
|
||||||
###########################################################################################
|
###########################################################################################
|
||||||
COMPOSE_PROFILES="n8n,portainer,monitoring,databasus"
|
COMPOSE_PROFILES="n8n,portainer,monitoring,databasus"
|
||||||
PROMETHEUS_PASSWORD_HASH=
|
PROMETHEUS_PASSWORD_HASH=
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ dify/
|
|||||||
volumes/
|
volumes/
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
docker-compose.n8n-workers.yml
|
docker-compose.n8n-workers.yml
|
||||||
|
postiz.env
|
||||||
welcome/data.json
|
welcome/data.json
|
||||||
welcome/changelog.json
|
welcome/changelog.json
|
||||||
|
|
||||||
|
|||||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -2,6 +2,61 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.3.3] - 2026-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Postiz** - Generate `postiz.env` file to prevent `dotenv-cli` crash in backend container (#40). Handles edge case where Docker creates the file as a directory, and quotes values to prevent misparses.
|
||||||
|
|
||||||
|
## [1.3.2] - 2026-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Docker Compose** - Respect `docker-compose.override.yml` for user customizations (#44). All compose file assembly points now include the override file when present.
|
||||||
|
|
||||||
|
## [1.3.1] - 2026-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Installer** - Skip n8n workflow import and worker configuration prompts when n8n profile is not selected
|
||||||
|
|
||||||
|
## [1.3.0] - 2026-02-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Appsmith** - Low-code platform for building internal tools, dashboards, and admin panels
|
||||||
|
|
||||||
|
## [1.2.8] - 2026-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Ragflow** - Fix nginx config mount path (`sites-available/default` → `conf.d/default.conf`) to resolve default "Welcome to nginx!" page (#41)
|
||||||
|
|
||||||
|
## [1.2.7] - 2026-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Docker** - Limit parallel image pulls (`COMPOSE_PARALLEL_LIMIT=3`) to prevent `TLS handshake timeout` errors when many services are selected
|
||||||
|
|
||||||
|
## [1.2.6] - 2026-02-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **ComfyUI** - Update Docker image to CUDA 12.8 (`cu128-slim`)
|
||||||
|
|
||||||
|
## [1.2.5] - 2026-02-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **n8n** - Use static ffmpeg binaries for Alpine/musl compatibility (fixes glibc errors)
|
||||||
|
|
||||||
|
## [1.2.4] - 2026-01-30
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Postiz** - Fix `BACKEND_INTERNAL_URL` to use `localhost` instead of Docker hostname (internal nginx requires localhost)
|
||||||
|
|
||||||
|
## [1.2.3] - 2026-01-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Gost proxy** - Add Telegram domains to `GOST_NO_PROXY` bypass list for n8n Telegram triggers
|
||||||
|
|
||||||
|
## [1.2.2] - 2026-01-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Custom TLS** - Fix duplicate hostname error when using custom certificates. Changed architecture from generating separate site blocks to using a shared TLS snippet that all services import.
|
||||||
|
|
||||||
## [1.2.1] - 2026-01-16
|
## [1.2.1] - 2026-01-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
59
CLAUDE.md
59
CLAUDE.md
@@ -10,7 +10,7 @@ This is **n8n-install**, a Docker Compose-based installer that provides a compre
|
|||||||
|
|
||||||
- **Profile-based service management**: Services are activated via Docker Compose profiles (e.g., `n8n`, `flowise`, `monitoring`). Profiles are stored in the `.env` file's `COMPOSE_PROFILES` variable.
|
- **Profile-based service management**: Services are activated via Docker Compose profiles (e.g., `n8n`, `flowise`, `monitoring`). Profiles are stored in the `.env` file's `COMPOSE_PROFILES` variable.
|
||||||
- **No exposed ports**: Services do NOT publish ports directly. All external HTTPS access is routed through Caddy reverse proxy on ports 80/443.
|
- **No exposed ports**: Services do NOT publish ports directly. All external HTTPS access is routed through Caddy reverse proxy on ports 80/443.
|
||||||
- **Shared secrets**: Core services (Postgres, Redis/Valkey, Caddy) are always included. Other services are optional and selected during installation.
|
- **Shared secrets**: Core services (Postgres, Valkey (Redis-compatible, container named `redis` for backward compatibility), Caddy) are always included. Other services are optional and selected during installation.
|
||||||
- **Queue-based n8n**: n8n runs in `queue` mode with Redis, Postgres, and dynamically scaled workers (`N8N_WORKER_COUNT`).
|
- **Queue-based n8n**: n8n runs in `queue` mode with Redis, Postgres, and dynamically scaled workers (`N8N_WORKER_COUNT`).
|
||||||
|
|
||||||
### Key Files
|
### Key Files
|
||||||
@@ -40,9 +40,14 @@ This is **n8n-install**, a Docker Compose-based installer that provides a compre
|
|||||||
- `scripts/docker_cleanup.sh`: Removes unused Docker resources (used by `make clean`)
|
- `scripts/docker_cleanup.sh`: Removes unused Docker resources (used by `make clean`)
|
||||||
- `scripts/download_top_workflows.sh`: Downloads community n8n workflows
|
- `scripts/download_top_workflows.sh`: Downloads community n8n workflows
|
||||||
- `scripts/import_workflows.sh`: Imports workflows from `n8n/backup/workflows/` into n8n (used by `make import`)
|
- `scripts/import_workflows.sh`: Imports workflows from `n8n/backup/workflows/` into n8n (used by `make import`)
|
||||||
|
- `scripts/restart.sh`: Restarts services with proper compose file handling (used by `make restart`)
|
||||||
|
- `scripts/setup_custom_tls.sh`: Configures custom TLS certificates (used by `make setup-tls`); supports `--remove` to revert to Let's Encrypt
|
||||||
|
- `start_services.py`: Python orchestrator for service startup order, builds Docker images, handles external services (Supabase/Dify cloning, env preparation, startup), generates SearXNG secret key, stops existing containers. Uses `python-dotenv` (`dotenv_values`).
|
||||||
|
|
||||||
**Project Name**: All docker-compose commands use `-p localai` (defined in Makefile as `PROJECT_NAME := localai`).
|
**Project Name**: All docker-compose commands use `-p localai` (defined in Makefile as `PROJECT_NAME := localai`).
|
||||||
|
|
||||||
|
**Version**: Stored in `VERSION` file at repository root.
|
||||||
|
|
||||||
### Installation Flow
|
### Installation Flow
|
||||||
|
|
||||||
`scripts/install.sh` orchestrates the installation by running numbered scripts in sequence:
|
`scripts/install.sh` orchestrates the installation by running numbered scripts in sequence:
|
||||||
@@ -56,7 +61,9 @@ This is **n8n-install**, a Docker Compose-based installer that provides a compre
|
|||||||
7. `07_final_report.sh` - Display credentials and URLs
|
7. `07_final_report.sh` - Display credentials and URLs
|
||||||
8. `08_fix_permissions.sh` - Fix file ownership for non-root access
|
8. `08_fix_permissions.sh` - Fix file ownership for non-root access
|
||||||
|
|
||||||
The update flow (`scripts/update.sh`) similarly orchestrates: git fetch + reset → service selection → `apply_update.sh` → restart.
|
The update flow (`scripts/update.sh`) similarly orchestrates: git fetch + reset → service selection → `apply_update.sh` → restart. During updates, `03_generate_secrets.sh --update` adds new variables from `.env.example` without regenerating existing ones (preserves user-set values).
|
||||||
|
|
||||||
|
**Git update modes**: Default is `reset` (hard reset to origin). Set `GIT_MODE=merge` in `.env` for fork workflows (merges from upstream instead of hard reset). The `make git-pull` command uses merge mode. Git branch support is explicit: `GIT_SUPPORTED_BRANCHES=("main" "develop")` in `git.sh`; unknown branches warn and fall back to `main`.
|
||||||
|
|
||||||
## Common Development Commands
|
## Common Development Commands
|
||||||
|
|
||||||
@@ -98,7 +105,7 @@ Follow this workflow when adding a new optional service (refer to `.claude/comma
|
|||||||
3. **.env.example**: Add `MYSERVICE_HOSTNAME=myservice.yourdomain.com` and credentials if using basic auth.
|
3. **.env.example**: Add `MYSERVICE_HOSTNAME=myservice.yourdomain.com` and credentials if using basic auth.
|
||||||
4. **scripts/03_generate_secrets.sh**: Generate passwords and bcrypt hashes. Add to `VARS_TO_GENERATE` map.
|
4. **scripts/03_generate_secrets.sh**: Generate passwords and bcrypt hashes. Add to `VARS_TO_GENERATE` map.
|
||||||
5. **scripts/04_wizard.sh**: Add service to `base_services_data` array for wizard selection.
|
5. **scripts/04_wizard.sh**: Add service to `base_services_data` array for wizard selection.
|
||||||
6. **scripts/databases.sh**: If service uses PostgreSQL, add database name to `INIT_DB_DATABASES` array.
|
6. **scripts/databases.sh**: If service uses PostgreSQL, add database name to `INIT_DB_DATABASES` array. Database creation is idempotent (checks existence before creating). Note: Postiz also requires `temporal` and `temporal_visibility` databases.
|
||||||
7. **scripts/generate_welcome_page.sh**: Add service to `SERVICES_ARRAY` for welcome dashboard.
|
7. **scripts/generate_welcome_page.sh**: Add service to `SERVICES_ARRAY` for welcome dashboard.
|
||||||
8. **welcome/app.js**: Add `SERVICE_METADATA` entry with name, description, icon, color, category.
|
8. **welcome/app.js**: Add `SERVICE_METADATA` entry with name, description, icon, color, category.
|
||||||
9. **scripts/07_final_report.sh**: Add service URL and credentials output using `is_profile_active "myservice"`.
|
9. **scripts/07_final_report.sh**: Add service URL and credentials output using `is_profile_active "myservice"`.
|
||||||
@@ -156,11 +163,11 @@ This project uses [Semantic Versioning](https://semver.org/). When updating `CHA
|
|||||||
- Configuration stored in `docker-compose.n8n-workers.yml` (auto-generated, gitignored)
|
- Configuration stored in `docker-compose.n8n-workers.yml` (auto-generated, gitignored)
|
||||||
- Runner connects to its worker via `network_mode: "service:n8n-worker-N"` (localhost:5679)
|
- Runner connects to its worker via `network_mode: "service:n8n-worker-N"` (localhost:5679)
|
||||||
- Runner image `n8nio/runners` must match n8n version
|
- Runner image `n8nio/runners` must match n8n version
|
||||||
|
- **Template profile pattern**: `docker-compose.yml` defines `n8n-worker-template` and `n8n-runner-template` with `profiles: ["n8n-template"]` (never activated directly). `generate_n8n_workers.sh` uses these as templates to generate `docker-compose.n8n-workers.yml` with the actual worker/runner services.
|
||||||
- **Scaling**: Change `N8N_WORKER_COUNT` in `.env` and run `bash scripts/generate_n8n_workers.sh`
|
- **Scaling**: Change `N8N_WORKER_COUNT` in `.env` and run `bash scripts/generate_n8n_workers.sh`
|
||||||
- **Code node libraries**: Configured via `n8n/n8n-task-runners.json` and `n8n/Dockerfile.runner`:
|
- **Code node libraries**: Configured via `n8n/n8n-task-runners.json` and `n8n/Dockerfile.runner`:
|
||||||
- JS packages installed via `pnpm add` in Dockerfile.runner
|
- **JavaScript runner**: packages installed via `pnpm add` in Dockerfile.runner; allowlist in `n8n-task-runners.json` (`NODE_FUNCTION_ALLOW_EXTERNAL`, `NODE_FUNCTION_ALLOW_BUILTIN`); default packages: `cheerio`, `axios`, `moment`, `lodash`
|
||||||
- Allowlist configured in `n8n-task-runners.json` (`NODE_FUNCTION_ALLOW_EXTERNAL`, `NODE_FUNCTION_ALLOW_BUILTIN`)
|
- **Python runner**: also configured in `n8n-task-runners.json`; uses `/opt/runners/task-runner-python/.venv/bin/python` with `N8N_RUNNERS_STDLIB_ALLOW: "*"` and `N8N_RUNNERS_EXTERNAL_ALLOW: "*"`
|
||||||
- Default packages: `cheerio`, `axios`, `moment`, `lodash`
|
|
||||||
- Workflows can access the host filesystem via `/data/shared` (mapped to `./shared`)
|
- Workflows can access the host filesystem via `/data/shared` (mapped to `./shared`)
|
||||||
- `N8N_BLOCK_ENV_ACCESS_IN_NODE=false` allows Code nodes to access environment variables
|
- `N8N_BLOCK_ENV_ACCESS_IN_NODE=false` allows Code nodes to access environment variables
|
||||||
|
|
||||||
@@ -170,6 +177,18 @@ This project uses [Semantic Versioning](https://semver.org/). When updating `CHA
|
|||||||
- Hostnames are passed via environment variables (e.g., `N8N_HOSTNAME`, `FLOWISE_HOSTNAME`)
|
- Hostnames are passed via environment variables (e.g., `N8N_HOSTNAME`, `FLOWISE_HOSTNAME`)
|
||||||
- Basic auth uses bcrypt hashes generated by `scripts/03_generate_secrets.sh` via Caddy's hash command
|
- Basic auth uses bcrypt hashes generated by `scripts/03_generate_secrets.sh` via Caddy's hash command
|
||||||
- Never add `ports:` to services in docker-compose.yml; let Caddy handle all external access
|
- Never add `ports:` to services in docker-compose.yml; let Caddy handle all external access
|
||||||
|
- **Caddy Addons** (`caddy-addon/`): Extend Caddy config without modifying the main Caddyfile. Files matching `site-*.conf` are auto-imported (gitignored, user-created). TLS is controlled via `tls-snippet.conf` (all service blocks use `import service_tls`). See `caddy-addon/README.md` for details.
|
||||||
|
- Custom TLS certificates go in `certs/` directory (gitignored), referenced as `/etc/caddy/certs/` inside the container
|
||||||
|
|
||||||
|
### External Compose Files (Supabase/Dify)
|
||||||
|
|
||||||
|
Complex services like Supabase and Dify maintain their own upstream docker-compose files:
|
||||||
|
- `start_services.py` handles cloning repos, preparing `.env` files, and starting services
|
||||||
|
- Each external service needs: `is_*_enabled()`, `clone_*_repo()`, `prepare_*_env()`, `start_*()` functions in `start_services.py`
|
||||||
|
- `scripts/utils.sh` provides `get_*_compose()` getter functions and `build_compose_files_array()` includes them
|
||||||
|
- `stop_all_services()` in `start_services.py` checks compose file existence (not profile) to ensure cleanup when a profile is removed
|
||||||
|
- All external compose files use the same project name (`-p localai`) so containers appear together
|
||||||
|
- **`docker-compose.override.yml`**: User customizations file (gitignored). Both `start_services.py` and `build_compose_files_array()` in `utils.sh` auto-detect and include it last (highest precedence). Users can override any service property without modifying tracked files.
|
||||||
|
|
||||||
### Secret Generation
|
### Secret Generation
|
||||||
|
|
||||||
@@ -178,6 +197,7 @@ The `scripts/03_generate_secrets.sh` script:
|
|||||||
- Creates bcrypt password hashes using Caddy's `hash-password` command
|
- Creates bcrypt password hashes using Caddy's `hash-password` command
|
||||||
- Preserves existing user-provided values in `.env`
|
- Preserves existing user-provided values in `.env`
|
||||||
- Supports different secret types via `VARS_TO_GENERATE` map: `password:32`, `jwt`, `api_key`, `base64:64`, `hex:32`
|
- Supports different secret types via `VARS_TO_GENERATE` map: `password:32`, `jwt`, `api_key`, `base64:64`, `hex:32`
|
||||||
|
- When called with `--update` flag (during updates), only adds new variables without regenerating existing ones
|
||||||
|
|
||||||
### Utility Functions (scripts/utils.sh)
|
### Utility Functions (scripts/utils.sh)
|
||||||
|
|
||||||
@@ -212,11 +232,24 @@ Common profiles:
|
|||||||
- `langfuse`: Langfuse observability (includes ClickHouse, MinIO, worker, web)
|
- `langfuse`: Langfuse observability (includes ClickHouse, MinIO, worker, web)
|
||||||
- `cpu`, `gpu-nvidia`, `gpu-amd`: Ollama hardware profiles (mutually exclusive)
|
- `cpu`, `gpu-nvidia`, `gpu-amd`: Ollama hardware profiles (mutually exclusive)
|
||||||
- `cloudflare-tunnel`: Cloudflare Tunnel for zero-trust access (see `cloudflare-instructions.md`)
|
- `cloudflare-tunnel`: Cloudflare Tunnel for zero-trust access (see `cloudflare-instructions.md`)
|
||||||
|
- `supabase`: Supabase BaaS (external compose, cloned at runtime; mutually exclusive with `dify`)
|
||||||
|
- `dify`: Dify AI platform (external compose, cloned at runtime; mutually exclusive with `supabase`)
|
||||||
- `gost`: HTTP/HTTPS proxy for routing AI service outbound traffic
|
- `gost`: HTTP/HTTPS proxy for routing AI service outbound traffic
|
||||||
- `python-runner`: Internal Python execution environment (no external access)
|
- `python-runner`: Internal Python execution environment (no external access)
|
||||||
|
- `searxng`, `letta`, `lightrag`, `libretranslate`, `crawl4ai`, `docling`, `waha`, `comfyui`, `paddleocr`, `ragapp`, `gotenberg`, `postiz`: Additional optional services
|
||||||
|
|
||||||
## Architecture Patterns
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Docker Compose YAML Anchors
|
||||||
|
|
||||||
|
`docker-compose.yml` defines reusable anchors at the top:
|
||||||
|
- `x-logging: &default-logging` - `json-file` with `max-size: 1m`, `max-file: 1`
|
||||||
|
- `x-proxy-env: &proxy-env` - HTTP/HTTPS proxy vars from `GOST_PROXY_URL`/`GOST_NO_PROXY`
|
||||||
|
- `x-n8n: &service-n8n` - Full n8n service definition (reused by workers via `extends`)
|
||||||
|
- `x-ollama: &service-ollama` - Ollama service definition (reused by CPU/GPU variants)
|
||||||
|
- `x-init-ollama: &init-ollama` - Ollama model pre-puller (auto-pulls `qwen2.5:7b-instruct-q4_K_M` and `nomic-embed-text`)
|
||||||
|
- `x-n8n-worker-runner: &service-n8n-worker-runner` - Runner template for worker generation
|
||||||
|
|
||||||
### Healthchecks
|
### Healthchecks
|
||||||
|
|
||||||
Services should define healthchecks for proper dependency management:
|
Services should define healthchecks for proper dependency management:
|
||||||
@@ -276,6 +309,8 @@ healthcheck:
|
|||||||
test: ["CMD-SHELL", "http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= wget -qO- http://localhost:8080/health || exit 1"]
|
test: ["CMD-SHELL", "http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= wget -qO- http://localhost:8080/health || exit 1"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**GOST_NO_PROXY**: ALL service container names must be listed in `GOST_NO_PROXY` in `.env.example`. This prevents internal Docker network traffic from routing through the proxy. This applies to every service, not just those using `<<: *proxy-env`.
|
||||||
|
|
||||||
### Welcome Page Dashboard
|
### Welcome Page Dashboard
|
||||||
|
|
||||||
The welcome page (`welcome/`) provides a post-install dashboard showing all active services:
|
The welcome page (`welcome/`) provides a post-install dashboard showing all active services:
|
||||||
@@ -291,6 +326,10 @@ Directories in `PRESERVE_DIRS` (defined in `scripts/utils.sh`) survive git updat
|
|||||||
|
|
||||||
These are backed up before `git reset --hard` and restored after.
|
These are backed up before `git reset --hard` and restored after.
|
||||||
|
|
||||||
|
### Restart Behavior
|
||||||
|
|
||||||
|
`scripts/restart.sh` stops all services first, then starts external stacks (Supabase/Dify) separately before the main stack (10s delay between). This is required because external compose files use relative volume paths that resolve from their own directory.
|
||||||
|
|
||||||
## Common Issues and Solutions
|
## Common Issues and Solutions
|
||||||
|
|
||||||
### Service won't start after adding
|
### Service won't start after adding
|
||||||
@@ -311,7 +350,11 @@ These are backed up before `git reset --hard` and restored after.
|
|||||||
## File Locations
|
## File Locations
|
||||||
|
|
||||||
- Shared files accessible by n8n: `./shared` (mounted as `/data/shared` in n8n)
|
- Shared files accessible by n8n: `./shared` (mounted as `/data/shared` in n8n)
|
||||||
|
- n8n backup/workflows: `n8n/backup/workflows/` (mounted as `/backup` in n8n containers)
|
||||||
- n8n storage: Docker volume `localai_n8n_storage`
|
- n8n storage: Docker volume `localai_n8n_storage`
|
||||||
|
- Flowise storage: `~/.flowise` on host (mounted from user's home directory, not a named volume)
|
||||||
|
- Custom TLS certificates: `certs/` (gitignored, mounted as `/etc/caddy/certs/`)
|
||||||
|
- Caddy addon configs: `caddy-addon/site-*.conf` (gitignored, auto-imported)
|
||||||
- Service-specific volumes: Defined in `volumes:` section at top of `docker-compose.yml`
|
- Service-specific volumes: Defined in `volumes:` section at top of `docker-compose.yml`
|
||||||
- Installation logs: stdout during script execution
|
- Installation logs: stdout during script execution
|
||||||
- Service logs: `docker compose -p localai logs <service>`
|
- Service logs: `docker compose -p localai logs <service>`
|
||||||
@@ -338,6 +381,10 @@ bash -n scripts/generate_n8n_workers.sh
|
|||||||
bash -n scripts/apply_update.sh
|
bash -n scripts/apply_update.sh
|
||||||
bash -n scripts/update.sh
|
bash -n scripts/update.sh
|
||||||
bash -n scripts/install.sh
|
bash -n scripts/install.sh
|
||||||
|
bash -n scripts/restart.sh
|
||||||
|
bash -n scripts/doctor.sh
|
||||||
|
bash -n scripts/setup_custom_tls.sh
|
||||||
|
bash -n scripts/docker_cleanup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Testing
|
### Full Testing
|
||||||
|
|||||||
53
Caddyfile
53
Caddyfile
@@ -3,30 +3,44 @@
|
|||||||
email {$LETSENCRYPT_EMAIL}
|
email {$LETSENCRYPT_EMAIL}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Import TLS snippet (must be before service blocks)
|
||||||
|
# Default: Let's Encrypt automatic certificates
|
||||||
|
# Custom: Run 'make setup-tls' to use your own certificates
|
||||||
|
import /etc/caddy/addons/tls-snippet.conf
|
||||||
|
|
||||||
|
# Appsmith
|
||||||
|
{$APPSMITH_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
|
reverse_proxy appsmith:80
|
||||||
|
}
|
||||||
|
|
||||||
# N8N
|
# N8N
|
||||||
{$N8N_HOSTNAME} {
|
{$N8N_HOSTNAME} {
|
||||||
# For domains, Caddy will automatically use Let's Encrypt
|
import service_tls
|
||||||
# For localhost/port addresses, HTTPS won't be enabled
|
|
||||||
reverse_proxy n8n:5678
|
reverse_proxy n8n:5678
|
||||||
}
|
}
|
||||||
|
|
||||||
# Open WebUI
|
# Open WebUI
|
||||||
{$WEBUI_HOSTNAME} {
|
{$WEBUI_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy open-webui:8080
|
reverse_proxy open-webui:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
# Flowise
|
# Flowise
|
||||||
{$FLOWISE_HOSTNAME} {
|
{$FLOWISE_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy flowise:3001
|
reverse_proxy flowise:3001
|
||||||
}
|
}
|
||||||
|
|
||||||
# Dify
|
# Dify
|
||||||
{$DIFY_HOSTNAME} {
|
{$DIFY_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy nginx:80
|
reverse_proxy nginx:80
|
||||||
}
|
}
|
||||||
|
|
||||||
# RAGApp
|
# RAGApp
|
||||||
{$RAGAPP_HOSTNAME} {
|
{$RAGAPP_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$RAGAPP_USERNAME} {$RAGAPP_PASSWORD_HASH}
|
{$RAGAPP_USERNAME} {$RAGAPP_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -35,37 +49,38 @@
|
|||||||
|
|
||||||
# RAGFlow
|
# RAGFlow
|
||||||
{$RAGFLOW_HOSTNAME} {
|
{$RAGFLOW_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy ragflow:80
|
reverse_proxy ragflow:80
|
||||||
}
|
}
|
||||||
|
|
||||||
# Langfuse
|
# Langfuse
|
||||||
{$LANGFUSE_HOSTNAME} {
|
{$LANGFUSE_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy langfuse-web:3000
|
reverse_proxy langfuse-web:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# # Ollama API
|
|
||||||
# {$OLLAMA_HOSTNAME} {
|
|
||||||
# reverse_proxy ollama:11434
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Supabase
|
# Supabase
|
||||||
{$SUPABASE_HOSTNAME} {
|
{$SUPABASE_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy kong:8000
|
reverse_proxy kong:8000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grafana
|
# Grafana
|
||||||
{$GRAFANA_HOSTNAME} {
|
{$GRAFANA_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy grafana:3000
|
reverse_proxy grafana:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# WAHA (WhatsApp HTTP API)
|
# WAHA (WhatsApp HTTP API)
|
||||||
{$WAHA_HOSTNAME} {
|
{$WAHA_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy waha:3000
|
reverse_proxy waha:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prometheus
|
# Prometheus
|
||||||
{$PROMETHEUS_HOSTNAME} {
|
{$PROMETHEUS_HOSTNAME} {
|
||||||
basic_auth {
|
import service_tls
|
||||||
|
basic_auth {
|
||||||
{$PROMETHEUS_USERNAME} {$PROMETHEUS_PASSWORD_HASH}
|
{$PROMETHEUS_USERNAME} {$PROMETHEUS_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
reverse_proxy prometheus:9090
|
reverse_proxy prometheus:9090
|
||||||
@@ -73,16 +88,19 @@
|
|||||||
|
|
||||||
# Portainer
|
# Portainer
|
||||||
{$PORTAINER_HOSTNAME} {
|
{$PORTAINER_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy portainer:9000
|
reverse_proxy portainer:9000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Postiz
|
# Postiz
|
||||||
{$POSTIZ_HOSTNAME} {
|
{$POSTIZ_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy postiz:5000
|
reverse_proxy postiz:5000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Temporal UI (workflow orchestration for Postiz)
|
# Temporal UI (workflow orchestration for Postiz)
|
||||||
{$TEMPORAL_UI_HOSTNAME} {
|
{$TEMPORAL_UI_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$TEMPORAL_UI_USERNAME} {$TEMPORAL_UI_PASSWORD_HASH}
|
{$TEMPORAL_UI_USERNAME} {$TEMPORAL_UI_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -91,31 +109,37 @@
|
|||||||
|
|
||||||
# Databasus
|
# Databasus
|
||||||
{$DATABASUS_HOSTNAME} {
|
{$DATABASUS_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy databasus:4005
|
reverse_proxy databasus:4005
|
||||||
}
|
}
|
||||||
|
|
||||||
# Letta
|
# Letta
|
||||||
{$LETTA_HOSTNAME} {
|
{$LETTA_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy letta:8283
|
reverse_proxy letta:8283
|
||||||
}
|
}
|
||||||
|
|
||||||
# LightRAG (Graph-based RAG with Knowledge Extraction)
|
# LightRAG (Graph-based RAG with Knowledge Extraction)
|
||||||
{$LIGHTRAG_HOSTNAME} {
|
{$LIGHTRAG_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy lightrag:9621
|
reverse_proxy lightrag:9621
|
||||||
}
|
}
|
||||||
|
|
||||||
# Weaviate
|
# Weaviate
|
||||||
{$WEAVIATE_HOSTNAME} {
|
{$WEAVIATE_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy weaviate:8080
|
reverse_proxy weaviate:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
# Qdrant
|
# Qdrant
|
||||||
{$QDRANT_HOSTNAME} {
|
{$QDRANT_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy qdrant:6333
|
reverse_proxy qdrant:6333
|
||||||
}
|
}
|
||||||
|
|
||||||
# ComfyUI
|
# ComfyUI
|
||||||
{$COMFYUI_HOSTNAME} {
|
{$COMFYUI_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$COMFYUI_USERNAME} {$COMFYUI_PASSWORD_HASH}
|
{$COMFYUI_USERNAME} {$COMFYUI_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -124,6 +148,7 @@
|
|||||||
|
|
||||||
# LibreTranslate (Self-hosted Translation API)
|
# LibreTranslate (Self-hosted Translation API)
|
||||||
{$LT_HOSTNAME} {
|
{$LT_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$LT_USERNAME} {$LT_PASSWORD_HASH}
|
{$LT_USERNAME} {$LT_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -132,21 +157,25 @@
|
|||||||
|
|
||||||
# Neo4j
|
# Neo4j
|
||||||
{$NEO4J_HOSTNAME} {
|
{$NEO4J_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy neo4j:7474
|
reverse_proxy neo4j:7474
|
||||||
}
|
}
|
||||||
|
|
||||||
# Neo4j Bolt Protocol (wss)
|
# Neo4j Bolt Protocol (wss)
|
||||||
https://{$NEO4J_HOSTNAME}:7687 {
|
https://{$NEO4J_HOSTNAME}:7687 {
|
||||||
|
import service_tls
|
||||||
reverse_proxy neo4j:7687
|
reverse_proxy neo4j:7687
|
||||||
}
|
}
|
||||||
|
|
||||||
# NocoDB
|
# NocoDB
|
||||||
{$NOCODB_HOSTNAME} {
|
{$NOCODB_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
reverse_proxy nocodb:8080
|
reverse_proxy nocodb:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
# PaddleOCR (PaddleX Basic Serving)
|
# PaddleOCR (PaddleX Basic Serving)
|
||||||
{$PADDLEOCR_HOSTNAME} {
|
{$PADDLEOCR_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$PADDLEOCR_USERNAME} {$PADDLEOCR_PASSWORD_HASH}
|
{$PADDLEOCR_USERNAME} {$PADDLEOCR_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -155,6 +184,7 @@ https://{$NEO4J_HOSTNAME}:7687 {
|
|||||||
|
|
||||||
# Docling (Document Conversion API)
|
# Docling (Document Conversion API)
|
||||||
{$DOCLING_HOSTNAME} {
|
{$DOCLING_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$DOCLING_USERNAME} {$DOCLING_PASSWORD_HASH}
|
{$DOCLING_USERNAME} {$DOCLING_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -174,6 +204,7 @@ http://{$WELCOME_HOSTNAME} {
|
|||||||
|
|
||||||
# HTTPS block for direct access
|
# HTTPS block for direct access
|
||||||
{$WELCOME_HOSTNAME} {
|
{$WELCOME_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
basic_auth {
|
basic_auth {
|
||||||
{$WELCOME_USERNAME} {$WELCOME_PASSWORD_HASH}
|
{$WELCOME_USERNAME} {$WELCOME_PASSWORD_HASH}
|
||||||
}
|
}
|
||||||
@@ -182,10 +213,12 @@ http://{$WELCOME_HOSTNAME} {
|
|||||||
try_files {path} /index.html
|
try_files {path} /index.html
|
||||||
}
|
}
|
||||||
|
|
||||||
import /etc/caddy/addons/*.conf
|
# Import custom site addons
|
||||||
|
import /etc/caddy/addons/site-*.conf
|
||||||
|
|
||||||
# # SearXNG
|
# SearXNG
|
||||||
{$SEARXNG_HOSTNAME} {
|
{$SEARXNG_HOSTNAME} {
|
||||||
|
import service_tls
|
||||||
@protected not remote_ip 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10
|
@protected not remote_ip 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10
|
||||||
|
|
||||||
basic_auth @protected {
|
basic_auth @protected {
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ This setup provides a comprehensive suite of cutting-edge services, all pre-conf
|
|||||||
|
|
||||||
The installer also makes the following powerful open-source tools **available for you to select and deploy** via an interactive wizard during setup:
|
The installer also makes the following powerful open-source tools **available for you to select and deploy** via an interactive wizard during setup:
|
||||||
|
|
||||||
|
✅ [**Appsmith**](https://www.appsmith.com/) - An open-source low-code platform for building internal tools, dashboards, and admin panels with a drag-and-drop UI builder.
|
||||||
|
|
||||||
✅ [**n8n**](https://n8n.io/) - A low-code platform with over 400 integrations and advanced AI components to automate workflows.
|
✅ [**n8n**](https://n8n.io/) - A low-code platform with over 400 integrations and advanced AI components to automate workflows.
|
||||||
|
|
||||||
✅ [**ComfyUI**](https://github.com/comfyanonymous/ComfyUI) - A powerful, node-based UI for Stable Diffusion workflows. Build and run image-generation pipelines visually, with support for custom nodes and extensions.
|
✅ [**ComfyUI**](https://github.com/comfyanonymous/ComfyUI) - A powerful, node-based UI for Stable Diffusion workflows. Build and run image-generation pipelines visually, with support for custom nodes and extensions.
|
||||||
@@ -179,6 +181,7 @@ After successful installation, your services are up and running! Here's how to g
|
|||||||
The installation script provided a summary report with all access URLs and credentials. Please refer to that report. The main services will be available at the following addresses (replace `yourdomain.com` with your actual domain):
|
The installation script provided a summary report with all access URLs and credentials. Please refer to that report. The main services will be available at the following addresses (replace `yourdomain.com` with your actual domain):
|
||||||
|
|
||||||
- **n8n:** `n8n.yourdomain.com` (Log in with the email address you provided during installation and the initial password from the summary report. You may be prompted to change this password on first login.)
|
- **n8n:** `n8n.yourdomain.com` (Log in with the email address you provided during installation and the initial password from the summary report. You may be prompted to change this password on first login.)
|
||||||
|
- **Appsmith:** `appsmith.yourdomain.com` (Low-code app builder)
|
||||||
- **ComfyUI:** `comfyui.yourdomain.com` (Node-based Stable Diffusion UI)
|
- **ComfyUI:** `comfyui.yourdomain.com` (Node-based Stable Diffusion UI)
|
||||||
- **Databasus:** `databasus.yourdomain.com`
|
- **Databasus:** `databasus.yourdomain.com`
|
||||||
- **Dify:** `dify.yourdomain.com` (AI application development platform with comprehensive LLMOps capabilities)
|
- **Dify:** `dify.yourdomain.com` (AI application development platform with comprehensive LLMOps capabilities)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This directory allows you to extend or override Caddy configuration without modifying the main `Caddyfile`.
|
This directory allows you to extend or override Caddy configuration without modifying the main `Caddyfile`.
|
||||||
|
|
||||||
All `.conf` files in this directory are automatically imported via `import /etc/caddy/addons/*.conf` at the end of the main Caddyfile.
|
Files matching `site-*.conf` in this directory are automatically imported via `import /etc/caddy/addons/site-*.conf` in the main Caddyfile.
|
||||||
|
|
||||||
## Use Cases
|
## Use Cases
|
||||||
|
|
||||||
@@ -15,6 +15,23 @@ All `.conf` files in this directory are automatically imported via `import /etc/
|
|||||||
|
|
||||||
For corporate/internal deployments where Let's Encrypt is not available, you can use your own certificates.
|
For corporate/internal deployments where Let's Encrypt is not available, you can use your own certificates.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
The main `Caddyfile` imports a TLS snippet that all service blocks use:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
# In Caddyfile (top)
|
||||||
|
import /etc/caddy/addons/tls-snippet.conf
|
||||||
|
|
||||||
|
# In each service block
|
||||||
|
{$N8N_HOSTNAME} {
|
||||||
|
import service_tls # <-- Uses the snippet
|
||||||
|
reverse_proxy n8n:5678
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the snippet is empty (Let's Encrypt is used). When you run `make setup-tls`, the snippet is updated with your certificate paths.
|
||||||
|
|
||||||
### Quick Setup
|
### Quick Setup
|
||||||
|
|
||||||
1. Place your certificates in the `certs/` directory:
|
1. Place your certificates in the `certs/` directory:
|
||||||
@@ -28,42 +45,22 @@ For corporate/internal deployments where Let's Encrypt is not available, you can
|
|||||||
make setup-tls
|
make setup-tls
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Restart Caddy:
|
3. The script will:
|
||||||
```bash
|
- Update `caddy-addon/tls-snippet.conf` with your certificate paths
|
||||||
docker compose -p localai restart caddy
|
- Optionally restart Caddy to apply changes
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Setup
|
### Reset to Let's Encrypt
|
||||||
|
|
||||||
1. Copy the example file:
|
To switch back to automatic Let's Encrypt certificates:
|
||||||
```bash
|
|
||||||
cp caddy-addon/custom-tls.conf.example caddy-addon/custom-tls.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Edit `custom-tls.conf` with your hostnames and certificate paths
|
```bash
|
||||||
|
make setup-tls --remove
|
||||||
3. Place certificates in `certs/` directory
|
|
||||||
|
|
||||||
4. Restart Caddy:
|
|
||||||
```bash
|
|
||||||
docker compose -p localai restart caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
## How Site Override Works
|
|
||||||
|
|
||||||
When you define a site block in an addon file with the same hostname as the main Caddyfile, Caddy will use **both** configurations. To completely override a site, use the exact same hostname.
|
|
||||||
|
|
||||||
Example: To override `n8n.yourdomain.com` with a custom certificate:
|
|
||||||
|
|
||||||
```
|
|
||||||
# caddy-addon/custom-tls.conf
|
|
||||||
n8n.internal.company.com {
|
|
||||||
tls /etc/caddy/certs/wildcard.crt /etc/caddy/certs/wildcard.key
|
|
||||||
reverse_proxy n8n:5678
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure your `.env` file has `N8N_HOSTNAME=n8n.internal.company.com`.
|
Or run directly:
|
||||||
|
```bash
|
||||||
|
bash scripts/setup_custom_tls.sh --remove
|
||||||
|
```
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
@@ -71,8 +68,9 @@ Make sure your `.env` file has `N8N_HOSTNAME=n8n.internal.company.com`.
|
|||||||
caddy-addon/
|
caddy-addon/
|
||||||
├── .gitkeep # Keeps directory in git
|
├── .gitkeep # Keeps directory in git
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
├── custom-tls.conf.example # Example for custom certificates
|
├── tls-snippet.conf.example # Template for TLS snippet (tracked in git)
|
||||||
└── custom-tls.conf # Your custom config (gitignored)
|
├── tls-snippet.conf # Your TLS config (gitignored, auto-created)
|
||||||
|
└── site-*.conf # Your custom addons (gitignored, must start with "site-")
|
||||||
|
|
||||||
certs/
|
certs/
|
||||||
├── .gitkeep # Keeps directory in git
|
├── .gitkeep # Keeps directory in git
|
||||||
@@ -80,11 +78,26 @@ certs/
|
|||||||
└── wildcard.key # Your private key (gitignored)
|
└── wildcard.key # Your private key (gitignored)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Adding Custom Addons
|
||||||
|
|
||||||
|
You can create `site-*.conf` files for custom Caddy configurations. They will be automatically loaded by the main Caddyfile.
|
||||||
|
|
||||||
|
**Important:** Custom addon files MUST start with `site-` prefix to be loaded (e.g., `site-custom.conf`, `site-myapp.conf`).
|
||||||
|
|
||||||
|
Example: `caddy-addon/site-custom-headers.conf`
|
||||||
|
```caddy
|
||||||
|
# Add custom headers to all responses
|
||||||
|
(custom_headers) {
|
||||||
|
header X-Custom-Header "My Value"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
- Files in `caddy-addon/*.conf` are gitignored (preserved during updates)
|
- `tls-snippet.conf.example` is tracked in git (template with default Let's Encrypt behavior)
|
||||||
|
- `tls-snippet.conf` is gitignored and auto-created from template (preserved during updates)
|
||||||
|
- `site-*.conf` files are gitignored (preserved during updates)
|
||||||
- Files in `certs/` are gitignored (certificates are not committed)
|
- Files in `certs/` are gitignored (certificates are not committed)
|
||||||
- Example files (`*.example`) are tracked in git
|
|
||||||
- Caddy validates configuration on startup - check logs if it fails:
|
- Caddy validates configuration on startup - check logs if it fails:
|
||||||
```bash
|
```bash
|
||||||
docker compose -p localai logs caddy
|
docker compose -p localai logs caddy
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
# Custom TLS Configuration for Corporate/Internal Certificates
|
|
||||||
#
|
|
||||||
# This file provides examples for using your own TLS certificates instead of Let's Encrypt.
|
|
||||||
# Copy this file to custom-tls.conf and modify as needed.
|
|
||||||
#
|
|
||||||
# Prerequisites:
|
|
||||||
# 1. Place your certificate files in the ./certs/ directory
|
|
||||||
# 2. Update .env hostnames to match your internal domain
|
|
||||||
# 3. Restart Caddy: docker compose -p localai restart caddy
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Option 1: Reusable TLS snippet (recommended for wildcard certificates)
|
|
||||||
# =============================================================================
|
|
||||||
# Define once, import in each service block
|
|
||||||
|
|
||||||
(custom_tls) {
|
|
||||||
tls /etc/caddy/certs/wildcard.crt /etc/caddy/certs/wildcard.key
|
|
||||||
}
|
|
||||||
|
|
||||||
# Then for each service you want to override:
|
|
||||||
#
|
|
||||||
# n8n.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy n8n:5678
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# flowise.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy flowise:3001
|
|
||||||
# }
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Option 2: Individual service configuration
|
|
||||||
# =============================================================================
|
|
||||||
# Use when you have different certificates for different services
|
|
||||||
|
|
||||||
# n8n.internal.company.com {
|
|
||||||
# tls /etc/caddy/certs/n8n.crt /etc/caddy/certs/n8n.key
|
|
||||||
# reverse_proxy n8n:5678
|
|
||||||
# }
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Option 3: Internal CA with auto-reload
|
|
||||||
# =============================================================================
|
|
||||||
# Caddy can auto-reload certificates when they change
|
|
||||||
|
|
||||||
# n8n.internal.company.com {
|
|
||||||
# tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem {
|
|
||||||
# # Optional: specify CA certificate for client verification
|
|
||||||
# # client_auth {
|
|
||||||
# # mode require_and_verify
|
|
||||||
# # trusted_ca_cert_file /etc/caddy/certs/ca.pem
|
|
||||||
# # }
|
|
||||||
# }
|
|
||||||
# reverse_proxy n8n:5678
|
|
||||||
# }
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Full Example: All common services with wildcard certificate
|
|
||||||
# =============================================================================
|
|
||||||
# Uncomment and modify the hostnames to match your .env configuration
|
|
||||||
|
|
||||||
# # N8N
|
|
||||||
# n8n.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy n8n:5678
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Flowise
|
|
||||||
# flowise.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy flowise:3001
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Open WebUI
|
|
||||||
# webui.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy open-webui:8080
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Grafana
|
|
||||||
# grafana.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy grafana:3000
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Portainer
|
|
||||||
# portainer.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy portainer:9000
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Langfuse
|
|
||||||
# langfuse.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy langfuse-web:3000
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Supabase
|
|
||||||
# supabase.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# reverse_proxy kong:8000
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # Welcome Page (with basic auth preserved)
|
|
||||||
# welcome.internal.company.com {
|
|
||||||
# import custom_tls
|
|
||||||
# basic_auth {
|
|
||||||
# {$WELCOME_USERNAME} {$WELCOME_PASSWORD_HASH}
|
|
||||||
# }
|
|
||||||
# root * /srv/welcome
|
|
||||||
# file_server
|
|
||||||
# try_files {path} /index.html
|
|
||||||
# }
|
|
||||||
10
caddy-addon/tls-snippet.conf.example
Normal file
10
caddy-addon/tls-snippet.conf.example
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# TLS Configuration Snippet
|
||||||
|
# Imported by all service blocks in the main Caddyfile.
|
||||||
|
#
|
||||||
|
# Default: Empty (uses Let's Encrypt automatic certificates)
|
||||||
|
# Custom: Overwritten by 'make setup-tls' with your certificate paths
|
||||||
|
# Reset: Run 'make setup-tls --remove' to restore Let's Encrypt
|
||||||
|
|
||||||
|
(service_tls) {
|
||||||
|
# Default: Let's Encrypt automatic certificates (empty = no override)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
volumes:
|
volumes:
|
||||||
|
appsmith_data:
|
||||||
caddy-config:
|
caddy-config:
|
||||||
caddy-data:
|
caddy-data:
|
||||||
comfyui_data:
|
comfyui_data:
|
||||||
@@ -144,6 +145,26 @@ x-n8n-worker-runner: &service-n8n-worker-runner
|
|||||||
N8N_RUNNERS_TASK_BROKER_URI: http://127.0.0.1:5679
|
N8N_RUNNERS_TASK_BROKER_URI: http://127.0.0.1:5679
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
appsmith:
|
||||||
|
image: appsmith/appsmith-ce:release
|
||||||
|
container_name: appsmith
|
||||||
|
profiles: ["appsmith"]
|
||||||
|
restart: unless-stopped
|
||||||
|
logging: *default-logging
|
||||||
|
environment:
|
||||||
|
<<: *proxy-env
|
||||||
|
APPSMITH_ENCRYPTION_PASSWORD: ${APPSMITH_ENCRYPTION_PASSWORD}
|
||||||
|
APPSMITH_ENCRYPTION_SALT: ${APPSMITH_ENCRYPTION_SALT}
|
||||||
|
APPSMITH_DISABLE_TELEMETRY: "true"
|
||||||
|
volumes:
|
||||||
|
- appsmith_data:/appsmith-stacks
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= wget -qO- http://localhost/api/v1/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 120s
|
||||||
|
|
||||||
flowise:
|
flowise:
|
||||||
image: flowiseai/flowise
|
image: flowiseai/flowise
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -318,6 +339,7 @@ services:
|
|||||||
- caddy-data:/data:rw
|
- caddy-data:/data:rw
|
||||||
- caddy-config:/config:rw
|
- caddy-config:/config:rw
|
||||||
environment:
|
environment:
|
||||||
|
APPSMITH_HOSTNAME: ${APPSMITH_HOSTNAME}
|
||||||
COMFYUI_HOSTNAME: ${COMFYUI_HOSTNAME}
|
COMFYUI_HOSTNAME: ${COMFYUI_HOSTNAME}
|
||||||
COMFYUI_PASSWORD_HASH: ${COMFYUI_PASSWORD_HASH}
|
COMFYUI_PASSWORD_HASH: ${COMFYUI_PASSWORD_HASH}
|
||||||
COMFYUI_USERNAME: ${COMFYUI_USERNAME}
|
COMFYUI_USERNAME: ${COMFYUI_USERNAME}
|
||||||
@@ -836,7 +858,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
<<: *proxy-env
|
<<: *proxy-env
|
||||||
BACKEND_INTERNAL_URL: http://postiz:3000
|
BACKEND_INTERNAL_URL: http://localhost:3000
|
||||||
DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/${POSTIZ_DB_NAME:-postiz}?schema=postiz"
|
DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/${POSTIZ_DB_NAME:-postiz}?schema=postiz"
|
||||||
DISABLE_REGISTRATION: ${POSTIZ_DISABLE_REGISTRATION}
|
DISABLE_REGISTRATION: ${POSTIZ_DISABLE_REGISTRATION}
|
||||||
FRONTEND_URL: ${POSTIZ_HOSTNAME:+https://}${POSTIZ_HOSTNAME}
|
FRONTEND_URL: ${POSTIZ_HOSTNAME:+https://}${POSTIZ_HOSTNAME}
|
||||||
@@ -884,6 +906,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- postiz-config:/config/
|
- postiz-config:/config/
|
||||||
- postiz-uploads:/uploads/
|
- postiz-uploads:/uploads/
|
||||||
|
- ./postiz.env:/app/.env:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -908,7 +931,7 @@ services:
|
|||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
|
||||||
comfyui:
|
comfyui:
|
||||||
image: yanwk/comfyui-boot:cu124-slim
|
image: yanwk/comfyui-boot:cu128-slim
|
||||||
container_name: comfyui
|
container_name: comfyui
|
||||||
profiles: ["comfyui"]
|
profiles: ["comfyui"]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -1030,10 +1053,10 @@ services:
|
|||||||
REDIS_HOST: ragflow-redis
|
REDIS_HOST: ragflow-redis
|
||||||
REDIS_PASSWORD: ${RAGFLOW_REDIS_PASSWORD}
|
REDIS_PASSWORD: ${RAGFLOW_REDIS_PASSWORD}
|
||||||
REDIS_PORT: 6379
|
REDIS_PORT: 6379
|
||||||
SVR_HTTP_PORT: 80
|
SVR_HTTP_PORT: 9380
|
||||||
volumes:
|
volumes:
|
||||||
- ragflow_data:/ragflow
|
- ragflow_data:/ragflow
|
||||||
- ./ragflow/nginx.conf:/etc/nginx/sites-available/default:ro
|
- ./ragflow/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
ragflow-elasticsearch:
|
ragflow-elasticsearch:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
# Stage 1: Get static ffmpeg binaries (statically linked, works on Alpine/musl)
|
||||||
|
FROM mwader/static-ffmpeg:latest AS ffmpeg
|
||||||
|
|
||||||
|
# Stage 2: Build final n8n image with ffmpeg
|
||||||
FROM n8nio/n8n:stable
|
FROM n8nio/n8n:stable
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
# Install static ffmpeg binary from BtbN GitHub releases
|
# Copy static ffmpeg binaries from the ffmpeg stage
|
||||||
RUN wget -qO- --tries=3 --timeout=60 https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz | \
|
COPY --from=ffmpeg /ffmpeg /usr/local/bin/ffmpeg
|
||||||
tar -xJC /tmp && \
|
COPY --from=ffmpeg /ffprobe /usr/local/bin/ffprobe
|
||||||
mv /tmp/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg /tmp/ffmpeg-master-latest-linux64-gpl/bin/ffprobe /usr/local/bin/ && \
|
|
||||||
rm -rf /tmp/ffmpeg-*
|
|
||||||
USER node
|
USER node
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ USER_INPUT_VARS=(
|
|||||||
# Variables to generate: varName="type:length"
|
# Variables to generate: varName="type:length"
|
||||||
# Types: password (alphanum), secret (base64), hex, base64, alphanum
|
# Types: password (alphanum), secret (base64), hex, base64, alphanum
|
||||||
declare -A VARS_TO_GENERATE=(
|
declare -A VARS_TO_GENERATE=(
|
||||||
|
["APPSMITH_ENCRYPTION_PASSWORD"]="password:32"
|
||||||
|
["APPSMITH_ENCRYPTION_SALT"]="password:32"
|
||||||
["CLICKHOUSE_PASSWORD"]="password:32"
|
["CLICKHOUSE_PASSWORD"]="password:32"
|
||||||
["COMFYUI_PASSWORD"]="password:32" # Added ComfyUI basic auth password
|
["COMFYUI_PASSWORD"]="password:32" # Added ComfyUI basic auth password
|
||||||
["DASHBOARD_PASSWORD"]="password:32" # Supabase Dashboard
|
["DASHBOARD_PASSWORD"]="password:32" # Supabase Dashboard
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ current_profiles_for_matching=",$CURRENT_PROFILES_VALUE,"
|
|||||||
# --- Define available services and their descriptions ---
|
# --- Define available services and their descriptions ---
|
||||||
# Base service definitions (tag, description)
|
# Base service definitions (tag, description)
|
||||||
base_services_data=(
|
base_services_data=(
|
||||||
|
"appsmith" "Appsmith (Low-code Platform for Internal Tools & Dashboards)"
|
||||||
"cloudflare-tunnel" "Cloudflare Tunnel (Zero-Trust Secure Access)"
|
"cloudflare-tunnel" "Cloudflare Tunnel (Zero-Trust Secure Access)"
|
||||||
"comfyui" "ComfyUI (Node-based Stable Diffusion UI)"
|
"comfyui" "ComfyUI (Node-based Stable Diffusion UI)"
|
||||||
"crawl4ai" "Crawl4ai (Web Crawler for AI)"
|
"crawl4ai" "Crawl4ai (Web Crawler for AI)"
|
||||||
@@ -215,7 +216,7 @@ if [ $gost_selected -eq 1 ]; then
|
|||||||
EXISTING_UPSTREAM=$(read_env_var "GOST_UPSTREAM_PROXY")
|
EXISTING_UPSTREAM=$(read_env_var "GOST_UPSTREAM_PROXY")
|
||||||
|
|
||||||
GOST_UPSTREAM_INPUT=$(wt_input "Gost Upstream Proxy" \
|
GOST_UPSTREAM_INPUT=$(wt_input "Gost Upstream Proxy" \
|
||||||
"Enter your external proxy URL for geo-bypass.\n\nExamples:\n socks5://user:pass@proxy.com:1080\n http://user:pass@proxy.com:8080\n\nThis proxy should be located outside restricted regions." \
|
"Enter your external proxy URL for geo-bypass.\n\nExamples:\n socks5://user:pass@proxy.com:1080\n http://user:pass@proxy.com:8080\n\nIMPORTANT: For HTTP proxies use http://, NOT https://.\nThe protocol refers to proxy type, not connection security.\n\nThis proxy should be located outside restricted regions." \
|
||||||
"$EXISTING_UPSTREAM") || true
|
"$EXISTING_UPSTREAM") || true
|
||||||
|
|
||||||
if [ -n "$GOST_UPSTREAM_INPUT" ]; then
|
if [ -n "$GOST_UPSTREAM_INPUT" ]; then
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ init_paths
|
|||||||
# Ensure .env exists
|
# Ensure .env exists
|
||||||
ensure_file_exists "$ENV_FILE"
|
ensure_file_exists "$ENV_FILE"
|
||||||
|
|
||||||
|
# Load COMPOSE_PROFILES early so is_profile_active works for all sections
|
||||||
|
COMPOSE_PROFILES_VALUE="$(read_env_var COMPOSE_PROFILES)"
|
||||||
|
COMPOSE_PROFILES="$COMPOSE_PROFILES_VALUE"
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# Prompt for OpenAI API key (optional) using .env value as source of truth
|
# Prompt for OpenAI API key (optional) using .env value as source of truth
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
@@ -48,87 +52,89 @@ fi
|
|||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# Logic for n8n workflow import (RUN_N8N_IMPORT)
|
# Logic for n8n workflow import (RUN_N8N_IMPORT)
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
log_subheader "n8n Workflow Import"
|
if is_profile_active "n8n"; then
|
||||||
final_run_n8n_import_decision="false"
|
log_subheader "n8n Workflow Import"
|
||||||
require_whiptail
|
|
||||||
if wt_yesno "Import n8n Workflows" "Import ~300 ready-made n8n workflows now? This can take ~30 minutes." "no"; then
|
|
||||||
final_run_n8n_import_decision="true"
|
|
||||||
else
|
|
||||||
final_run_n8n_import_decision="false"
|
final_run_n8n_import_decision="false"
|
||||||
fi
|
require_whiptail
|
||||||
|
if wt_yesno "Import n8n Workflows" "Import ~300 ready-made n8n workflows now? This can take ~30 minutes." "no"; then
|
||||||
|
final_run_n8n_import_decision="true"
|
||||||
|
else
|
||||||
|
final_run_n8n_import_decision="false"
|
||||||
|
fi
|
||||||
|
|
||||||
# Persist RUN_N8N_IMPORT to .env
|
# Persist RUN_N8N_IMPORT to .env
|
||||||
write_env_var "RUN_N8N_IMPORT" "$final_run_n8n_import_decision"
|
write_env_var "RUN_N8N_IMPORT" "$final_run_n8n_import_decision"
|
||||||
|
else
|
||||||
|
write_env_var "RUN_N8N_IMPORT" "false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# Prompt for number of n8n workers
|
# Prompt for number of n8n workers
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
log_subheader "n8n Worker Configuration"
|
if is_profile_active "n8n"; then
|
||||||
EXISTING_N8N_WORKER_COUNT="$(read_env_var N8N_WORKER_COUNT)"
|
log_subheader "n8n Worker Configuration"
|
||||||
require_whiptail
|
EXISTING_N8N_WORKER_COUNT="$(read_env_var N8N_WORKER_COUNT)"
|
||||||
if [[ -n "$EXISTING_N8N_WORKER_COUNT" ]]; then
|
require_whiptail
|
||||||
N8N_WORKER_COUNT_CURRENT="$EXISTING_N8N_WORKER_COUNT"
|
if [[ -n "$EXISTING_N8N_WORKER_COUNT" ]]; then
|
||||||
N8N_WORKER_COUNT_INPUT_RAW=$(wt_input "n8n Workers (instances)" "Enter new number of n8n workers, or leave as current ($N8N_WORKER_COUNT_CURRENT)." "") || true
|
N8N_WORKER_COUNT_CURRENT="$EXISTING_N8N_WORKER_COUNT"
|
||||||
if [[ -z "$N8N_WORKER_COUNT_INPUT_RAW" ]]; then
|
N8N_WORKER_COUNT_INPUT_RAW=$(wt_input "n8n Workers (instances)" "Enter new number of n8n workers, or leave as current ($N8N_WORKER_COUNT_CURRENT)." "") || true
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
if [[ -z "$N8N_WORKER_COUNT_INPUT_RAW" ]]; then
|
||||||
else
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
||||||
if [[ "$N8N_WORKER_COUNT_INPUT_RAW" =~ ^0*[1-9][0-9]*$ ]]; then
|
else
|
||||||
N8N_WORKER_COUNT_TEMP="$((10#$N8N_WORKER_COUNT_INPUT_RAW))"
|
if [[ "$N8N_WORKER_COUNT_INPUT_RAW" =~ ^0*[1-9][0-9]*$ ]]; then
|
||||||
if [[ "$N8N_WORKER_COUNT_TEMP" -ge 1 ]]; then
|
N8N_WORKER_COUNT_TEMP="$((10#$N8N_WORKER_COUNT_INPUT_RAW))"
|
||||||
if wt_yesno "Confirm Workers" "Update n8n workers to $N8N_WORKER_COUNT_TEMP?" "yes"; then
|
if [[ "$N8N_WORKER_COUNT_TEMP" -ge 1 ]]; then
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_TEMP"
|
if wt_yesno "Confirm Workers" "Update n8n workers to $N8N_WORKER_COUNT_TEMP?" "yes"; then
|
||||||
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_TEMP"
|
||||||
|
else
|
||||||
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
||||||
|
log_info "Change declined. Keeping N8N_WORKER_COUNT at $N8N_WORKER_COUNT."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
|
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Number must be positive. Keeping $N8N_WORKER_COUNT_CURRENT."
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
||||||
log_info "Change declined. Keeping N8N_WORKER_COUNT at $N8N_WORKER_COUNT."
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Number must be positive. Keeping $N8N_WORKER_COUNT_CURRENT."
|
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Please enter a positive integer. Keeping $N8N_WORKER_COUNT_CURRENT."
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Please enter a positive integer. Keeping $N8N_WORKER_COUNT_CURRENT."
|
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
|
||||||
fi
|
fi
|
||||||
fi
|
else
|
||||||
else
|
while true; do
|
||||||
while true; do
|
N8N_WORKER_COUNT_INPUT_RAW=$(wt_input "n8n Workers" "Enter number of n8n workers to run (default 1)." "1") || true
|
||||||
N8N_WORKER_COUNT_INPUT_RAW=$(wt_input "n8n Workers" "Enter number of n8n workers to run (default 1)." "1") || true
|
N8N_WORKER_COUNT_CANDIDATE="${N8N_WORKER_COUNT_INPUT_RAW:-1}"
|
||||||
N8N_WORKER_COUNT_CANDIDATE="${N8N_WORKER_COUNT_INPUT_RAW:-1}"
|
if [[ "$N8N_WORKER_COUNT_CANDIDATE" =~ ^0*[1-9][0-9]*$ ]]; then
|
||||||
if [[ "$N8N_WORKER_COUNT_CANDIDATE" =~ ^0*[1-9][0-9]*$ ]]; then
|
N8N_WORKER_COUNT_VALIDATED="$((10#$N8N_WORKER_COUNT_CANDIDATE))"
|
||||||
N8N_WORKER_COUNT_VALIDATED="$((10#$N8N_WORKER_COUNT_CANDIDATE))"
|
if [[ "$N8N_WORKER_COUNT_VALIDATED" -ge 1 ]]; then
|
||||||
if [[ "$N8N_WORKER_COUNT_VALIDATED" -ge 1 ]]; then
|
if wt_yesno "Confirm Workers" "Run $N8N_WORKER_COUNT_VALIDATED n8n worker(s)?" "yes"; then
|
||||||
if wt_yesno "Confirm Workers" "Run $N8N_WORKER_COUNT_VALIDATED n8n worker(s)?" "yes"; then
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_VALIDATED"
|
||||||
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_VALIDATED"
|
break
|
||||||
break
|
fi
|
||||||
|
else
|
||||||
|
log_error "Number of workers must be a positive integer."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Number of workers must be a positive integer."
|
log_error "Invalid input '$N8N_WORKER_COUNT_CANDIDATE'. Please enter a positive integer (e.g., 1, 2)."
|
||||||
fi
|
fi
|
||||||
else
|
done
|
||||||
log_error "Invalid input '$N8N_WORKER_COUNT_CANDIDATE'. Please enter a positive integer (e.g., 1, 2)."
|
fi
|
||||||
fi
|
# Ensure N8N_WORKER_COUNT is definitely set (should be by logic above)
|
||||||
done
|
N8N_WORKER_COUNT="${N8N_WORKER_COUNT:-1}"
|
||||||
|
|
||||||
|
# Persist N8N_WORKER_COUNT to .env
|
||||||
|
write_env_var "N8N_WORKER_COUNT" "$N8N_WORKER_COUNT"
|
||||||
|
|
||||||
|
# Generate worker-runner pairs configuration
|
||||||
|
# Each worker gets its own dedicated task runner sidecar
|
||||||
|
log_info "Generating n8n worker-runner pairs configuration..."
|
||||||
|
bash "$SCRIPT_DIR/generate_n8n_workers.sh"
|
||||||
fi
|
fi
|
||||||
# Ensure N8N_WORKER_COUNT is definitely set (should be by logic above)
|
|
||||||
N8N_WORKER_COUNT="${N8N_WORKER_COUNT:-1}"
|
|
||||||
|
|
||||||
# Persist N8N_WORKER_COUNT to .env
|
|
||||||
write_env_var "N8N_WORKER_COUNT" "$N8N_WORKER_COUNT"
|
|
||||||
|
|
||||||
# Generate worker-runner pairs configuration
|
|
||||||
# Each worker gets its own dedicated task runner sidecar
|
|
||||||
log_info "Generating n8n worker-runner pairs configuration..."
|
|
||||||
bash "$SCRIPT_DIR/generate_n8n_workers.sh"
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# Cloudflare Tunnel Token (if cloudflare-tunnel profile is active)
|
# Cloudflare Tunnel Token (if cloudflare-tunnel profile is active)
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
COMPOSE_PROFILES_VALUE="$(read_env_var COMPOSE_PROFILES)"
|
|
||||||
# Set COMPOSE_PROFILES for is_profile_active to work
|
|
||||||
COMPOSE_PROFILES="$COMPOSE_PROFILES_VALUE"
|
|
||||||
|
|
||||||
if is_profile_active "cloudflare-tunnel"; then
|
if is_profile_active "cloudflare-tunnel"; then
|
||||||
log_subheader "Cloudflare Tunnel"
|
log_subheader "Cloudflare Tunnel"
|
||||||
existing_cf_token="$(read_env_var CLOUDFLARE_TUNNEL_TOKEN)"
|
existing_cf_token="$(read_env_var CLOUDFLARE_TUNNEL_TOKEN)"
|
||||||
|
|||||||
@@ -32,6 +32,23 @@ require_file "$PROJECT_ROOT/docker-compose.yml" "docker-compose.yml file not fou
|
|||||||
require_file "$PROJECT_ROOT/Caddyfile" "Caddyfile not found in project root. Reverse proxy might not work."
|
require_file "$PROJECT_ROOT/Caddyfile" "Caddyfile not found in project root. Reverse proxy might not work."
|
||||||
require_file "$PROJECT_ROOT/start_services.py" "start_services.py file not found in project root."
|
require_file "$PROJECT_ROOT/start_services.py" "start_services.py file not found in project root."
|
||||||
|
|
||||||
|
# Remove legacy custom-tls.conf that causes duplicate host errors
|
||||||
|
# This is needed for users upgrading from older versions
|
||||||
|
# TODO: Remove this cleanup block after v3.0 release (all users migrated)
|
||||||
|
OLD_TLS_CONFIG="$PROJECT_ROOT/caddy-addon/custom-tls.conf"
|
||||||
|
if [[ -f "$OLD_TLS_CONFIG" ]]; then
|
||||||
|
log_warning "Removing obsolete custom-tls.conf (causes duplicate host errors)"
|
||||||
|
rm -f "$OLD_TLS_CONFIG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure TLS snippet exists (auto-create from template if missing)
|
||||||
|
TLS_SNIPPET="$PROJECT_ROOT/caddy-addon/tls-snippet.conf"
|
||||||
|
TLS_TEMPLATE="$PROJECT_ROOT/caddy-addon/tls-snippet.conf.example"
|
||||||
|
if [[ ! -f "$TLS_SNIPPET" ]] && [[ -f "$TLS_TEMPLATE" ]]; then
|
||||||
|
cp "$TLS_TEMPLATE" "$TLS_SNIPPET"
|
||||||
|
log_info "Created tls-snippet.conf from template (Let's Encrypt mode)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if Docker daemon is running
|
# Check if Docker daemon is running
|
||||||
if ! docker info > /dev/null 2>&1; then
|
if ! docker info > /dev/null 2>&1; then
|
||||||
log_error "Docker daemon is not running. Please start Docker and try again."
|
log_error "Docker daemon is not running. Please start Docker and try again."
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ echo ""
|
|||||||
echo -e " ${WHITE}2.${NC} Store the Welcome Page credentials securely"
|
echo -e " ${WHITE}2.${NC} Store the Welcome Page credentials securely"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${WHITE}3.${NC} Configure services as needed:"
|
echo -e " ${WHITE}3.${NC} Configure services as needed:"
|
||||||
|
if is_profile_active "appsmith"; then
|
||||||
|
echo -e " ${GREEN}*${NC} ${WHITE}Appsmith${NC}: Create admin account on first login (may take a few minutes to start)"
|
||||||
|
fi
|
||||||
if is_profile_active "n8n"; then
|
if is_profile_active "n8n"; then
|
||||||
echo -e " ${GREEN}*${NC} ${WHITE}n8n${NC}: Complete first-run setup with your email"
|
echo -e " ${GREEN}*${NC} ${WHITE}n8n${NC}: Complete first-run setup with your email"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -27,6 +27,19 @@ GENERATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|||||||
# Build services array - each entry is a formatted JSON block
|
# Build services array - each entry is a formatted JSON block
|
||||||
declare -a SERVICES_ARRAY
|
declare -a SERVICES_ARRAY
|
||||||
|
|
||||||
|
# Appsmith
|
||||||
|
if is_profile_active "appsmith"; then
|
||||||
|
SERVICES_ARRAY+=(" \"appsmith\": {
|
||||||
|
\"hostname\": \"$(json_escape "$APPSMITH_HOSTNAME")\",
|
||||||
|
\"credentials\": {
|
||||||
|
\"note\": \"Create your account on first login\"
|
||||||
|
},
|
||||||
|
\"extra\": {
|
||||||
|
\"docs\": \"https://docs.appsmith.com\"
|
||||||
|
}
|
||||||
|
}")
|
||||||
|
fi
|
||||||
|
|
||||||
# n8n
|
# n8n
|
||||||
if is_profile_active "n8n"; then
|
if is_profile_active "n8n"; then
|
||||||
N8N_WORKER_COUNT_VAL="${N8N_WORKER_COUNT:-1}"
|
N8N_WORKER_COUNT_VAL="${N8N_WORKER_COUNT:-1}"
|
||||||
@@ -519,6 +532,16 @@ if is_profile_active "databasus"; then
|
|||||||
((STEP_NUM++))
|
((STEP_NUM++))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Set up Appsmith (if appsmith active)
|
||||||
|
if is_profile_active "appsmith"; then
|
||||||
|
QUICK_START_ARRAY+=(" {
|
||||||
|
\"step\": $STEP_NUM,
|
||||||
|
\"title\": \"Set up Appsmith\",
|
||||||
|
\"description\": \"Create your admin account and build your first app\"
|
||||||
|
}")
|
||||||
|
((STEP_NUM++))
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 4: Monitor system (if monitoring active)
|
# Step 4: Monitor system (if monitoring active)
|
||||||
if is_profile_active "monitoring"; then
|
if is_profile_active "monitoring"; then
|
||||||
QUICK_START_ARRAY+=(" {
|
QUICK_START_ARRAY+=(" {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# - docker-compose.n8n-workers.yml (if exists and n8n profile active)
|
# - docker-compose.n8n-workers.yml (if exists and n8n profile active)
|
||||||
# - supabase/docker/docker-compose.yml (if exists and supabase profile active)
|
# - supabase/docker/docker-compose.yml (if exists and supabase profile active)
|
||||||
# - dify/docker/docker-compose.yaml (if exists and dify profile active)
|
# - dify/docker/docker-compose.yaml (if exists and dify profile active)
|
||||||
|
# - docker-compose.override.yml (if exists, user overrides with highest precedence)
|
||||||
#
|
#
|
||||||
# Usage: bash scripts/restart.sh
|
# Usage: bash scripts/restart.sh
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -33,6 +34,20 @@ EXTERNAL_SERVICE_INIT_DELAY=10
|
|||||||
# Build compose files array (sets global COMPOSE_FILES)
|
# Build compose files array (sets global COMPOSE_FILES)
|
||||||
build_compose_files_array
|
build_compose_files_array
|
||||||
|
|
||||||
|
# Ensure postiz.env exists if Postiz is enabled (required for volume mount)
|
||||||
|
# This is a safety net for cases where restart runs without start_services.py
|
||||||
|
# (e.g., git pull + make restart instead of make update)
|
||||||
|
if is_profile_active "postiz"; then
|
||||||
|
if [ -d "$PROJECT_ROOT/postiz.env" ]; then
|
||||||
|
log_warning "postiz.env exists as a directory (created by Docker). Removing and recreating as file."
|
||||||
|
rm -rf "$PROJECT_ROOT/postiz.env"
|
||||||
|
touch "$PROJECT_ROOT/postiz.env"
|
||||||
|
elif [ ! -f "$PROJECT_ROOT/postiz.env" ]; then
|
||||||
|
log_warning "postiz.env not found, creating empty file. Run 'make update' to generate full config."
|
||||||
|
touch "$PROJECT_ROOT/postiz.env"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Restarting services..."
|
log_info "Restarting services..."
|
||||||
log_info "Using compose files: ${COMPOSE_FILES[*]}"
|
log_info "Using compose files: ${COMPOSE_FILES[*]}"
|
||||||
|
|
||||||
@@ -71,6 +86,10 @@ MAIN_COMPOSE_FILES=("-f" "$PROJECT_ROOT/docker-compose.yml")
|
|||||||
if path=$(get_n8n_workers_compose); then
|
if path=$(get_n8n_workers_compose); then
|
||||||
MAIN_COMPOSE_FILES+=("-f" "$path")
|
MAIN_COMPOSE_FILES+=("-f" "$path")
|
||||||
fi
|
fi
|
||||||
|
OVERRIDE_COMPOSE="$PROJECT_ROOT/docker-compose.override.yml"
|
||||||
|
if [ -f "$OVERRIDE_COMPOSE" ]; then
|
||||||
|
MAIN_COMPOSE_FILES+=("-f" "$OVERRIDE_COMPOSE")
|
||||||
|
fi
|
||||||
|
|
||||||
# Start main services
|
# Start main services
|
||||||
log_info "Starting main services..."
|
log_info "Starting main services..."
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# setup_custom_tls.sh - Configure custom TLS certificates for Caddy
|
# setup_custom_tls.sh - Configure custom TLS certificates for Caddy
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Generates caddy-addon/custom-tls.conf for using corporate/internal certificates
|
# Updates caddy-addon/tls-snippet.conf to use corporate/internal certificates
|
||||||
# instead of Let's Encrypt.
|
# instead of Let's Encrypt.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# bash scripts/setup_custom_tls.sh # Interactive mode
|
# bash scripts/setup_custom_tls.sh # Interactive mode
|
||||||
# bash scripts/setup_custom_tls.sh cert.crt key.key # Non-interactive mode
|
# bash scripts/setup_custom_tls.sh cert.crt key.key # Non-interactive mode
|
||||||
|
# bash scripts/setup_custom_tls.sh --remove # Reset to Let's Encrypt
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
# - Place certificate files in ./certs/ directory
|
# - Place certificate files in ./certs/ directory
|
||||||
@@ -18,13 +19,27 @@ set -euo pipefail
|
|||||||
|
|
||||||
source "$(dirname "$0")/utils.sh" && init_paths
|
source "$(dirname "$0")/utils.sh" && init_paths
|
||||||
|
|
||||||
ADDON_FILE="$PROJECT_ROOT/caddy-addon/custom-tls.conf"
|
SNIPPET_FILE="$PROJECT_ROOT/caddy-addon/tls-snippet.conf"
|
||||||
|
SNIPPET_EXAMPLE="$PROJECT_ROOT/caddy-addon/tls-snippet.conf.example"
|
||||||
CERTS_DIR="$PROJECT_ROOT/certs"
|
CERTS_DIR="$PROJECT_ROOT/certs"
|
||||||
|
|
||||||
|
# Legacy file that causes duplicate host errors (must be cleaned up on migration)
|
||||||
|
# TODO: Remove OLD_CONFIG and cleanup_legacy_config() after v3.0 release (all users migrated)
|
||||||
|
OLD_CONFIG="$PROJECT_ROOT/caddy-addon/custom-tls.conf"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# FUNCTIONS
|
# FUNCTIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
cleanup_legacy_config() {
|
||||||
|
# Remove old custom-tls.conf that causes duplicate host errors
|
||||||
|
# This is needed for users upgrading from older versions
|
||||||
|
if [[ -f "$OLD_CONFIG" ]]; then
|
||||||
|
log_warning "Removing obsolete custom-tls.conf (causes duplicate host errors)"
|
||||||
|
rm -f "$OLD_CONFIG"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
Setup Custom TLS Certificates for Caddy
|
Setup Custom TLS Certificates for Caddy
|
||||||
@@ -33,7 +48,7 @@ Usage: $(basename "$0") [OPTIONS] [CERT_FILE] [KEY_FILE]
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
--remove Remove custom TLS configuration
|
--remove Reset to Let's Encrypt automatic certificates
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
CERT_FILE Path to certificate file (relative to ./certs/)
|
CERT_FILE Path to certificate file (relative to ./certs/)
|
||||||
@@ -42,13 +57,12 @@ Arguments:
|
|||||||
Examples:
|
Examples:
|
||||||
$(basename "$0") # Interactive mode
|
$(basename "$0") # Interactive mode
|
||||||
$(basename "$0") wildcard.crt wildcard.key # Use specific files
|
$(basename "$0") wildcard.crt wildcard.key # Use specific files
|
||||||
$(basename "$0") --remove # Remove custom TLS config
|
$(basename "$0") --remove # Reset to Let's Encrypt
|
||||||
|
|
||||||
The script will:
|
The script will:
|
||||||
1. Detect certificate files in ./certs/
|
1. Detect certificate files in ./certs/
|
||||||
2. Read active services from .env
|
2. Update caddy-addon/tls-snippet.conf with your certificate paths
|
||||||
3. Generate caddy-addon/custom-tls.conf
|
3. Optionally restart Caddy
|
||||||
4. Optionally restart Caddy
|
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -75,157 +89,53 @@ find_keys() {
|
|||||||
echo "${keys[*]:-}"
|
echo "${keys[*]:-}"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_active_services() {
|
ensure_snippet_exists() {
|
||||||
# Get list of services with their hostnames from .env
|
# Create tls-snippet.conf from example if it doesn't exist
|
||||||
load_env
|
# This ensures the file survives git updates (it's gitignored)
|
||||||
local services=()
|
if [[ ! -f "$SNIPPET_FILE" ]]; then
|
||||||
|
if [[ -f "$SNIPPET_EXAMPLE" ]]; then
|
||||||
# Map of service names to their hostname variables
|
cp "$SNIPPET_EXAMPLE" "$SNIPPET_FILE"
|
||||||
declare -A service_map=(
|
log_info "Created tls-snippet.conf from template"
|
||||||
["n8n"]="N8N_HOSTNAME"
|
else
|
||||||
["flowise"]="FLOWISE_HOSTNAME"
|
# Fallback: create default content directly
|
||||||
["webui"]="WEBUI_HOSTNAME"
|
remove_config
|
||||||
["grafana"]="GRAFANA_HOSTNAME"
|
|
||||||
["prometheus"]="PROMETHEUS_HOSTNAME"
|
|
||||||
["portainer"]="PORTAINER_HOSTNAME"
|
|
||||||
["langfuse"]="LANGFUSE_HOSTNAME"
|
|
||||||
["supabase"]="SUPABASE_HOSTNAME"
|
|
||||||
["dify"]="DIFY_HOSTNAME"
|
|
||||||
["nocodb"]="NOCODB_HOSTNAME"
|
|
||||||
["ragapp"]="RAGAPP_HOSTNAME"
|
|
||||||
["ragflow"]="RAGFLOW_HOSTNAME"
|
|
||||||
["waha"]="WAHA_HOSTNAME"
|
|
||||||
["searxng"]="SEARXNG_HOSTNAME"
|
|
||||||
["comfyui"]="COMFYUI_HOSTNAME"
|
|
||||||
["welcome"]="WELCOME_HOSTNAME"
|
|
||||||
["databasus"]="DATABASUS_HOSTNAME"
|
|
||||||
["letta"]="LETTA_HOSTNAME"
|
|
||||||
["lightrag"]="LIGHTRAG_HOSTNAME"
|
|
||||||
["weaviate"]="WEAVIATE_HOSTNAME"
|
|
||||||
["qdrant"]="QDRANT_HOSTNAME"
|
|
||||||
["neo4j"]="NEO4J_HOSTNAME"
|
|
||||||
["postiz"]="POSTIZ_HOSTNAME"
|
|
||||||
["libretranslate"]="LT_HOSTNAME"
|
|
||||||
["paddleocr"]="PADDLEOCR_HOSTNAME"
|
|
||||||
["docling"]="DOCLING_HOSTNAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
for service in "${!service_map[@]}"; do
|
|
||||||
local hostname_var="${service_map[$service]}"
|
|
||||||
local hostname="${!hostname_var:-}"
|
|
||||||
if [[ -n "$hostname" && "$hostname" != *"yourdomain.com" ]]; then
|
|
||||||
services+=("$service:$hostname")
|
|
||||||
fi
|
fi
|
||||||
done
|
fi
|
||||||
|
|
||||||
echo "${services[*]:-}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_config() {
|
generate_config() {
|
||||||
local cert_file="$1"
|
local cert_file="$1"
|
||||||
local key_file="$2"
|
local key_file="$2"
|
||||||
local services=("${@:3}")
|
|
||||||
|
|
||||||
cat > "$ADDON_FILE" << 'HEADER'
|
cat > "$SNIPPET_FILE" << EOF
|
||||||
# Custom TLS Configuration
|
# TLS Configuration Snippet
|
||||||
# Generated by setup_custom_tls.sh
|
# Generated by setup_custom_tls.sh on $(date -Iseconds)
|
||||||
#
|
# Using custom certificates instead of Let's Encrypt.
|
||||||
# This file overrides default Let's Encrypt certificates with custom ones.
|
# Reset to Let's Encrypt: make setup-tls --remove
|
||||||
# Regenerate with: make setup-tls
|
|
||||||
|
|
||||||
# Reusable TLS snippet
|
(service_tls) {
|
||||||
(custom_tls) {
|
tls /etc/caddy/certs/$cert_file /etc/caddy/certs/$key_file
|
||||||
HEADER
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
echo " tls /etc/caddy/certs/$cert_file /etc/caddy/certs/$key_file" >> "$ADDON_FILE"
|
log_success "Generated $SNIPPET_FILE"
|
||||||
echo "}" >> "$ADDON_FILE"
|
|
||||||
echo "" >> "$ADDON_FILE"
|
|
||||||
|
|
||||||
# Service-specific reverse proxy mappings
|
|
||||||
declare -A proxy_map=(
|
|
||||||
["n8n"]="n8n:5678"
|
|
||||||
["flowise"]="flowise:3001"
|
|
||||||
["webui"]="open-webui:8080"
|
|
||||||
["grafana"]="grafana:3000"
|
|
||||||
["prometheus"]="prometheus:9090"
|
|
||||||
["portainer"]="portainer:9000"
|
|
||||||
["langfuse"]="langfuse-web:3000"
|
|
||||||
["supabase"]="kong:8000"
|
|
||||||
["dify"]="nginx:80"
|
|
||||||
["nocodb"]="nocodb:8080"
|
|
||||||
["ragapp"]="ragapp:8000"
|
|
||||||
["ragflow"]="ragflow:80"
|
|
||||||
["waha"]="waha:3000"
|
|
||||||
["searxng"]="searxng:8080"
|
|
||||||
["comfyui"]="comfyui:8188"
|
|
||||||
["welcome"]="file_server"
|
|
||||||
["databasus"]="databasus:4005"
|
|
||||||
["letta"]="letta:8283"
|
|
||||||
["lightrag"]="lightrag:9621"
|
|
||||||
["weaviate"]="weaviate:8080"
|
|
||||||
["qdrant"]="qdrant:6333"
|
|
||||||
["neo4j"]="neo4j:7474"
|
|
||||||
["postiz"]="postiz:5000"
|
|
||||||
["libretranslate"]="libretranslate:5000"
|
|
||||||
["paddleocr"]="paddleocr:8080"
|
|
||||||
["docling"]="docling:5001"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Services that need basic auth (format: USERNAME_VAR:PASSWORD_HASH_VAR)
|
|
||||||
declare -A auth_services=(
|
|
||||||
["prometheus"]="PROMETHEUS_USERNAME:PROMETHEUS_PASSWORD_HASH"
|
|
||||||
["ragapp"]="RAGAPP_USERNAME:RAGAPP_PASSWORD_HASH"
|
|
||||||
["comfyui"]="COMFYUI_USERNAME:COMFYUI_PASSWORD_HASH"
|
|
||||||
["welcome"]="WELCOME_USERNAME:WELCOME_PASSWORD_HASH"
|
|
||||||
["libretranslate"]="LT_USERNAME:LT_PASSWORD_HASH"
|
|
||||||
["paddleocr"]="PADDLEOCR_USERNAME:PADDLEOCR_PASSWORD_HASH"
|
|
||||||
["docling"]="DOCLING_USERNAME:DOCLING_PASSWORD_HASH"
|
|
||||||
)
|
|
||||||
|
|
||||||
for service_entry in "${services[@]}"; do
|
|
||||||
local service="${service_entry%%:*}"
|
|
||||||
local hostname="${service_entry#*:}"
|
|
||||||
local proxy="${proxy_map[$service]:-}"
|
|
||||||
|
|
||||||
[[ -z "$proxy" ]] && continue
|
|
||||||
|
|
||||||
echo "# $service" >> "$ADDON_FILE"
|
|
||||||
echo "$hostname {" >> "$ADDON_FILE"
|
|
||||||
echo " import custom_tls" >> "$ADDON_FILE"
|
|
||||||
|
|
||||||
# Add basic auth if needed
|
|
||||||
if [[ -n "${auth_services[$service]:-}" ]]; then
|
|
||||||
local auth_config="${auth_services[$service]}"
|
|
||||||
local username_var="${auth_config%%:*}"
|
|
||||||
local password_hash_var="${auth_config#*:}"
|
|
||||||
echo " basic_auth {" >> "$ADDON_FILE"
|
|
||||||
echo " {\$${username_var}} {\$${password_hash_var}}" >> "$ADDON_FILE"
|
|
||||||
echo " }" >> "$ADDON_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add reverse proxy or file server
|
|
||||||
if [[ "$proxy" == "file_server" ]]; then
|
|
||||||
echo " root * /srv/welcome" >> "$ADDON_FILE"
|
|
||||||
echo " file_server" >> "$ADDON_FILE"
|
|
||||||
echo " try_files {path} /index.html" >> "$ADDON_FILE"
|
|
||||||
else
|
|
||||||
echo " reverse_proxy $proxy" >> "$ADDON_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "}" >> "$ADDON_FILE"
|
|
||||||
echo "" >> "$ADDON_FILE"
|
|
||||||
done
|
|
||||||
|
|
||||||
log_success "Generated $ADDON_FILE"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_config() {
|
remove_config() {
|
||||||
if [[ -f "$ADDON_FILE" ]]; then
|
cat > "$SNIPPET_FILE" << 'EOF'
|
||||||
rm -f "$ADDON_FILE"
|
# TLS Configuration Snippet
|
||||||
log_success "Removed custom TLS configuration"
|
# Imported by all service blocks in the main Caddyfile.
|
||||||
else
|
#
|
||||||
log_info "No custom TLS configuration found"
|
# Default: Empty (uses Let's Encrypt automatic certificates)
|
||||||
fi
|
# Custom: Overwritten by 'make setup-tls' with your certificate paths
|
||||||
|
# Reset: Run 'make setup-tls --remove' to restore Let's Encrypt
|
||||||
|
|
||||||
|
(service_tls) {
|
||||||
|
# Default: Let's Encrypt automatic certificates (empty = no override)
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_success "Reset to Let's Encrypt (automatic certificates)"
|
||||||
}
|
}
|
||||||
|
|
||||||
restart_caddy() {
|
restart_caddy() {
|
||||||
@@ -250,12 +160,19 @@ main() {
|
|||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
--remove)
|
--remove)
|
||||||
|
cleanup_legacy_config
|
||||||
remove_config
|
remove_config
|
||||||
restart_caddy
|
restart_caddy
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# Clean up legacy config that causes duplicate hosts
|
||||||
|
cleanup_legacy_config
|
||||||
|
|
||||||
|
# Ensure snippet file exists (survives git updates)
|
||||||
|
ensure_snippet_exists
|
||||||
|
|
||||||
# Ensure certs directory exists
|
# Ensure certs directory exists
|
||||||
mkdir -p "$CERTS_DIR"
|
mkdir -p "$CERTS_DIR"
|
||||||
|
|
||||||
@@ -319,29 +236,16 @@ main() {
|
|||||||
log_info "Using certificate: $cert_file"
|
log_info "Using certificate: $cert_file"
|
||||||
log_info "Using key: $key_file"
|
log_info "Using key: $key_file"
|
||||||
|
|
||||||
# Get active services
|
# Ensure certificate files are readable by Caddy container
|
||||||
local services_arr
|
# (Docker volume mounts preserve host permissions, Caddy may run as different UID)
|
||||||
IFS=' ' read -ra services_arr <<< "$(get_active_services)"
|
chmod 644 "$CERTS_DIR/$cert_file" "$CERTS_DIR/$key_file"
|
||||||
|
|
||||||
if [[ ${#services_arr[@]} -eq 0 ]]; then
|
|
||||||
log_warning "No services with configured hostnames found in .env"
|
|
||||||
log_info "Make sure to update *_HOSTNAME variables in .env with your domain"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Found ${#services_arr[@]} services with configured hostnames"
|
|
||||||
|
|
||||||
# Generate configuration
|
# Generate configuration
|
||||||
generate_config "$cert_file" "$key_file" "${services_arr[@]}"
|
generate_config "$cert_file" "$key_file"
|
||||||
|
|
||||||
# Show summary
|
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Configuration generated for the following services:"
|
log_info "Custom TLS configured successfully!"
|
||||||
for service_entry in "${services_arr[@]}"; do
|
log_info "All services will use: /etc/caddy/certs/$cert_file"
|
||||||
local service="${service_entry%%:*}"
|
|
||||||
local hostname="${service_entry#*:}"
|
|
||||||
echo " - $service: $hostname"
|
|
||||||
done
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Restart Caddy
|
# Restart Caddy
|
||||||
|
|||||||
@@ -134,6 +134,11 @@ if is_profile_active "databasus"; then
|
|||||||
check_image_update "databasus" "databasus/databasus:latest"
|
check_image_update "databasus" "databasus/databasus:latest"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if is_profile_active "appsmith"; then
|
||||||
|
log_subheader "Appsmith"
|
||||||
|
check_image_update "appsmith" "appsmith/appsmith-ce:release"
|
||||||
|
fi
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
log_divider
|
log_divider
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ get_dify_compose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Build array of all active compose files (main + external services)
|
# Build array of all active compose files (main + external services)
|
||||||
|
# Appends docker-compose.override.yml last if it exists (user overrides, highest precedence)
|
||||||
# IMPORTANT: Requires COMPOSE_PROFILES to be set before calling (via load_env)
|
# IMPORTANT: Requires COMPOSE_PROFILES to be set before calling (via load_env)
|
||||||
# Usage: build_compose_files_array; docker compose "${COMPOSE_FILES[@]}" up -d
|
# Usage: build_compose_files_array; docker compose "${COMPOSE_FILES[@]}" up -d
|
||||||
# Result is stored in global COMPOSE_FILES array
|
# Result is stored in global COMPOSE_FILES array
|
||||||
@@ -369,6 +370,12 @@ build_compose_files_array() {
|
|||||||
if path=$(get_dify_compose); then
|
if path=$(get_dify_compose); then
|
||||||
COMPOSE_FILES+=("-f" "$path")
|
COMPOSE_FILES+=("-f" "$path")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Include user overrides last (highest precedence)
|
||||||
|
local override="$PROJECT_ROOT/docker-compose.override.yml"
|
||||||
|
if [ -f "$override" ]; then
|
||||||
|
COMPOSE_FILES+=("-f" "$override")
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
#=============================================================================
|
#=============================================================================
|
||||||
|
|||||||
@@ -166,6 +166,76 @@ def prepare_dify_env():
|
|||||||
with open(env_path, 'w') as f:
|
with open(env_path, 'w') as f:
|
||||||
f.write("\n".join(lines) + "\n")
|
f.write("\n".join(lines) + "\n")
|
||||||
|
|
||||||
|
def is_postiz_enabled():
|
||||||
|
"""Check if 'postiz' is in COMPOSE_PROFILES in .env file."""
|
||||||
|
env_values = dotenv_values(".env")
|
||||||
|
compose_profiles = env_values.get("COMPOSE_PROFILES", "")
|
||||||
|
return "postiz" in compose_profiles.split(',')
|
||||||
|
|
||||||
|
def prepare_postiz_env():
|
||||||
|
"""Generate postiz.env for mounting as /app/.env in Postiz container.
|
||||||
|
|
||||||
|
The Postiz image uses dotenv-cli (dotenv -e ../../.env) to load config.
|
||||||
|
Always regenerated to reflect current .env values.
|
||||||
|
"""
|
||||||
|
if not is_postiz_enabled():
|
||||||
|
print("Postiz is not enabled, skipping env preparation.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Generating postiz.env from root .env values...")
|
||||||
|
root_env = dotenv_values(".env")
|
||||||
|
|
||||||
|
hostname = root_env.get("POSTIZ_HOSTNAME", "")
|
||||||
|
frontend_url = f"https://{hostname}" if hostname else ""
|
||||||
|
|
||||||
|
env_vars = {
|
||||||
|
"BACKEND_INTERNAL_URL": "http://localhost:3000",
|
||||||
|
"DATABASE_URL": f"postgresql://postgres:{root_env.get('POSTGRES_PASSWORD', '')}@postgres:5432/{root_env.get('POSTIZ_DB_NAME', 'postiz')}?schema=postiz",
|
||||||
|
"DISABLE_REGISTRATION": root_env.get("POSTIZ_DISABLE_REGISTRATION", "false"),
|
||||||
|
"FRONTEND_URL": frontend_url,
|
||||||
|
"IS_GENERAL": "true",
|
||||||
|
"JWT_SECRET": root_env.get("JWT_SECRET", ""),
|
||||||
|
"MAIN_URL": frontend_url,
|
||||||
|
"NEXT_PUBLIC_BACKEND_URL": f"{frontend_url}/api" if frontend_url else "",
|
||||||
|
"NEXT_PUBLIC_UPLOAD_DIRECTORY": "/uploads",
|
||||||
|
"REDIS_URL": "redis://redis:6379",
|
||||||
|
"STORAGE_PROVIDER": "local",
|
||||||
|
"TEMPORAL_ADDRESS": "temporal:7233",
|
||||||
|
"UPLOAD_DIRECTORY": "/uploads",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Social media API keys — direct pass-through from root .env
|
||||||
|
social_keys = [
|
||||||
|
"X_API_KEY", "X_API_SECRET",
|
||||||
|
"LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET",
|
||||||
|
"REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET",
|
||||||
|
"GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET",
|
||||||
|
"BEEHIIVE_API_KEY", "BEEHIIVE_PUBLICATION_ID",
|
||||||
|
"THREADS_APP_ID", "THREADS_APP_SECRET",
|
||||||
|
"FACEBOOK_APP_ID", "FACEBOOK_APP_SECRET",
|
||||||
|
"YOUTUBE_CLIENT_ID", "YOUTUBE_CLIENT_SECRET",
|
||||||
|
"TIKTOK_CLIENT_ID", "TIKTOK_CLIENT_SECRET",
|
||||||
|
"PINTEREST_CLIENT_ID", "PINTEREST_CLIENT_SECRET",
|
||||||
|
"DRIBBBLE_CLIENT_ID", "DRIBBBLE_CLIENT_SECRET",
|
||||||
|
"DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET",
|
||||||
|
"DISCORD_BOT_TOKEN_ID",
|
||||||
|
"SLACK_ID", "SLACK_SECRET", "SLACK_SIGNING_SECRET",
|
||||||
|
"MASTODON_URL", "MASTODON_CLIENT_ID", "MASTODON_CLIENT_SECRET",
|
||||||
|
]
|
||||||
|
for key in social_keys:
|
||||||
|
env_vars[key] = root_env.get(key, "")
|
||||||
|
|
||||||
|
# Handle case where Docker created postiz.env as a directory
|
||||||
|
if os.path.isdir("postiz.env"):
|
||||||
|
print("Warning: postiz.env exists as a directory (likely created by Docker). Removing...")
|
||||||
|
shutil.rmtree("postiz.env")
|
||||||
|
|
||||||
|
with open("postiz.env", 'w') as f:
|
||||||
|
for key, value in env_vars.items():
|
||||||
|
f.write(f'{key}="{value}"\n')
|
||||||
|
|
||||||
|
print(f"Generated postiz.env with {len(env_vars)} variables.")
|
||||||
|
|
||||||
def stop_existing_containers():
|
def stop_existing_containers():
|
||||||
"""Stop and remove existing containers for our unified project ('localai')."""
|
"""Stop and remove existing containers for our unified project ('localai')."""
|
||||||
print("Stopping and removing existing containers for the unified project 'localai'...")
|
print("Stopping and removing existing containers for the unified project 'localai'...")
|
||||||
@@ -195,6 +265,11 @@ def stop_existing_containers():
|
|||||||
if os.path.exists(n8n_workers_compose_path):
|
if os.path.exists(n8n_workers_compose_path):
|
||||||
cmd.extend(["-f", n8n_workers_compose_path])
|
cmd.extend(["-f", n8n_workers_compose_path])
|
||||||
|
|
||||||
|
# Include user overrides if present
|
||||||
|
override_path = "docker-compose.override.yml"
|
||||||
|
if os.path.exists(override_path):
|
||||||
|
cmd.extend(["-f", override_path])
|
||||||
|
|
||||||
cmd.extend(["down"])
|
cmd.extend(["down"])
|
||||||
run_command(cmd)
|
run_command(cmd)
|
||||||
|
|
||||||
@@ -230,6 +305,11 @@ def start_local_ai():
|
|||||||
if os.path.exists(n8n_workers_compose_path):
|
if os.path.exists(n8n_workers_compose_path):
|
||||||
compose_files.extend(["-f", n8n_workers_compose_path])
|
compose_files.extend(["-f", n8n_workers_compose_path])
|
||||||
|
|
||||||
|
# Include user overrides if present (must be last for highest precedence)
|
||||||
|
override_path = "docker-compose.override.yml"
|
||||||
|
if os.path.exists(override_path):
|
||||||
|
compose_files.extend(["-f", override_path])
|
||||||
|
|
||||||
# Explicitly build services and pull newer base images first.
|
# Explicitly build services and pull newer base images first.
|
||||||
print("Checking for newer base images and building services...")
|
print("Checking for newer base images and building services...")
|
||||||
build_cmd = ["docker", "compose", "-p", "localai"] + compose_files + ["build", "--pull"]
|
build_cmd = ["docker", "compose", "-p", "localai"] + compose_files + ["build", "--pull"]
|
||||||
@@ -394,7 +474,10 @@ def main():
|
|||||||
# Generate SearXNG secret key and check docker-compose.yml
|
# Generate SearXNG secret key and check docker-compose.yml
|
||||||
generate_searxng_secret_key()
|
generate_searxng_secret_key()
|
||||||
check_and_fix_docker_compose_for_searxng()
|
check_and_fix_docker_compose_for_searxng()
|
||||||
|
|
||||||
|
# Generate Postiz env file
|
||||||
|
prepare_postiz_env()
|
||||||
|
|
||||||
stop_existing_containers()
|
stop_existing_containers()
|
||||||
|
|
||||||
# Start Supabase first
|
# Start Supabase first
|
||||||
|
|||||||
@@ -148,6 +148,14 @@
|
|||||||
// DATA - Service metadata and commands
|
// DATA - Service metadata and commands
|
||||||
// ============================================
|
// ============================================
|
||||||
const SERVICE_METADATA = {
|
const SERVICE_METADATA = {
|
||||||
|
'appsmith': {
|
||||||
|
name: 'Appsmith',
|
||||||
|
description: 'Low-code Internal Tools',
|
||||||
|
icon: 'AS',
|
||||||
|
color: 'bg-[#5f2dde]',
|
||||||
|
category: 'tools',
|
||||||
|
docsUrl: 'https://docs.appsmith.com'
|
||||||
|
},
|
||||||
'n8n': {
|
'n8n': {
|
||||||
name: 'n8n',
|
name: 'n8n',
|
||||||
description: 'Workflow Automation',
|
description: 'Workflow Automation',
|
||||||
|
|||||||
Reference in New Issue
Block a user