ilumin-cli 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/mcp-add.d.ts.map +1 -1
- package/dist/commands/mcp-add.js +0 -10
- package/dist/commands/mcp-add.js.map +1 -1
- package/dist/commands/skills-install.js +1 -1
- package/dist/commands/skills-install.js.map +1 -1
- package/package.json +3 -4
- package/skills/ilumin-cloud/SKILL.md +0 -217
- package/skills/ilumin-cloud/references/compose.md +0 -425
- package/skills/ilumin-cloud/references/recommended-stack.md +0 -179
- package/skills/ilumin-security/SKILL.md +0 -355
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-add.d.ts","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"mcp-add.d.ts","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,UAAU,aAAa;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CA8DtE"}
|
package/dist/commands/mcp-add.js
CHANGED
|
@@ -11,7 +11,6 @@ import { installClaudeDesktop } from "../clients/claude-desktop.js";
|
|
|
11
11
|
import { installGemini } from "../clients/gemini.js";
|
|
12
12
|
import { installCursor } from "../clients/cursor.js";
|
|
13
13
|
import { installCodex } from "../clients/codex.js";
|
|
14
|
-
import { installSkillsForClient } from "../utils/install-skills.js";
|
|
15
14
|
// Clientes disponíveis com metadados
|
|
16
15
|
const SUPPORTED_CLIENTS = {
|
|
17
16
|
"claude-code": { label: "Claude Code" },
|
|
@@ -67,15 +66,6 @@ export function runMcpAdd(server, options) {
|
|
|
67
66
|
hasError = true;
|
|
68
67
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69
68
|
printError(`${label}: ${msg}`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const skillsDir = installSkillsForClient(client);
|
|
74
|
-
printSuccess(`Skills instaladas em ${skillsDir}`);
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
78
|
-
printWarning(`${label}: skills não instaladas — ${msg}`);
|
|
79
69
|
}
|
|
80
70
|
}
|
|
81
71
|
console.log();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-add.js","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-add.js","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,qCAAqC;AACrC,MAAM,iBAAiB,GAA+C;IAClE,aAAa,EAAK,EAAE,KAAK,EAAE,aAAa,EAAE;IAC1C,gBAAgB,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE;IAC7C,QAAQ,EAAU,EAAE,KAAK,EAAE,YAAY,EAAE;IACzC,QAAQ,EAAU,EAAE,KAAK,EAAE,QAAQ,EAAE;IACrC,OAAO,EAAW,EAAE,KAAK,EAAE,cAAc,EAAE;CAC9C,CAAC;AAEF,+CAA+C;AAC/C,MAAM,iBAAiB,GAAG,CAAC,QAAQ,CAAC,CAAC;AAOrC,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,OAAsB;IAC5D,oBAAoB;IACpB,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QACpD,UAAU,CAAC,2BAA2B,MAAM,mBAAmB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,mBAAmB;IACnB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,UAAU,CAAC,mCAAmC,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0FAA0F,CAAC,CAAC,CAAC;QACpH,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,8BAA8B;IAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxD,IAAI,OAAO,GAAsB,EAAE,CAAC;IAEpC,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,iCAAiC;QACjC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAsB,CAAC;QAC9D,SAAS,CAAC,8EAA8E,CAAC,CAAC;IAC9F,CAAC;SAAM,CAAC;QACJ,iDAAiD;QACjD,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAsB,CAAC;QAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,UAAU,CAAC,2BAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACvF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;QAC9C,IAAI,CAAC;YACD,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,YAAY,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,UAAU,CAAC,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC;QACnC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,YAAY,CAAC,iDAAiD,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACJ,YAAY,CAAC,+DAA+D,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAuB,EAAE,MAAc;IAC7D,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,aAAa,CAAC,CAAI,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACxD,KAAK,gBAAgB,CAAC,CAAC,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC3D,KAAK,QAAQ,CAAC,CAAS,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;QACpD,KAAK,QAAQ,CAAC,CAAS,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;QACpD,KAAK,OAAO,CAAC,CAAU,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;AACL,CAAC"}
|
|
@@ -10,7 +10,7 @@ import { spawnSync } from "child_process";
|
|
|
10
10
|
import kleur from "kleur";
|
|
11
11
|
import { printInfo, printSuccess, printError } from "../utils/logger.js";
|
|
12
12
|
export function runSkillsInstall(options) {
|
|
13
|
-
const repo = "IluminClients/ilumin-
|
|
13
|
+
const repo = "IluminClients/ilumin-skills";
|
|
14
14
|
const args = ["skills", "add", repo, "-g"];
|
|
15
15
|
if (options.agent) {
|
|
16
16
|
for (const agent of options.agent.split(",").map(a => a.trim())) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-install.js","sourceRoot":"","sources":["../../src/commands/skills-install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMzE,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC1D,MAAM,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"skills-install.js","sourceRoot":"","sources":["../../src/commands/skills-install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMzE,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC1D,MAAM,IAAI,GAAG,6BAA6B,CAAC;IAE3C,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,wCAAwC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;QAClC,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACf,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,UAAU,CAAC,oEAAoE,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,YAAY,CAAC,0CAA0C,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilumin-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Ilumin Cloud CLI — instala e gerencia o MCP da Ilumin em vários clientes de IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
"ilumin-cli": "dist/index.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc && chmod +x dist/index.js
|
|
12
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
14
|
"start": "node dist/index.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
18
|
-
"skills"
|
|
17
|
+
"dist"
|
|
19
18
|
],
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"commander": "^14.0.2",
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ilumin-cloud-developer
|
|
3
|
-
description: Use this skill when managing deployments, servers, custom domains, or installing and integrating applications using the Ilumin Cloud Model Context Protocol (MCP). Useful for building code, deploying, updating, and setting up catalog integrations like WhatsApp, databases, queues, and more.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Ilumin Cloud Developer
|
|
7
|
-
|
|
8
|
-
This skill provides the comprehensive workflow, tools, best practices, and contextual knowledge for interacting with Ilumin Cloud infrastructure via the Ilumin MCP Server.
|
|
9
|
-
|
|
10
|
-
## What is Ilumin Cloud?
|
|
11
|
-
|
|
12
|
-
**Ilumin Cloud** is a Brazilian PaaS (Platform as a Service) focused on simplifying self-hosted application deployment. It provides:
|
|
13
|
-
|
|
14
|
-
- **Managed VPS Servers** — The user has one or more cloud servers (e.g., `srv1.user.ilumin.app`) pre-configured with Docker, Traefik (reverse proxy), and SSL termination.
|
|
15
|
-
- **App Catalog** — A curated collection of ready-to-deploy open-source applications (databases, messaging APIs, automation tools, etc.) that can be installed with a single command — no Docker knowledge required from the user.
|
|
16
|
-
- **CI/CD Pipeline** — Users can push their own source code, which is built via Docker into an image, versioned with a timestamp tag, and deployed as a container.
|
|
17
|
-
- **Automatic HTTPS** — Every deployed app automatically gets a subdomain with SSL (e.g., `myapp.srv1.user.ilumin.app`), and custom domains can also be pointed via CNAME.
|
|
18
|
-
- **Traefik Routing** — All traffic is routed by a Traefik reverse proxy running on the server. Compose files must follow Ilumin's label conventions for routing to work correctly.
|
|
19
|
-
|
|
20
|
-
The Ilumin MCP connects AI agents directly to this infrastructure, enabling autonomous deployment, integration, and maintenance operations without requiring the user to use SSH or the Ilumin web dashboard manually.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Common Use Cases
|
|
25
|
-
|
|
26
|
-
Understanding real-world scenarios where this skill is most valuable:
|
|
27
|
-
|
|
28
|
-
### Deploying a Custom Application
|
|
29
|
-
The user has a web app (Node.js, Python/FastAPI, Next.js, etc.) in a repository and wants it live on the internet. The AI uses `deploy_project` to build and push it, then `install_app` to deploy it for the first time, or `update_app` for subsequent deployments.
|
|
30
|
-
|
|
31
|
-
### Adding WhatsApp to a Project
|
|
32
|
-
The user wants to send/receive WhatsApp messages in their app. The AI installs the **Evolution API** from the catalog, then uses `get_catalog_app_documentation` to obtain its endpoint and API key, and writes the integration code into the user's project.
|
|
33
|
-
|
|
34
|
-
### Adding a Database
|
|
35
|
-
The user needs a PostgreSQL or Redis instance. The AI finds the appropriate app in the catalog and installs it on the same server, exposing the internal connection string for the user's application to consume.
|
|
36
|
-
|
|
37
|
-
### Integrating Automation Workflows
|
|
38
|
-
The user wants to automate tasks (email, webhooks, AI pipelines). The AI installs **n8n** or a similar tool from the catalog, which then connects to the user's app via webhooks or APIs.
|
|
39
|
-
|
|
40
|
-
### Connecting a Custom Domain
|
|
41
|
-
After deploying an app, the user wants it accessible at `app.mycompany.com` instead of the Ilumin subdomain. The AI uses `manage_domain` and instructs the user on configuring the CNAME at their DNS provider.
|
|
42
|
-
|
|
43
|
-
### Rolling Updates Without Downtime
|
|
44
|
-
The user has pushed new code. The AI runs `deploy_project` to build the new image, captures the timestamp tag, and runs `update_app` with the specific tag, ensuring Docker pulls the fresh layer and not a cached one.
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## App Catalog Overview
|
|
49
|
-
|
|
50
|
-
The catalog contains popular open-source services. When the user's need matches one of these, always prefer installing from the catalog instead of building from scratch:
|
|
51
|
-
|
|
52
|
-
| Category | Examples |
|
|
53
|
-
|---|---|
|
|
54
|
-
| **Messaging / WhatsApp** | Evolution API, Chatwoot |
|
|
55
|
-
| **Automation / Workflows** | n8n, Typebot |
|
|
56
|
-
| **Databases** | PostgreSQL, MySQL, Redis, MongoDB |
|
|
57
|
-
| **AI / LLMs** | Open WebUI, Flowise |
|
|
58
|
-
| **CMS / Web** | WordPress, Ghost, Strapi |
|
|
59
|
-
| **Analytics** | Metabase, Plausible |
|
|
60
|
-
| **Storage** | MinIO |
|
|
61
|
-
| **Email** | Mailu, Stalwart |
|
|
62
|
-
|
|
63
|
-
> Always run `list_catalog` to get the current up-to-date list and check if a specific app is available before suggesting a custom implementation.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Architecture Reference
|
|
68
|
-
|
|
69
|
-
Understanding Ilumin's infrastructure prevents misconfiguration:
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
[Internet] → [Traefik (reverse proxy on server)] → [Docker Containers]
|
|
73
|
-
↑
|
|
74
|
-
Reads routing rules from Docker labels
|
|
75
|
-
Handles SSL termination automatically
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
- **Each app is a Docker container** with a `docker-compose.yml` defining the service, network, and Traefik labels.
|
|
79
|
-
- **All containers** must be on the `network: ilumin-network` (or `external: true`) for Traefik to discover them and for containers to communicate internally.
|
|
80
|
-
- **Internal container-to-container communication** uses the container/service name as the hostname (e.g., a Python app connects to `postgres-service:5432` on the shared network).
|
|
81
|
-
- **`get_compose_guidelines`** returns the exact label templates and network definitions required — always use it before writing a compose file.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Available MCP Tools
|
|
86
|
-
|
|
87
|
-
The Ilumin MCP server exposes specialized tools for the complete application lifecycle. Prioritize these whenever the objective involves Ilumin Cloud resources:
|
|
88
|
-
|
|
89
|
-
### Information & Discovery
|
|
90
|
-
- **`list_servers`** — Discover the user's available servers, domains, and currently active applications. *Always use this to verify the environment state before proposing an installation.*
|
|
91
|
-
- **`list_deploys`** — Check historical build logs and image tags of past deployments.
|
|
92
|
-
- **`list_catalog`** — List official and community applications available for 1-click installation.
|
|
93
|
-
|
|
94
|
-
### Building & Versioning
|
|
95
|
-
- **`deploy_project`** — Zip and push the current project source code, triggering a Docker build in the cloud. Returns a specific image tag (timestamp format, e.g., `20260407130809`) on success.
|
|
96
|
-
- **`update_app`** — Update an existing application to a new version. **IMPORTANT**: Always use specific timestamp tags instead of `latest` to force Docker to pull the fresh image rather than using cached layers.
|
|
97
|
-
|
|
98
|
-
### Installation & Configuration
|
|
99
|
-
- **`install_app`** — Install a custom app onto a server for the first time. Requires a `docker-compose.yml` string formatted to Ilumin standards.
|
|
100
|
-
- **`get_compose_guidelines`** — **MANDATORY** before generating any Docker Compose file. Returns the Traefik labels, network standards, and variable placeholders required for correct domain mapping.
|
|
101
|
-
- **`manage_domain`** — Point a custom external domain (e.g., `app.mysite.com`) to an application. Always remind the user to create a CNAME at their DNS provider pointing to the server's primary domain.
|
|
102
|
-
- **`get_app_env`** — Fetches the detailed environment variables (ENVs) for a specific application. Essential when an AI needs to retrieve credentials (like an API Key, database password, or URL) natively configured in the application environment (e.g., retrieving Evolution API credentials).
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
### Catalog & Integrations
|
|
106
|
-
- **`install_catalog_app`** — Install a complete application from the catalog without building source code (e.g., installing a WhatsApp API or database).
|
|
107
|
-
- **`get_catalog_app_documentation`** — **MANDATORY** after installing a catalog app (or when integrating an existing one). Fetches connection data, API structure, and context needed to write integration code correctly. If this fails, fall back to Context7 or web search.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## Core Best Practices and Workflows
|
|
112
|
-
|
|
113
|
-
### 1. The "Memory" File (`ilumin.md`)
|
|
114
|
-
|
|
115
|
-
Always act as the primary state manager for the project's deployment.
|
|
116
|
-
|
|
117
|
-
- **Always maintain a file named `ilumin.md` in the user's project root.**
|
|
118
|
-
- **Record**: Every successful installation, specific timestamp image tags used, the server domain (e.g., `srv1.user.ilumin.app`), custom domains configured, environment variables set, and any architectural notes.
|
|
119
|
-
- **Before any action**: Check `ilumin.md` first to determine whether to call `install_app` (first time) or `update_app` (subsequent deployments) — this prevents destructive duplicate installations.
|
|
120
|
-
|
|
121
|
-
### 2. Proactive Application Integrations Workflow
|
|
122
|
-
|
|
123
|
-
When the user asks to build features requiring complex backend services (WhatsApp, queues, databases, automation), follow this pattern:
|
|
124
|
-
|
|
125
|
-
1. Run `list_catalog` to check if an off-the-shelf app fulfills the requirement.
|
|
126
|
-
2. Present the finding to the user and request permission to install it.
|
|
127
|
-
3. If approved, run `install_catalog_app` to deploy the integration.
|
|
128
|
-
4. Run `get_catalog_app_documentation` with the app's slug to learn the exact integration endpoints, credentials, and variables exposed.
|
|
129
|
-
5. Use that documentation to write the precise integration code inside the user's main project.
|
|
130
|
-
6. Record the installation details in `ilumin.md`.
|
|
131
|
-
|
|
132
|
-
### 3. New Application Deployment Workflow
|
|
133
|
-
|
|
134
|
-
1. Check `ilumin.md` and/or `list_servers` to confirm the app doesn't already exist.
|
|
135
|
-
2. Run `deploy_project` to build the Docker image and obtain the image tag.
|
|
136
|
-
3. Run `get_compose_guidelines` to learn the Traefik standards and network config.
|
|
137
|
-
4. Run `install_app` using the obtained image tag and the generated compose file.
|
|
138
|
-
5. Record the server, tag, app name, and URL in `ilumin.md`.
|
|
139
|
-
|
|
140
|
-
### 4. Sending Environment Variables (ENVs) to Apps
|
|
141
|
-
|
|
142
|
-
**A) Catalog Apps — only the 4 predefined variables**
|
|
143
|
-
|
|
144
|
-
Catalog apps have their `docker-compose.yml` pre-built by the platform. The only ENVs you can pass are the 4 standard platform variables — and only the ones the app actually requires (as listed by `list_catalog`):
|
|
145
|
-
|
|
146
|
-
- `APP_USER` — username/login
|
|
147
|
-
- `APP_PASSWORD` — password
|
|
148
|
-
- `APP_EMAIL` — admin email
|
|
149
|
-
- `APP_API_KEY` — api key or token
|
|
150
|
-
|
|
151
|
-
Pass them via `envVars` in `install_catalog_app`. You **cannot** send arbitrary custom ENV names — they will be ignored. Always ask the user to provide the values; never invent them.
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
install_catalog_app(
|
|
155
|
-
serverDomain: "srv1.user.com",
|
|
156
|
-
slug: "waha",
|
|
157
|
-
envVars: {
|
|
158
|
-
"APP_USER": "admin",
|
|
159
|
-
"APP_PASSWORD": "strongpassword"
|
|
160
|
-
}
|
|
161
|
-
)
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
**B) Custom Apps — any ENVs via the compose `environment` block**
|
|
165
|
-
|
|
166
|
-
When using `install_app` with a custom `docker-compose.yml`, you can freely define any environment variables needed by the application in the `environment` section:
|
|
167
|
-
|
|
168
|
-
```yaml
|
|
169
|
-
services:
|
|
170
|
-
my-app:
|
|
171
|
-
image: ghcr.io/user/my-app:20260407130809
|
|
172
|
-
environment:
|
|
173
|
-
DATABASE_URL: "postgresql://user:pass@postgres:5432/mydb"
|
|
174
|
-
SECRET_KEY: "my-secret"
|
|
175
|
-
DEBUG: "false"
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Always ask the user for sensitive values before writing them into the compose. Never hardcode guessed values.
|
|
179
|
-
|
|
180
|
-
**C) Reading ENVs from an Existing App — via `get_app_env`**
|
|
181
|
-
|
|
182
|
-
If the AI needs credentials from an already-installed app (e.g., the Evolution API key or a database password), use `get_app_env` to fetch the live ENV values directly. This avoids asking the user for information they may not remember.
|
|
183
|
-
|
|
184
|
-
### 4. Updating an Existing Application
|
|
185
|
-
|
|
186
|
-
1. Verify it exists via `list_servers` or `ilumin.md`.
|
|
187
|
-
2. Run `deploy_project` to commit changes and obtain the new timestamp version tag (e.g., `20260407130809`).
|
|
188
|
-
3. Run `update_app` supplying the `app_name` and the new specific version tag. **Never use `latest`.**
|
|
189
|
-
4. Update `ilumin.md` with the new tag and date.
|
|
190
|
-
|
|
191
|
-
### 5. Custom Domain Configuration
|
|
192
|
-
|
|
193
|
-
When using `manage_domain`, always:
|
|
194
|
-
- Explicitly inform the user to create a **CNAME record** at their DNS provider pointing their custom domain to the server's primary domain (e.g., `srv1.user.ilumin.app`).
|
|
195
|
-
- Check `ilumin.md` to verify if a custom domain is already established, preventing accidental overwrites without user confirmation.
|
|
196
|
-
- Note that DNS propagation can take up to 24–48 hours, though it's typically much faster.
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## Common Errors and Troubleshooting
|
|
201
|
-
|
|
202
|
-
| Symptom | Likely Cause | Resolution |
|
|
203
|
-
|---|---|---|
|
|
204
|
-
| App unreachable after install | Missing or incorrect Traefik labels | Re-run `get_compose_guidelines`, regenerate compose file |
|
|
205
|
-
| Docker pulls old image after update | Used `latest` tag instead of specific tag | Always use timestamp tags from `deploy_project` output |
|
|
206
|
-
| Container exits immediately | Missing required env var or misconfigured port | Review compose file; check logs via server SSH or Ilumin dashboard |
|
|
207
|
-
| Custom domain returns 404 | CNAME not yet propagated or `manage_domain` not called | Verify DNS, re-run `manage_domain` if needed |
|
|
208
|
-
| `install_app` fails with "already exists" | App installed previously | Use `update_app` instead |
|
|
209
|
-
| Catalog app missing credentials | Env vars not passed during `install_catalog_app` | Check `list_catalog` for required `env_vars`, provide them |
|
|
210
|
-
| Inter-container connection refused | Containers on different networks | Ensure all services share `ilumin-network` as an external network |
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Additional Resources
|
|
215
|
-
|
|
216
|
-
- For Docker Compose formatting rules, Traefik labels, network configuration, and reference examples, see [references/compose.md](references/compose.md)
|
|
217
|
-
- For the recommended default stack (NocoDB, Redis, BullMQ) when the user hasn't specified infrastructure, see [references/recommended-stack.md](references/recommended-stack.md)
|
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
# Ilumin Compose Formatter
|
|
2
|
-
|
|
3
|
-
This skill defines the mandatory rules and patterns for writing `docker-compose.yml` files compatible with the Ilumin Cloud platform. Follow every rule — small mistakes like a fixed router name or a wrong network definition are the most common causes of deployment failures.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## How Ilumin's Infrastructure Works
|
|
8
|
-
|
|
9
|
-
Understanding the infrastructure prevents 90% of errors:
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
[Internet] → [Traefik v3 on port 80/443] → [Docker containers via labels]
|
|
13
|
-
↑
|
|
14
|
-
Reads routing rules from labels
|
|
15
|
-
Handles SSL via Let's Encrypt
|
|
16
|
-
Routes by hostname or path
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- Traefik runs as a separate container on the `traefik` **external** Docker network.
|
|
20
|
-
- All app containers that need public access must join the `traefik` network.
|
|
21
|
-
- Containers that must stay private (databases, caches) must join only the `internal` network.
|
|
22
|
-
- Containers on the same `internal` network communicate using the **service name** as hostname (e.g., `postgres:5432`).
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Mandatory Formatting Rules
|
|
27
|
-
|
|
28
|
-
### 1. No Comments
|
|
29
|
-
Remove **all** comments (lines starting with `#`) from the final compose file.
|
|
30
|
-
|
|
31
|
-
### 2. No Resource Limits
|
|
32
|
-
Remove any `deploy`, `resources`, `limits`, or `reservations` sections — Ilumin manages these at the infrastructure level.
|
|
33
|
-
|
|
34
|
-
### 3. Image Version Variable
|
|
35
|
-
Replace the image tag of the **main application** with `${APP_VERSION}`.
|
|
36
|
-
|
|
37
|
-
```yml
|
|
38
|
-
# ❌ Wrong
|
|
39
|
-
image: n8n:latest
|
|
40
|
-
image: ghost:5.82.2
|
|
41
|
-
image: myapp:v2.1.0
|
|
42
|
-
|
|
43
|
-
# ✅ Correct
|
|
44
|
-
image: n8n:${APP_VERSION}
|
|
45
|
-
image: ghost:${APP_VERSION}
|
|
46
|
-
image: myapp:${APP_VERSION}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
> **Critical:** `APP_VERSION` replaces the **entire tag** after the colon, including any `v` prefix. Never write `v${APP_VERSION}`.
|
|
50
|
-
|
|
51
|
-
### 4. Database Password Variable
|
|
52
|
-
Replace **all** database passwords with `${DB_PASSWORD}`:
|
|
53
|
-
|
|
54
|
-
```yml
|
|
55
|
-
# Applies to: POSTGRES_PASSWORD, MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD, MARIADB_ROOT_PASSWORD, etc.
|
|
56
|
-
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
57
|
-
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
|
58
|
-
- MYSQL_PASSWORD=${DB_PASSWORD}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 5. Optional Admin Credentials
|
|
62
|
-
If the app supports initial admin setup, use these standard variables:
|
|
63
|
-
- `${APP_USERNAME}` — admin username
|
|
64
|
-
- `${APP_PASSWORD}` — admin password
|
|
65
|
-
- `${APP_EMAIL}` — admin email
|
|
66
|
-
|
|
67
|
-
### 6. No Backslashes in URLs
|
|
68
|
-
Never use `\` before `$` in URLs inside the compose file.
|
|
69
|
-
|
|
70
|
-
```yml
|
|
71
|
-
# ❌ Wrong
|
|
72
|
-
- APP_URL=https://\${BASE_DOMAIN}
|
|
73
|
-
|
|
74
|
-
# ✅ Correct
|
|
75
|
-
- APP_URL=https://${BASE_DOMAIN}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Network Rules (Critical)
|
|
81
|
-
|
|
82
|
-
```yml
|
|
83
|
-
# ✅ Main app service (public-facing) — both networks
|
|
84
|
-
networks:
|
|
85
|
-
- traefik
|
|
86
|
-
- internal
|
|
87
|
-
|
|
88
|
-
# ✅ Dependency services (db, redis, queue) — internal only
|
|
89
|
-
networks:
|
|
90
|
-
- internal
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Always define networks at the bottom of the file exactly like this:**
|
|
94
|
-
|
|
95
|
-
```yml
|
|
96
|
-
networks:
|
|
97
|
-
traefik:
|
|
98
|
-
external: true
|
|
99
|
-
internal:
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
> ⚠️ The `traefik` network must be `external: true`. Forgetting this causes Traefik to be unable to discover the container.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Traefik Labels (Critical)
|
|
107
|
-
|
|
108
|
-
Every public-facing service needs these labels. **Copy this block exactly** and replace `<appname>` with a unique identifier for the app.
|
|
109
|
-
|
|
110
|
-
```yml
|
|
111
|
-
labels:
|
|
112
|
-
- traefik.enable=true
|
|
113
|
-
- traefik.docker.network=traefik
|
|
114
|
-
- traefik.http.routers.<appname>.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
115
|
-
- traefik.http.routers.<appname>.entrypoints=websecure
|
|
116
|
-
- traefik.http.routers.<appname>.tls=true
|
|
117
|
-
- traefik.http.routers.<appname>.tls.certresolver=letsencrypt
|
|
118
|
-
- traefik.http.services.<appname>.loadbalancer.server.port=<PORT>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Router Naming Rules (Most Common Error)
|
|
122
|
-
|
|
123
|
-
The router/service name in Traefik labels **must be unique across all apps on the server**. Using generic names like `backend` or `frontend` causes conflicts when multiple apps are deployed.
|
|
124
|
-
|
|
125
|
-
**Formula:** `{appname}{role}` — always prefix with the app name.
|
|
126
|
-
|
|
127
|
-
```yml
|
|
128
|
-
# ❌ WRONG — will conflict with any other app using the same generic name
|
|
129
|
-
- traefik.http.routers.backend.rule=...
|
|
130
|
-
- traefik.http.routers.frontend.rule=...
|
|
131
|
-
|
|
132
|
-
# ✅ CORRECT — unique per app
|
|
133
|
-
- traefik.http.routers.n8nmain.rule=...
|
|
134
|
-
- traefik.http.routers.ghostblog.rule=...
|
|
135
|
-
- traefik.http.routers.myappfrontend.rule=...
|
|
136
|
-
- traefik.http.routers.myappbackend.rule=...
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Domain Rule Syntax
|
|
140
|
-
|
|
141
|
-
The domain rule must use this exact syntax — it supports both the Ilumin base domain and an optional custom domain:
|
|
142
|
-
|
|
143
|
-
```
|
|
144
|
-
Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
> **Why this syntax?** `${CUSTOM_DOMAIN:+ || Host(...)}` is a shell parameter expansion that only adds the custom domain rule if the variable is set. This allows the platform to manage domain routing dynamically without requiring a compose file change.
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## Multi-Service Compose Rules
|
|
152
|
-
|
|
153
|
-
### Two Services, One Domain (Path Routing — Frontend + Backend)
|
|
154
|
-
|
|
155
|
-
When your app has a frontend and a backend on the **same domain**:
|
|
156
|
-
|
|
157
|
-
```yml
|
|
158
|
-
# Frontend: answers on / (no path prefix needed)
|
|
159
|
-
labels:
|
|
160
|
-
- traefik.http.routers.myappfrontend.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
161
|
-
- traefik.http.services.myappfrontend.loadbalancer.server.port=3000
|
|
162
|
-
|
|
163
|
-
# Backend: answers on /api
|
|
164
|
-
labels:
|
|
165
|
-
- traefik.http.routers.myappbackend.rule=(Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}) && PathPrefix(`/api`)
|
|
166
|
-
- traefik.http.services.myappbackend.loadbalancer.server.port=8000
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Two Services, Two Subdomains
|
|
170
|
-
|
|
171
|
-
When you need separate subdomains for backend and frontend:
|
|
172
|
-
|
|
173
|
-
```yml
|
|
174
|
-
# Frontend — base domain
|
|
175
|
-
labels:
|
|
176
|
-
- traefik.http.routers.myappfrontend.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
177
|
-
|
|
178
|
-
# Backend — api subdomain prefix
|
|
179
|
-
labels:
|
|
180
|
-
- traefik.http.routers.myappbackend.rule=Host(`api.${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`api.${CUSTOM_DOMAIN}`)}
|
|
181
|
-
|
|
182
|
-
# Other services follow the same prefix pattern:
|
|
183
|
-
# db.${BASE_DOMAIN}, admin.${BASE_DOMAIN}, etc.
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
## HTTP → HTTPS Redirect (Optional but Recommended)
|
|
189
|
-
|
|
190
|
-
To redirect HTTP traffic to HTTPS, add these labels alongside the main router:
|
|
191
|
-
|
|
192
|
-
```yml
|
|
193
|
-
labels:
|
|
194
|
-
# ... (main HTTPS router labels above) ...
|
|
195
|
-
|
|
196
|
-
# HTTP redirect router
|
|
197
|
-
- traefik.http.routers.<appname>-http.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
198
|
-
- traefik.http.routers.<appname>-http.entrypoints=web
|
|
199
|
-
- traefik.http.routers.<appname>-http.middlewares=redirect-to-https
|
|
200
|
-
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Architecture Options
|
|
206
|
-
|
|
207
|
-
### Option 1: Unified Build (Recommended) ✅
|
|
208
|
-
|
|
209
|
-
Serve the compiled frontend through the backend. One container, no CORS issues.
|
|
210
|
-
|
|
211
|
-
- Build the frontend in Stage 1 of the Dockerfile.
|
|
212
|
-
- Copy `dist/` into the backend Stage 2.
|
|
213
|
-
- Backend serves static files from `/` and API from `/api`.
|
|
214
|
-
- Only one Traefik router needed.
|
|
215
|
-
|
|
216
|
-
### Option 2: Separate Containers
|
|
217
|
-
|
|
218
|
-
Use path routing or subdomains. Requires proper CORS config on the backend.
|
|
219
|
-
|
|
220
|
-
> The browser **cannot** access `http://backend:8000` — the Docker internal network is not accessible from the user's browser. The frontend must call the public domain (e.g., `/api` relative path or `https://api.domain.com`).
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## Common Errors and Fixes
|
|
225
|
-
|
|
226
|
-
| Error | Cause | Fix |
|
|
227
|
-
|---|---|---|
|
|
228
|
-
| App unreachable (502 Bad Gateway) | Wrong port in `loadbalancer.server.port` | Set the port the container actually listens on |
|
|
229
|
-
| App unreachable (no SSL / cert error) | Missing `tls=true` or `certresolver=letsencrypt` | Add both TLS labels |
|
|
230
|
-
| Two apps conflict (first works, second doesn't) | Duplicate router names in Traefik labels | Use unique names: `{appname}{role}` |
|
|
231
|
-
| Database not reachable from app | DB container not on `internal` network | Ensure both app and DB are on `internal` |
|
|
232
|
-
| Traefik can't discover container | `traefik` network not set as `external: true` | Fix the network definition at the bottom |
|
|
233
|
-
| URL contains `v${APP_VERSION}` | Manually adding `v` before the variable | Remove the `v` — `APP_VERSION` already includes it |
|
|
234
|
-
| URL contains backslash `\$` | Escaping `$` in compose | Remove `\` — dollar signs don't need escaping in compose YAML |
|
|
235
|
-
| Custom domain not working | `manage_domain` not called after install | Call `manage_domain` and configure CNAME at DNS provider |
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Reference Examples
|
|
240
|
-
|
|
241
|
-
### Minimal Single App (Uptime Kuma)
|
|
242
|
-
|
|
243
|
-
```yml
|
|
244
|
-
services:
|
|
245
|
-
uptime-kuma:
|
|
246
|
-
image: louislam/uptime-kuma:${APP_VERSION}
|
|
247
|
-
volumes:
|
|
248
|
-
- uptime_data:/app/data
|
|
249
|
-
networks:
|
|
250
|
-
- traefik
|
|
251
|
-
- internal
|
|
252
|
-
labels:
|
|
253
|
-
- traefik.enable=true
|
|
254
|
-
- traefik.docker.network=traefik
|
|
255
|
-
- traefik.http.routers.uptimekuma.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
256
|
-
- traefik.http.routers.uptimekuma.entrypoints=websecure
|
|
257
|
-
- traefik.http.routers.uptimekuma.tls=true
|
|
258
|
-
- traefik.http.routers.uptimekuma.tls.certresolver=letsencrypt
|
|
259
|
-
- traefik.http.services.uptimekuma.loadbalancer.server.port=3001
|
|
260
|
-
restart: unless-stopped
|
|
261
|
-
|
|
262
|
-
volumes:
|
|
263
|
-
uptime_data:
|
|
264
|
-
|
|
265
|
-
networks:
|
|
266
|
-
traefik:
|
|
267
|
-
external: true
|
|
268
|
-
internal:
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### App + Database (WordPress + MySQL)
|
|
272
|
-
|
|
273
|
-
```yml
|
|
274
|
-
services:
|
|
275
|
-
wordpress:
|
|
276
|
-
image: wordpress:${APP_VERSION}
|
|
277
|
-
depends_on:
|
|
278
|
-
- mysql
|
|
279
|
-
environment:
|
|
280
|
-
- WORDPRESS_DB_HOST=mysql
|
|
281
|
-
- WORDPRESS_DB_USER=wordpress
|
|
282
|
-
- WORDPRESS_DB_PASSWORD=${DB_PASSWORD}
|
|
283
|
-
- WORDPRESS_DB_NAME=wordpress
|
|
284
|
-
volumes:
|
|
285
|
-
- wordpress_data:/var/www/html
|
|
286
|
-
networks:
|
|
287
|
-
- traefik
|
|
288
|
-
- internal
|
|
289
|
-
labels:
|
|
290
|
-
- traefik.enable=true
|
|
291
|
-
- traefik.docker.network=traefik
|
|
292
|
-
- traefik.http.routers.wordpress.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
293
|
-
- traefik.http.routers.wordpress.entrypoints=websecure
|
|
294
|
-
- traefik.http.routers.wordpress.tls=true
|
|
295
|
-
- traefik.http.routers.wordpress.tls.certresolver=letsencrypt
|
|
296
|
-
- traefik.http.services.wordpress.loadbalancer.server.port=80
|
|
297
|
-
restart: unless-stopped
|
|
298
|
-
|
|
299
|
-
mysql:
|
|
300
|
-
image: mysql:5.7
|
|
301
|
-
environment:
|
|
302
|
-
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
|
303
|
-
- MYSQL_DATABASE=wordpress
|
|
304
|
-
- MYSQL_USER=wordpress
|
|
305
|
-
- MYSQL_PASSWORD=${DB_PASSWORD}
|
|
306
|
-
volumes:
|
|
307
|
-
- mysql_data:/var/lib/mysql
|
|
308
|
-
networks:
|
|
309
|
-
- internal
|
|
310
|
-
restart: unless-stopped
|
|
311
|
-
|
|
312
|
-
volumes:
|
|
313
|
-
wordpress_data:
|
|
314
|
-
mysql_data:
|
|
315
|
-
|
|
316
|
-
networks:
|
|
317
|
-
traefik:
|
|
318
|
-
external: true
|
|
319
|
-
internal:
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### App + Database (Baserow + PostgreSQL)
|
|
323
|
-
|
|
324
|
-
```yml
|
|
325
|
-
services:
|
|
326
|
-
baserow:
|
|
327
|
-
image: baserow/baserow:${APP_VERSION}
|
|
328
|
-
depends_on:
|
|
329
|
-
- postgres
|
|
330
|
-
volumes:
|
|
331
|
-
- baserow_data:/baserow/data
|
|
332
|
-
environment:
|
|
333
|
-
- BASEROW_PUBLIC_URL=https://${BASE_DOMAIN}
|
|
334
|
-
- DATABASE_HOST=postgres
|
|
335
|
-
- DATABASE_NAME=baserow
|
|
336
|
-
- DATABASE_USER=postgres
|
|
337
|
-
- DATABASE_PASSWORD=${DB_PASSWORD}
|
|
338
|
-
networks:
|
|
339
|
-
- traefik
|
|
340
|
-
- internal
|
|
341
|
-
labels:
|
|
342
|
-
- traefik.enable=true
|
|
343
|
-
- traefik.docker.network=traefik
|
|
344
|
-
- traefik.http.routers.baserow.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
|
|
345
|
-
- traefik.http.routers.baserow.entrypoints=websecure
|
|
346
|
-
- traefik.http.routers.baserow.tls=true
|
|
347
|
-
- traefik.http.routers.baserow.tls.certresolver=letsencrypt
|
|
348
|
-
- traefik.http.routers.baserow.service=baserow
|
|
349
|
-
- traefik.http.services.baserow.loadbalancer.server.port=80
|
|
350
|
-
restart: unless-stopped
|
|
351
|
-
|
|
352
|
-
postgres:
|
|
353
|
-
image: postgres:16
|
|
354
|
-
environment:
|
|
355
|
-
- POSTGRES_USER=postgres
|
|
356
|
-
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
357
|
-
- POSTGRES_DB=baserow
|
|
358
|
-
volumes:
|
|
359
|
-
- postgres_data:/var/lib/postgresql/data
|
|
360
|
-
networks:
|
|
361
|
-
- internal
|
|
362
|
-
restart: unless-stopped
|
|
363
|
-
|
|
364
|
-
volumes:
|
|
365
|
-
baserow_data:
|
|
366
|
-
postgres_data:
|
|
367
|
-
|
|
368
|
-
networks:
|
|
369
|
-
traefik:
|
|
370
|
-
external: true
|
|
371
|
-
internal:
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Custom App (Fixed Domain, No Variables)
|
|
375
|
-
|
|
376
|
-
Used when the user wants to hardcode a specific domain. Inform the user that Ilumin's domain management panel will NOT work in this mode — they must manage DNS manually.
|
|
377
|
-
|
|
378
|
-
```yml
|
|
379
|
-
services:
|
|
380
|
-
app:
|
|
381
|
-
image: myapp:${APP_VERSION}
|
|
382
|
-
container_name: myapp
|
|
383
|
-
restart: unless-stopped
|
|
384
|
-
environment:
|
|
385
|
-
- NODE_ENV=production
|
|
386
|
-
networks:
|
|
387
|
-
- traefik
|
|
388
|
-
- internal
|
|
389
|
-
labels:
|
|
390
|
-
- traefik.enable=true
|
|
391
|
-
- traefik.docker.network=traefik
|
|
392
|
-
- traefik.http.routers.myapp.rule=Host(`app.mycompany.com`)
|
|
393
|
-
- traefik.http.routers.myapp.entrypoints=websecure
|
|
394
|
-
- traefik.http.routers.myapp.tls=true
|
|
395
|
-
- traefik.http.routers.myapp.tls.certresolver=letsencrypt
|
|
396
|
-
- traefik.http.routers.myapp-http.rule=Host(`app.mycompany.com`)
|
|
397
|
-
- traefik.http.routers.myapp-http.entrypoints=web
|
|
398
|
-
- traefik.http.routers.myapp-http.middlewares=redirect-to-https
|
|
399
|
-
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
|
|
400
|
-
- traefik.http.services.myapp.loadbalancer.server.port=3000
|
|
401
|
-
|
|
402
|
-
networks:
|
|
403
|
-
traefik:
|
|
404
|
-
external: true
|
|
405
|
-
internal:
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
## Pre-Deploy Compose Checklist
|
|
411
|
-
|
|
412
|
-
- [ ] All image versions replaced with `${APP_VERSION}` (no `v` prefix before the variable)
|
|
413
|
-
- [ ] All database passwords replaced with `${DB_PASSWORD}`
|
|
414
|
-
- [ ] No hardcoded secrets or credentials in the file
|
|
415
|
-
- [ ] Main service is on both `traefik` and `internal` networks
|
|
416
|
-
- [ ] Database/cache services are on `internal` network only
|
|
417
|
-
- [ ] Network definitions at the bottom: `traefik` is `external: true`, `internal` has no extra config
|
|
418
|
-
- [ ] Traefik router name is unique (format: `{appname}{role}`)
|
|
419
|
-
- [ ] Domain rule uses exact syntax with `${BASE_DOMAIN}` and `${CUSTOM_DOMAIN:+ ...}` expansion
|
|
420
|
-
- [ ] `loadbalancer.server.port` matches the actual port the container listens on
|
|
421
|
-
- [ ] TLS labels present: `tls=true` + `tls.certresolver=letsencrypt`
|
|
422
|
-
- [ ] No `\` backslashes before `$` in URLs
|
|
423
|
-
- [ ] No comments (`#`) in the final file
|
|
424
|
-
- [ ] No `deploy`, `resources`, `limits`, or `reservations` sections
|
|
425
|
-
- [ ] All volumes declared in the `volumes:` section at the bottom
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# Ilumin Recommended Stack
|
|
2
|
-
|
|
3
|
-
When the user hasn't specified which infrastructure services to use, follow this recommended stack. These services are optimized for Ilumin Cloud — they are lightweight, self-hosted, and integrate well with each other via the `internal` Docker network.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## How Internal Communication Works
|
|
8
|
-
|
|
9
|
-
All services in the same `docker-compose.yml` (or on the same `internal` Docker network) can communicate using the **service name as the hostname**.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
App Container → [internal network] → postgres:5432
|
|
13
|
-
App Container → [internal network] → redis:6379
|
|
14
|
-
NocoDB → [internal network] → postgres:5432
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
> The key rule: **both the app and the service it connects to must share the same network** (always `internal`). The public internet never touches these internal services — only the main app container is exposed via Traefik.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 1. 🗄️ Database: NocoDB + PostgreSQL
|
|
22
|
-
|
|
23
|
-
**NocoDB** is the recommended database layer for Ilumin apps. It is a lightweight, self-hosted Airtable alternative that sits on top of PostgreSQL and exposes a full REST + GraphQL API, a visual UI, and programmatic table creation via API — no raw SQL required for most operations.
|
|
24
|
-
|
|
25
|
-
### Why NocoDB?
|
|
26
|
-
- Full REST API to read/write/create tables from any app or AI agent
|
|
27
|
-
- Visual interface for non-developers to manage data
|
|
28
|
-
- Integrates with webhooks, automations, and third-party tools
|
|
29
|
-
- JWT-based API authentication via `NC_AUTH_JWT_SECRET`
|
|
30
|
-
|
|
31
|
-
### Connecting Your App to NocoDB
|
|
32
|
-
|
|
33
|
-
NocoDB exposes an HTTP API. Your app does **not** connect to NocoDB as a database directly — it makes API calls to NocoDB's REST endpoint.
|
|
34
|
-
|
|
35
|
-
If your app is in the **same compose** or **same `internal` network**, use the internal hostname:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
# Internal (same internal network) — preferred
|
|
39
|
-
NOCODB_URL=http://nocodb:8080
|
|
40
|
-
|
|
41
|
-
# External (different server or no shared network)
|
|
42
|
-
NOCODB_URL=https://your-nocodb-domain.com
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
```python
|
|
46
|
-
# Python example — connecting via internal network
|
|
47
|
-
import httpx
|
|
48
|
-
|
|
49
|
-
NOCODB_URL = os.getenv("NOCODB_URL", "http://nocodb:8080")
|
|
50
|
-
NOCODB_API_KEY = os.getenv("NOCODB_API_KEY") # = NC_AUTH_JWT_SECRET value
|
|
51
|
-
|
|
52
|
-
headers = {
|
|
53
|
-
"xc-auth": NOCODB_API_KEY,
|
|
54
|
-
"Content-Type": "application/json"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# List records from a table
|
|
58
|
-
response = httpx.get(f"{NOCODB_URL}/api/v1/db/data/noco/{{TABLE_ID}}/{{VIEW_ID}}", headers=headers)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### If NocoDB Is Already Installed
|
|
62
|
-
|
|
63
|
-
Before installing a new NocoDB, check if one is already running on the server:
|
|
64
|
-
|
|
65
|
-
1. Run `list_servers` to see installed apps.
|
|
66
|
-
2. If NocoDB is already there, retrieve its `NC_AUTH_JWT_SECRET` env var — this is the API key your app uses to authenticate.
|
|
67
|
-
3. Use the internal URL `http://nocodb:8080` if your app is in the same compose, or the public domain if separate.
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## 2. ⚡ Cache & Sessions: Redis + RedisInsight
|
|
72
|
-
|
|
73
|
-
**Redis** is the recommended solution for caching, session storage, and rate limiting. **RedisInsight** is the official Redis visual UI — install them together for observability.
|
|
74
|
-
|
|
75
|
-
### Connecting Your App to Redis
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
# Internal connection string (same internal network)
|
|
79
|
-
REDIS_URL=redis://redis:6379
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// Node.js / TypeScript — ioredis
|
|
84
|
-
import Redis from 'ioredis';
|
|
85
|
-
|
|
86
|
-
const redis = new Redis(process.env.REDIS_URL || 'redis://redis:6379');
|
|
87
|
-
|
|
88
|
-
// Cache example
|
|
89
|
-
await redis.set('key', 'value', 'EX', 3600); // expires in 1 hour
|
|
90
|
-
const value = await redis.get('key');
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
```python
|
|
94
|
-
# Python — redis-py
|
|
95
|
-
import redis
|
|
96
|
-
import os
|
|
97
|
-
|
|
98
|
-
r = redis.from_url(os.getenv("REDIS_URL", "redis://redis:6379"))
|
|
99
|
-
|
|
100
|
-
r.set("key", "value", ex=3600)
|
|
101
|
-
value = r.get("key")
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
> **Network rule:** Redis is on `internal` only — it is **never** exposed publicly. RedisInsight (the UI) is on `traefik + internal` so it can be accessed via browser. Your app connects to `redis:6379` on the `internal` network.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 3. 🔄 Job Queues: BullMQ (uses Redis)
|
|
109
|
-
|
|
110
|
-
**BullMQ** is the recommended job queue solution. It uses Redis as its backend — so if Redis is already installed (from item 2), BullMQ is ready to use with no extra infrastructure.
|
|
111
|
-
|
|
112
|
-
**BullMQ is a library, not a separate service.** It runs inside your application code.
|
|
113
|
-
|
|
114
|
-
### When to use BullMQ
|
|
115
|
-
- Sending emails asynchronously
|
|
116
|
-
- Processing uploaded files or images
|
|
117
|
-
- Scheduling recurring tasks (cron-like)
|
|
118
|
-
- Handling webhooks with retry logic
|
|
119
|
-
- Any long-running task that shouldn't block an HTTP response
|
|
120
|
-
|
|
121
|
-
### BullMQ Integration (Node.js / TypeScript)
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
import { Queue, Worker } from 'bullmq';
|
|
125
|
-
import IORedis from 'ioredis';
|
|
126
|
-
|
|
127
|
-
const connection = new IORedis(process.env.REDIS_URL || 'redis://redis:6379', {
|
|
128
|
-
maxRetriesPerRequest: null, // required for BullMQ
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// --- Producer (add jobs to the queue) ---
|
|
132
|
-
const emailQueue = new Queue('emails', { connection });
|
|
133
|
-
|
|
134
|
-
await emailQueue.add('send-welcome', {
|
|
135
|
-
to: 'user@example.com',
|
|
136
|
-
subject: 'Welcome!',
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// --- Worker (process jobs from the queue) ---
|
|
140
|
-
const worker = new Worker('emails', async (job) => {
|
|
141
|
-
const { to, subject } = job.data;
|
|
142
|
-
await sendEmail(to, subject); // your email logic
|
|
143
|
-
}, { connection });
|
|
144
|
-
|
|
145
|
-
worker.on('completed', (job) => console.log(`Job ${job.id} completed`));
|
|
146
|
-
worker.on('failed', (job, err) => console.error(`Job ${job?.id} failed:`, err));
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### BullMQ Integration (Python — with rq or celery as alternatives)
|
|
150
|
-
|
|
151
|
-
> BullMQ is Node.js-native. For Python apps, use **Celery + Redis** or **rq + Redis** instead — same Redis instance, same connection string.
|
|
152
|
-
|
|
153
|
-
```python
|
|
154
|
-
# Python — Celery with Redis broker
|
|
155
|
-
from celery import Celery
|
|
156
|
-
|
|
157
|
-
app = Celery('tasks', broker=os.getenv('REDIS_URL', 'redis://redis:6379/0'))
|
|
158
|
-
|
|
159
|
-
@app.task
|
|
160
|
-
def send_email(to: str, subject: str):
|
|
161
|
-
# your email logic
|
|
162
|
-
pass
|
|
163
|
-
|
|
164
|
-
# Calling the task (async)
|
|
165
|
-
send_email.delay('user@example.com', 'Welcome!')
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## Decision Guide
|
|
171
|
-
|
|
172
|
-
| Need | Recommended Solution |
|
|
173
|
-
|---|---|
|
|
174
|
-
| Store and query structured data | NocoDB + PostgreSQL |
|
|
175
|
-
| Cache API responses, store sessions | Redis |
|
|
176
|
-
| Background jobs, async tasks, retries | BullMQ (Node.js) / Celery (Python) — both use Redis |
|
|
177
|
-
| Visual database management | NocoDB UI (built-in) |
|
|
178
|
-
| Visual Redis inspection | RedisInsight |
|
|
179
|
-
| All of the above | Full stack compose above |
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ilumin-security-reviewer
|
|
3
|
-
description: Use this skill before deploying any application to production. It covers mandatory security practices including authentication, frontend/backend separation, input validation, secrets management, dependency hygiene, and audit logging. Apply these checks on every new feature or deployment.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Application Security Reviewer
|
|
7
|
-
|
|
8
|
-
This skill defines the mandatory security standards that must be verified **before deploying any application to production**. Apply each section as a checklist. Missing any item can expose user data, allow unauthorized access, or compromise the entire platform.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. Authentication & Session Management
|
|
13
|
-
|
|
14
|
-
### Bearer Token / JWT
|
|
15
|
-
All protected API routes **must** require an `Authorization: Bearer <token>` header. Unauthenticated requests must return `401 Unauthorized` immediately — no partial data should ever be returned.
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
# Correct — FastAPI example
|
|
19
|
-
from fastapi import Depends, HTTPException, status
|
|
20
|
-
from fastapi.security import HTTPBearer
|
|
21
|
-
|
|
22
|
-
security = HTTPBearer()
|
|
23
|
-
|
|
24
|
-
@app.get("/api/protected")
|
|
25
|
-
def protected_route(token = Depends(security)):
|
|
26
|
-
user = verify_jwt(token.credentials)
|
|
27
|
-
if not user:
|
|
28
|
-
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// Correct — Express.js example
|
|
33
|
-
app.use('/api', (req, res, next) => {
|
|
34
|
-
const token = req.headers.authorization?.split(' ')[1];
|
|
35
|
-
if (!token || !verifyJWT(token)) return res.status(401).json({ error: 'Unauthorized' });
|
|
36
|
-
next();
|
|
37
|
-
});
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Password Storage
|
|
41
|
-
- **NEVER** store passwords in plain text or use MD5/SHA-1.
|
|
42
|
-
- Use **Argon2id** (preferred) or **BCrypt** with a minimum work factor of 10.
|
|
43
|
-
- Or use PostgreSQL's `pgCrypto` extension:
|
|
44
|
-
```sql
|
|
45
|
-
-- Store: crypt('user_password', gen_salt('bf', 10))
|
|
46
|
-
-- Verify: SELECT (password_hash = crypt('attempt', password_hash)) AS match FROM users WHERE id = $1;
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Cookie Security Flags
|
|
50
|
-
All session cookies **must** include:
|
|
51
|
-
- `HttpOnly` — prevents `document.cookie` access (mitigates XSS).
|
|
52
|
-
- `Secure` — transmits cookies over HTTPS only.
|
|
53
|
-
- `SameSite=Lax` or `Strict` — protects against CSRF attacks.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 2. Frontend/Backend Separation
|
|
58
|
-
|
|
59
|
-
**The frontend must NEVER make direct calls to third-party APIs with sensitive credentials.** Any API key, auth header, or secret that's placed in frontend code is exposed to every user via browser dev tools.
|
|
60
|
-
|
|
61
|
-
### The Correct Pattern
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
User Browser → Frontend (no secrets) → Your Backend API → Third-Party API (with secrets)
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**Why this matters:** If your app calls the Evolution API, an AI API, or any service with an API key directly from the frontend, the user can open the Network tab in DevTools and steal that key.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
// WRONG — API key exposed in frontend
|
|
71
|
-
const response = await fetch('https://api.openai.com/v1/chat', {
|
|
72
|
-
headers: { 'Authorization': `Bearer ${OPENAI_KEY}` } // USER CAN SEE THIS
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// CORRECT — Frontend calls your own backend
|
|
76
|
-
const response = await fetch('/api/chat', {
|
|
77
|
-
headers: { 'Authorization': `Bearer ${userJwtToken}` }
|
|
78
|
-
});
|
|
79
|
-
// Your backend then calls OpenAI with the secret key stored in env vars
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### What Belongs Where
|
|
83
|
-
|
|
84
|
-
| Layer | Allowed | Forbidden |
|
|
85
|
-
|---|---|---|
|
|
86
|
-
| **Frontend** | User JWT tokens, public config, UI logic | API keys, DB credentials, internal service URLs |
|
|
87
|
-
| **Backend** | All secrets, third-party API calls, DB queries | Returning raw sensitive data without filtering |
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## 3. GOLDEN RULE — Input Validation & Sanitization
|
|
92
|
-
|
|
93
|
-
> **Never trust user input. Always validate on the backend.**
|
|
94
|
-
|
|
95
|
-
Frontend validation is for UX only. A malicious user can bypass it entirely. Every piece of data entering your backend must be validated for **type, size, and format** before use.
|
|
96
|
-
|
|
97
|
-
### SQL Injection Prevention
|
|
98
|
-
Always use **Prepared Statements** or ORM parameterized queries. Never interpolate user input into SQL strings.
|
|
99
|
-
|
|
100
|
-
```python
|
|
101
|
-
# WRONG — SQL Injection vulnerability
|
|
102
|
-
query = f"SELECT * FROM users WHERE email = '{user_email}'"
|
|
103
|
-
|
|
104
|
-
# CORRECT — Parameterized query
|
|
105
|
-
query = "SELECT * FROM users WHERE email = $1"
|
|
106
|
-
result = await db.fetch(query, user_email)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### XSS Prevention
|
|
110
|
-
- Use template engines with **auto-escaping** enabled (React does this by default for JSX).
|
|
111
|
-
- Never use `dangerouslySetInnerHTML` with unescaped user content.
|
|
112
|
-
- Sanitize HTML input with a library like `DOMPurify` if rich text is required.
|
|
113
|
-
|
|
114
|
-
### Input Schema Validation (Backend)
|
|
115
|
-
Validate every request body with a schema library before processing:
|
|
116
|
-
|
|
117
|
-
```python
|
|
118
|
-
# FastAPI + Pydantic — automatic type and size validation
|
|
119
|
-
from pydantic import BaseModel, EmailStr, constr
|
|
120
|
-
|
|
121
|
-
class CreateUserRequest(BaseModel):
|
|
122
|
-
name: constr(min_length=2, max_length=100)
|
|
123
|
-
email: EmailStr
|
|
124
|
-
password: constr(min_length=8, max_length=128)
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
// Node.js + Zod
|
|
129
|
-
import { z } from 'zod';
|
|
130
|
-
|
|
131
|
-
const schema = z.object({
|
|
132
|
-
name: z.string().min(2).max(100),
|
|
133
|
-
email: z.string().email(),
|
|
134
|
-
password: z.string().min(8).max(128),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const data = schema.parse(req.body); // throws if invalid
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### IDOR (Insecure Direct Object Reference) Prevention
|
|
141
|
-
Never trust the resource ID from the request alone. Always verify ownership:
|
|
142
|
-
|
|
143
|
-
```sql
|
|
144
|
-
-- WRONG: assumes the user owns order 500
|
|
145
|
-
SELECT * FROM orders WHERE id = $1;
|
|
146
|
-
|
|
147
|
-
-- CORRECT: validates ownership using the authenticated user's ID from the session
|
|
148
|
-
SELECT * FROM orders WHERE id = $1 AND customer_id = $2;
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## 4. Secrets & Environment Variables
|
|
154
|
-
|
|
155
|
-
### Zero Hardcoding Rule
|
|
156
|
-
No secret must **ever** appear in source code or be committed to Git.
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# WRONG — hardcoded secret in code
|
|
160
|
-
DATABASE_URL = "postgresql://user:SuperSecret123@db:5432/mydb"
|
|
161
|
-
API_KEY = "sk-abc123..."
|
|
162
|
-
|
|
163
|
-
# CORRECT — read from environment
|
|
164
|
-
import os
|
|
165
|
-
DATABASE_URL = os.getenv("DATABASE_URL")
|
|
166
|
-
API_KEY = os.getenv("API_KEY")
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### .env File Rules
|
|
170
|
-
- **Commit `.env.example`** (with placeholder values) — never the actual `.env`.
|
|
171
|
-
- Add `.env` to `.gitignore` on day one of the project.
|
|
172
|
-
- On Ilumin Cloud, configure secrets as environment variables in the compose file using the `${VAR_NAME}` syntax.
|
|
173
|
-
|
|
174
|
-
```yaml
|
|
175
|
-
# Correct use in docker-compose.yml for Ilumin Cloud
|
|
176
|
-
environment:
|
|
177
|
-
DATABASE_URL: ${DATABASE_URL}
|
|
178
|
-
SECRET_KEY: ${SECRET_KEY}
|
|
179
|
-
API_KEY: ${API_KEY}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Git Leak Prevention
|
|
183
|
-
Before any `git push`, verify no secrets were accidentally staged:
|
|
184
|
-
- Use `git diff --staged` to review what's being committed.
|
|
185
|
-
- Optionally install `gitleaks` or `trufflehog` as a pre-commit hook.
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## 5. Dependency Hygiene
|
|
190
|
-
|
|
191
|
-
Outdated dependencies are a common attack vector. Old versions frequently contain known CVEs (Common Vulnerabilities and Exposures).
|
|
192
|
-
|
|
193
|
-
### Regular Audit Commands
|
|
194
|
-
Run these before every production deployment:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
# Node.js / npm
|
|
198
|
-
npm audit
|
|
199
|
-
npm audit fix
|
|
200
|
-
|
|
201
|
-
# Python
|
|
202
|
-
pip-audit
|
|
203
|
-
# or
|
|
204
|
-
safety check -r requirements.txt
|
|
205
|
-
|
|
206
|
-
# Check for outdated packages
|
|
207
|
-
npm outdated
|
|
208
|
-
pip list --outdated
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Lock Files
|
|
212
|
-
Always commit your lock file to guarantee reproducible and secure builds:
|
|
213
|
-
- `package-lock.json` or `yarn.lock` for Node.js
|
|
214
|
-
- `poetry.lock` or `requirements.txt` with pinned versions for Python
|
|
215
|
-
|
|
216
|
-
```txt
|
|
217
|
-
# Pin exact versions in requirements.txt
|
|
218
|
-
fastapi==0.111.0
|
|
219
|
-
pydantic==2.7.1
|
|
220
|
-
httpx==0.27.0
|
|
221
|
-
|
|
222
|
-
# Avoid unpinned versions in production
|
|
223
|
-
fastapi>=0.100
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## 6. Audit Logging
|
|
229
|
-
|
|
230
|
-
Log security-critical events to enable forensic analysis in case of an incident:
|
|
231
|
-
|
|
232
|
-
| Event | Must be logged |
|
|
233
|
-
|---|---|
|
|
234
|
-
| Failed login attempts | Yes |
|
|
235
|
-
| Successful logins | Yes |
|
|
236
|
-
| Permission/role changes | Yes |
|
|
237
|
-
| Access to sensitive data (PII) | Yes |
|
|
238
|
-
| Validation errors that look like attacks | Yes |
|
|
239
|
-
| Password resets | Yes |
|
|
240
|
-
|
|
241
|
-
> **WARNING:** Never log passwords, full credit card numbers, or complete authentication tokens. Log only partial data (e.g., last 4 digits, masked email).
|
|
242
|
-
|
|
243
|
-
```python
|
|
244
|
-
# Safe logging example
|
|
245
|
-
import logging
|
|
246
|
-
|
|
247
|
-
logger = logging.getLogger("security")
|
|
248
|
-
|
|
249
|
-
def on_failed_login(email: str, ip: str):
|
|
250
|
-
masked_email = email[:3] + "***@" + email.split("@")[-1]
|
|
251
|
-
logger.warning(f"Failed login attempt | email={masked_email} | ip={ip}")
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## 7. Access Control (RBAC)
|
|
257
|
-
|
|
258
|
-
Apply the **Principle of Least Privilege**: users should only access what they need for their role.
|
|
259
|
-
|
|
260
|
-
- Define roles explicitly: `admin`, `editor`, `viewer`, etc.
|
|
261
|
-
- Enforce role checks **on the backend**, never just on frontend route guards.
|
|
262
|
-
- For SaaS/multi-tenant apps: always scope queries to the authenticated user's `tenant_id` or `organization_id` — never return cross-tenant data.
|
|
263
|
-
|
|
264
|
-
```python
|
|
265
|
-
# Role check middleware example
|
|
266
|
-
def require_role(required_role: str):
|
|
267
|
-
def decorator(user = Depends(get_current_user)):
|
|
268
|
-
if user.role != required_role:
|
|
269
|
-
raise HTTPException(status_code=403, detail="Forbidden")
|
|
270
|
-
return user
|
|
271
|
-
return decorator
|
|
272
|
-
|
|
273
|
-
@app.delete("/api/users/{id}", dependencies=[Depends(require_role("admin"))])
|
|
274
|
-
def delete_user(id: int): ...
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## 8. Rate Limiting & Abuse Prevention (Frontend + Backend)
|
|
280
|
-
|
|
281
|
-
To prevent users from creating automations (like malicious scripts or spamming clicks) that overwhelm the database—such as creating 500 rows per second and overloading the CPU and storage—you **MUST** implement rate limiting at both layers:
|
|
282
|
-
|
|
283
|
-
### Frontend Defenses
|
|
284
|
-
While frontend protections can be bypassed by advanced users, they prevent accidental spam and simple UI-based automations:
|
|
285
|
-
- **Disable Buttons on Submit:** Always disable the submit button and show a loading state while a request is in flight.
|
|
286
|
-
- **Debounce / Throttle:** Use debouncing for rapid events (like search inputs) to prevent firing an API call on every keystroke.
|
|
287
|
-
- **Cooldowns:** For specific actions (e.g., "Send SMS", "Generate Image"), implement a visual cooldown timer.
|
|
288
|
-
|
|
289
|
-
```tsx
|
|
290
|
-
// Correct — Disable button while loading to prevent double-clicks / spam
|
|
291
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
292
|
-
|
|
293
|
-
const handleSubmit = async () => {
|
|
294
|
-
if (isLoading) return;
|
|
295
|
-
setIsLoading(true);
|
|
296
|
-
try {
|
|
297
|
-
await api.post('/data', payload);
|
|
298
|
-
} finally {
|
|
299
|
-
setIsLoading(false);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return (
|
|
304
|
-
<button disabled={isLoading} onClick={handleSubmit}>
|
|
305
|
-
{isLoading ? 'Processing...' : 'Submit'}
|
|
306
|
-
</button>
|
|
307
|
-
);
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Backend Rate Limiting (The Real Shield)
|
|
311
|
-
Since frontend limits can be bypassed via direct API calls (e.g., cURL, Postman), the backend **MUST** enforce the ultimate rate limit per IP or User ID.
|
|
312
|
-
- Apply strict limits to all `POST`, `PUT`, `PATCH`, and `DELETE` endpoints.
|
|
313
|
-
- E.g., Limit data creation (posts, comments) to a reasonable amount per minute to protect database IO and CPU.
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## Pre-Deployment Security Checklist
|
|
318
|
-
|
|
319
|
-
Run through every item before a production deploy:
|
|
320
|
-
|
|
321
|
-
### Authentication & Authorization
|
|
322
|
-
- [ ] All protected routes require a valid Bearer token
|
|
323
|
-
- [ ] JWT expiration is set (recommended: ≤ 24h for access tokens)
|
|
324
|
-
- [ ] Passwords are hashed with BCrypt/Argon2 or pgCrypto — never stored plain
|
|
325
|
-
- [ ] Session cookies have `HttpOnly`, `Secure`, and `SameSite` flags set
|
|
326
|
-
|
|
327
|
-
### Frontend/Backend Separation
|
|
328
|
-
- [ ] No API keys or secrets exist in frontend code
|
|
329
|
-
- [ ] All sensitive third-party calls are proxied through the backend
|
|
330
|
-
- [ ] Frontend only stores the user's own JWT — nothing else sensitive
|
|
331
|
-
|
|
332
|
-
### Input Validation (Golden Rule)
|
|
333
|
-
- [ ] All request bodies are validated with a schema (Pydantic, Zod, Joi, etc.)
|
|
334
|
-
- [ ] All database queries use parameterized statements / ORM methods
|
|
335
|
-
- [ ] Ownership is verified for every resource access (IDOR prevention)
|
|
336
|
-
- [ ] All user-generated content rendered in HTML is escaped
|
|
337
|
-
|
|
338
|
-
### Rate Limiting & Abuse Prevention
|
|
339
|
-
- [ ] Frontend UI disables buttons/inputs while requests are pending
|
|
340
|
-
- [ ] Backend strictly implements rate limits on mutation endpoints (`POST`, `PUT`, `DELETE`) to avoid database stuffing
|
|
341
|
-
|
|
342
|
-
### Secrets & Environment
|
|
343
|
-
- [ ] No hardcoded secrets in source code
|
|
344
|
-
- [ ] `.env` is in `.gitignore` and not committed
|
|
345
|
-
- [ ] `.env.example` exists with placeholder values for team reference
|
|
346
|
-
- [ ] All production secrets are configured as environment variables
|
|
347
|
-
|
|
348
|
-
### Dependencies
|
|
349
|
-
- [ ] `npm audit` / `pip-audit` ran with no critical vulnerabilities
|
|
350
|
-
- [ ] Lock file is committed and up to date
|
|
351
|
-
- [ ] No dependency is significantly outdated with known CVEs
|
|
352
|
-
|
|
353
|
-
### Logging
|
|
354
|
-
- [ ] Failed login events are being logged
|
|
355
|
-
- [ ] No sensitive data (passwords, tokens, card numbers) appears in logs
|