Codync
무료 오픈소스 1:1 대안, Grok Bot과 Muse 대신
오픈소스 Go 기반 AI 친화형 CMS, 커널·확장 구조, MCP 구동, 50ms 이하 공개 렌더, GPL‑3.0, 추적 없음
Open-source, Go-based, AI-native CMS. Kernel + extension architecture, MCP-driven, sub-50ms public renders. GPL-3.0, no telemetry.
개발 도구HTML · GoGitHub @erikkubica온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기High-performance, AI-native CMS built in Go. Kernel + extension architecture. MCP-first.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗High-performance, AI-native CMS built in Go. Kernel + extension architecture. MCP-first.
Squilla is a content management system designed around one idea: an AI should be able to build and manage an entire website without human intervention. The kernel provides content nodes, rendering, auth, and a powerful CoreAPI. Everything else (media, email, SEO, forms) is an extension — gRPC plugins with their own data, logic, and admin UI.
The key differentiator: every CMS operation is exposed as an MCP tool. An AI agent can create node types, seed content, activate themes, and manage extensions through a structured API — no filesystem access, no shell commands, no HTML scraping.
Core = Linux kernel (infrastructure only). Extensions = Debian packages (own their full stack). Admin SPA = browser shell (just loads extension micro-frontends). Themes = templates + scripts + assets (registered on activation, no restart needed).
| Layer | Technology |
|---|---|
| Language | Go 1.24+ |
| HTTP | Fiber (routing, middleware) |
| ORM | GORM (PostgreSQL 16+) |
| Database | PostgreSQL (JSONB, GIN indexes) |
| Admin UI | React + TypeScript (Vite, Tailwind, shadcn/ui) |
| Templates | Go html/template |
| Scripting | Tengo (sandboxed VM, core/* modules) |
| Plugins | HashiCorp go-plugin (gRPC, bidirectional) |
| Storage | Local disk (S3 planned) |
| Security | Capability-based permissions, Ed25519 license verification |
# Clone and run with Docker
git clone <repo-url> && cd squilla
docker compose up --build
# App runs at http://localhost:3000
# Admin at http://localhost:3000/admin
# Default login: [email protected] / changeme
| Variable | Default | Description |
|---|---|---|
DB_HOST | localhost | PostgreSQL host |
DB_PORT | 5432 | PostgreSQL port |
DB_USER | squilla | Database user |
DB_PASSWORD | squilla | Database password |
DB_NAME | squilla | Database name |
THEME_PATH | themes/default | Path to active theme |
APP_ENV | production | development disables template caching |
PORT | 3000 | HTTP port |
DATABASE_URL | (unset) | Optional: postgres://user:pass@host:port/db?sslmode=disable. Overrides individual DB_* vars when set. |
ADMIN_EMAIL | [email protected] | Email for the auto-seeded admin user (first boot only). |
ADMIN_PASSWORD | (unset) | If unset, a random password is generated on first boot and printed to the app logs once. Set this to skip the random one. |
SQUILLA_SECRET_KEY | (unset) | AES-256 master key for at-rest encryption of secret settings. Must be a base64 string that decodes to exactly 32 raw bytes. Generate with openssl rand -base64 32. If unset, secret settings are stored in plaintext (dev only); production startup refuses to boot. |
SESSION_SECRET | (unset) | Session cookie signing key. Any sufficiently random string. Generate with openssl rand -base64 48. Required in production. |
MONITOR_BEARER_TOKEN | (unset) | Bearer token for /api/v1/stats monitoring endpoint. Any opaque string. Required in production. |
Squilla ships a coolify-compose.yml for near-zero-config deployment:
coolify-compose.yml.app service (Coolify fills SERVICE_FQDN_APP and provisions TLS).SQUILLA_SECRET_KEY in the Environment Variables tab — see the gotcha below.Coolify auto-generates the database credentials, session secret, and monitor token via its SERVICE_* magic variables — those work out of the box. The first admin password is generated on first boot and printed to the app container logs once (search the logs for first-boot admin credentials). To pre-set credentials, add ADMIN_EMAIL and ADMIN_PASSWORD env vars to the app service before the first deploy.
SQUILLA_SECRET_KEY requires manual overrideCoolify's SERVICE_BASE64_<NAME> magic variable produces a 32-character base64 string, which decodes to 24 raw bytes. The Squilla secrets service requires exactly 32 raw bytes (AES-256 spec), so the auto-generated value is rejected on boot and the container crash-loops with:
secrets init failed: SQUILLA_SECRET_KEY must be 32 raw bytes (base64-encoded): got 24 bytes, want 32
Fix: before first deploy, in the Coolify Environment Variables tab, set:
SQUILLA_SECRET_KEY=<paste output of: openssl rand -base64 32>
That produces a 44-character base64 string (32 raw bytes after decode), which the secrets service accepts. Verify with:
echo -n "$SQUILLA_SECRET_KEY" | base64 -d | wc -c # must print: 32
The SESSION_SECRET and MONITOR_BEARER_TOKEN env vars are not length-constrained — Coolify's SERVICE_BASE64_64_* (48 raw bytes) values work as-is.
The pre-built image is published to ghcr.io/erikkubica/squilla:latest (multi-arch, amd64 + arm64).
HARD RULE: If disabling/removing an extension would leave dead code in core, that code belongs in the extension, not core.
Core provides:
Extensions own:
Single Go interface providing all CMS capabilities. Three adapters:
core/* modules) — for .tgo scriptsSee docs/extension_api.md for the full reference.
Squilla exposes ~75 MCP tools across 17 domains, all under the core.<domain>.<verb> namespace. This is how AI agents interact with the CMS. The tables below highlight the most-used tools per domain — call core.guide for a live decision tree, recipes, and a CMS state snapshot, or core.extension.standards / core.theme.standards for authoring rules.
| Tool | Description |
|---|---|
core.node.create | Create a content node |
core.node.update | Update a node by ID |
core.node.get | Fetch a node by numeric ID |
core.node.query | Search/list nodes with filters; returns {nodes, total} |
core.node.delete | Permanently delete a node (use update with status='draft' to unpublish without deleting) |
core.nodetype.create | Register a custom post type |
core.nodetype.list / .get / .update / .delete | Manage node type definitions |
core.render.node_preview | Preview rendered page HTML (no events, no view counts) |
core.render.block / .layout | Smoke-test a block or layout in isolation |
| Tool | Description |
|---|---|
core.taxonomy.create / .list / .get / .update / .delete | Manage taxonomy definitions |
core.term.create / .list / .get / .update / .delete | Manage taxonomy term rows |
| Tool | Description |
|---|---|
core.menu.create / .list / .get / .update / .delete / .upsert | Manage menus; upsert is idempotent and resolves page:"<slug>" items to NodeIDs |
core.settings.get / .list / .set | Site settings |
core.media.upload / .import_url / .get / .query / .delete | Media library |
core.files.store / .delete | Raw file storage (no DB record) |
| Tool | Description |
|---|---|
core.theme.list / .active / .get | Inspect themes |
core.theme.activate / .deactivate | Hot-swap themes (no app restart) |
core.theme.deploy | Ship a packaged theme (base64 ZIP) into themes/<slug>/ and optionally activate — atomic dir swap, no docker cp, no git push |
core.theme.rescan | Re-scan themes/ and upsert rows for any directory dropped on disk |
core.layout.list / .get / .create / .update / .delete / .detach / .reattach | Manage page layouts |
core.block_types.list / .get / .create / .update / .delete / .detach / .reattach | Manage content block types |
core.field_types.list | List built-in field types and their how_to guides |
| Tool | Description |
|---|---|
core.extension.list / .get | Inspect extensions (active/inactive) |
core.extension.activate / .deactivate | Hot activate/deactivate (no app restart; subprocess only) |
core.extension.deploy | Ship a packaged extension (base64 ZIP) into extensions/<slug>/ and optionally hot-activate — atomic dir swap, plugin binaries chmod'd, no docker cp, no git push |
core.extension.rescan | Re-scan extensions/ and upsert rows for any directory dropped on disk |
| Tool | Description |
|---|---|
core.user.get / .query | Read-only user lookup |
core.data.get / .query / .create / .update / .delete | Low-level table access (prefer typed tools when available) |
core.email.send | Dispatch via active email provider extension |
core.http.fetch | Outbound HTTP (capability-gated) |
core.event.emit | Emit a custom event onto the bus |
core.filter.apply | Run a registered filter chain against a value |
| Tool | Description |
|---|---|
core.guide | Decision tree + recipes + live CMS state snapshot |
core.theme.standards | Theme authoring standards (Rules 1.5/1.6, etc.) |
core.extension.standards | Extension authoring standards (manifest, capabilities, hot deploy) |
Create a trip booking site from scratch:
1. core.theme.list → find theme ID
2. core.theme.activate(id) → activates theme, seeds node types + content
3. core.node.query(node_type="trip") → verify trips were created
4. core.render.node_preview(id=<trip_id>) → check rendering
Add a new content type:
1. core.nodetype.create(slug="recipe", label="Recipe", field_schema=[...])
2. core.node.create(node_type="recipe", title="Pho Bo", fields_data={...})
3. core.render.node_preview(id=<recipe_id>) → verify
Deploy a theme built outside the primary repo:
1. (locally) zip -r mytheme.zip mytheme/ # contains theme.json
2. core.theme.deploy(body_base64=<base64(mytheme.zip)>, activate=true)
3. core.render.node_preview(id=<home_node>) → verify the new look
Deploy an extension built outside the primary repo:
1. (locally) build the gRPC plugin for the host's OS/arch (e.g. linux/amd64)
2. (locally) zip -r myext.zip myext/ # extension.json + bin/myext + admin-ui/dist
3. core.extension.deploy(body_base64=<base64(myext.zip)>, activate=true)
4. core.extension.get(slug="myext") → confirm is_active=true
cmd/squilla/ Application entry point
internal/ Core kernel
coreapi/ CoreAPI interface + adapters (Tengo, gRPC, internal)
cms/ Content service, theme loader, extension loader
scripting/ Tengo VM runtime
models/ GORM models
auth/ Session auth, RBAC
events/ Event bus
mcp/ MCP tool server (~75 tools across 17 domains)
extensions/ Feature extensions (see extensions/README.md)
themes/ Theme repository
admin-ui/ React SPA shell
proto/ Protocol Buffer definitions
storage/ Local file storage
Themes are self-contained packages: layouts, partials, blocks, assets, scripts, and page templates.
themes/my-theme/
theme.json Manifest (layouts, blocks, assets, templates)
layouts/ Page layouts (default.html, trip.html, etc.)
partials/ Reusable fragments (site-header.html, site-footer.html)
blocks/ Content blocks (my-hero/view.html + block.json)
assets/ Static files (images/, styles/, scripts/)
scripts/ Tengo scripts (theme.tengo entry point)
templates/ Pre-populated page JSON files
When core.theme.activate(id) is called (or POST /admin/api/themes/:id/activate):
The #1 source of theme bugs is mismatch between seed data shape and template access patterns.
Rule: The seed script (theme.tengo) defines the data contract. Templates must match.
Example bug: seed stores tag as a string, template accesses tag.name:
// theme.tengo seeds: tag: "Foodie"
// template uses: {{ with $fd.tag }}{{ .name }}{{ end }} ← CRASH
// fix: {{ with $fd.tag }}{{ . }}{{ end }} ← correct
.fields vs .fields_dataDifferent template contexts use different key names for node fields:
| Where you are | Access pattern | Example |
|---|---|---|
| Layout template (current node) | .node.fields | {{ .node.fields.color }} |
| Block template (block fields) | .fields or direct | {{ .heading }} |
| Tengo filter result (list_nodes) | .fields_data | {{ .fields_data.color }} |
docs/theming.md — Complete theming guide (1400+ lines)docs/scripting_api.md — Tengo scripting referenceextensions/README.md — Extension development guideExtensions are feature packages. Two flavors:
See extensions/README.md for the complete guide and docs/extension_api.md for the API reference.
content_node with blocks_data and fields_data JSONBsnake_case Go files, .html templates, .tgo Tengo scripts| Doc | Description |
|---|---|
CLAUDE.md | AI coding assistant context (architecture, conventions) |
docs/architecture.md | Canonical architectural reference |
docs/extension_api.md | Building extensions (manifests, gRPC plugins, capabilities) |
docs/scripting_api.md | Tengo core/* modules for theme + extension scripts |
docs/theming.md | Complete theming guide (layouts, partials, blocks, assets) |
docs/forms.md | Forms extension public API reference |
docs/vdus.md | Server-Driven UI (admin shell + layout trees + SSE) |
docs/core_dev_guide.md | Kernel development workflows |
docs/core_features.md | Exhaustive feature inventory |
docs/database-schema.md | Database schema reference (27 GORM models, 38 migrations) |
docs/security.md | Security posture and PR-time checklist |
extensions/README.md | Extension development guide (alternative entry to docs/extension_api.md) |
themes/README.md | Theme development guide (layouts, blocks, partials, Tengo seeding, forms wiring) |
GNU General Public License v3.0 — see LICENSE.
CORS_ORIGINS | (unset) | Comma-separated list of allowed origins for CORS. Defaults to the public domain in Coolify deployments. |