14 Commits
v1.2.4 ... main

Author SHA1 Message Date
Yury Kossakovsky
277466f144 fix(postiz): generate .env file to prevent dotenv-cli crash (#40)
the postiz backend image uses dotenv-cli to load /app/.env, which
doesn't exist when config is only passed via docker environment vars.
generate postiz.env from root .env and mount it read-only. also handle
edge case where docker creates the file as a directory on bind mount
failure, and quote values to prevent dotenv-cli misparses.
2026-02-27 20:44:18 -07:00
Yury Kossakovsky
58c485e49a docs: improve CLAUDE.md with missing architecture details
add start_services.py to key files, document python task runner,
docker-compose.override.yml support, yaml anchors, restart behavior,
supabase/dify profiles, --update flag for secrets, and expand file
locations and syntax validation lists
2026-02-27 19:13:36 -07:00
Yury Kossakovsky
b34e1468aa chore: remove redundant agents.md 2026-02-27 19:06:51 -07:00
Yury Kossakovsky
6a1301bfc0 fix(docker): respect docker-compose.override.yml for user customizations (#44)
all compose file assembly points now include the override file last
when present, giving it highest precedence over other compose files
2026-02-27 19:05:50 -07:00
Yury Kossakovsky
19325191c3 fix(installer): skip n8n prompts when n8n profile is not active
load COMPOSE_PROFILES early in 05_configure_services.sh so
is_profile_active guards n8n workflow import and worker config
sections, avoiding confusing prompts for users who don't use n8n
2026-02-27 18:56:39 -07:00
Yury Kossakovsky
107f18296a feat: add appsmith low-code platform for internal tools
adds appsmith as an optional service with caddy reverse proxy,
auto-generated encryption secrets, wizard selection, welcome page
integration, update preview support, and final report output.
bumps version to 1.3.0.
2026-02-27 18:39:45 -07:00
Yury Kossakovsky
059e141daa fix(ragflow): correct nginx config and backend port (#41)
mount nginx config to conf.d/default.conf instead of
sites-available/default, and set SVR_HTTP_PORT to 9380
(official default) instead of 80 which conflicts with
nginx and causes 502 on api requests
2026-02-27 18:11:12 -07:00
Yury Kossakovsky
6505c5cdf4 fix(docker): limit parallel image pulls to prevent tls handshake timeout
set COMPOSE_PARALLEL_LIMIT=3 in .env.example to avoid net/http TLS
handshake timeout errors when pulling many images simultaneously
2026-02-27 16:55:40 -07:00
Yury Kossakovsky
f8e665f85f fix(comfyui): update docker image to cuda 12.8 2026-02-10 17:09:19 -07:00
Yury Kossakovsky
f2f51c6e13 docs: add agents.md with repository guidelines 2026-02-03 10:28:14 -07:00
Yury Kossakovsky
ceaa970273 docs: add missing architecture details to claude.md
document valkey/redis naming, VERSION file, GIT_MODE, caddy addons,
external compose files pattern, GOST_NO_PROXY requirement, and
n8n-template profile pattern
2026-02-03 10:28:10 -07:00
Yury Kossakovsky
6f1aaa0555 docs(changelog): release 1.2.5 2026-02-02 21:11:19 -07:00
Yury Kossakovsky
0dec31539e fix(n8n): use static ffmpeg for alpine compatibility 2026-02-02 21:04:06 -07:00
Yury Kossakovsky
b990b09681 docs: add missing scripts to key files in claude.md 2026-02-02 14:06:27 -07:00
19 changed files with 372 additions and 75 deletions

View File

@@ -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
@@ -436,7 +446,7 @@ 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,api.telegram.org,telegram.org,t.me,core.telegram.org 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
@@ -477,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
View File

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

View File

@@ -2,6 +2,46 @@
## [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 ## [1.2.4] - 2026-01-30
### Fixed ### Fixed

View File

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

View File

@@ -8,6 +8,12 @@
# Custom: Run 'make setup-tls' to use your own certificates # Custom: Run 'make setup-tls' to use your own certificates
import /etc/caddy/addons/tls-snippet.conf import /etc/caddy/addons/tls-snippet.conf
# Appsmith
{$APPSMITH_HOSTNAME} {
import service_tls
reverse_proxy appsmith:80
}
# N8N # N8N
{$N8N_HOSTNAME} { {$N8N_HOSTNAME} {
import service_tls import service_tls

View File

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

View File

@@ -1 +1 @@
1.0.0 1.3.3

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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+=(" {

View File

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

View File

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

View File

@@ -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
} }
#============================================================================= #=============================================================================

View File

@@ -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"]
@@ -395,6 +475,9 @@ def main():
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

View File

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