Shifu
Shifu is a simple Git-based content management system (CMS) and framework using the Go template syntax.
Install / Use
/learn @emvi/ShifuREADME
Shifu
Shifu is a simple Git-based content management system (CMS) and framework using the Go template syntax.
Content is managed in JSON files and can be automatically updated from Git, the file system, or a database. Templates are updated automatically and JavaScript/TypeScript and Sass can be compiled on the fly, allowing for fast local development. Shifu can also be used as a library in your Go application to add template functionality and custom behavior.
Features
- JSON-based content management
- Reference commonly used elements
- Support for i18n (translations)
- Serve static files
- Reusable Golang template files
- Build and minify JavaScript/TypeScript and Sass on save
- Static content cache
- Configurable 404 error fallback page
- Automatic sitemap generation
- Integrated analytics using Pirsch
- Track page views
- Track custom events
- Integrated A/B testing
- Custom Golang handlers for advanced functionality
- Simple configuration and easy deployment
- Standalone server or library
- Push/Pull files to/from a remote server
- Admin UI
Installation and Setup
Download the latest release for your platform from the releases section on GitHub.
Move the binary to a directory in your $PATH (like /usr/local/bin).
For Sass, you need to install the sass command globally (sudo npm i -g sass).
After that you can run Shifu from the command line with the shifu command.
shifu run <path>will run Shifu in the given directoryshifu init <path>will initialize a new project in the given directoryshifu versionwill print the version number of Shifushifu pullwill pull changedstaticandcontentfiles from a remote server if configuredshifu pushwill push changedstaticandcontentfiles to a remote server if configured
systemd sample configuration:
[Unit]
Description=Shifu Website
[Service]
Type=simple
ExecStart=/root/example.com/shifu
WorkingDirectory=/root/example.com
[Install]
WantedBy=multi-user.target
Configuration
Shifu is configured using a single config.json file inside the project's root directory. A .secrets.env file can be added to store secrets. Secrets are key-value pairs, one per line.
SECRET=value
You can then replace them inside config.json using ${SECRET}.
Below is the entire configuration. Keys starting with _ are comments.
{
"dev": false,
"_log_level": "debug, info, warn, error",
"log_level": "info",
"server": {
"_host": "Leave empty for production",
"host": "localhost",
"port": 8080,
"_shutdown_time": "Time before the server is forcefully shut down (optional).",
"shutdown_time": 30,
"_write_timeout": "Request write timeout.",
"write_timeout": 60,
"_read_timeout": "Request read timeout.",
"read_timeout": 60,
"_idle_timeout": "Request idle timeout.",
"idle_timeout": 60,
"tls_cert_file": "cert/file.pem",
"tls_key_file": "key/file.pem",
"hostname": "example.com",
"secure_cookies": true,
"cookie_domain_name": "example.com"
},
"_api": "Enables the API",
"api": {
"secret": "secret"
},
"_remote": "Configures a remote server for synchronization (push/pull) using the API secret",
"remote": {
"url": "https://example.com",
"secret": "secret"
},
"_git": "Pulls files from a Git repository regularly",
"git": {
"update_seconds": 600,
"repository": "https://..."
},
"content": {
"_provider": "git, fs",
"provider": "git",
"_not_found": "Overrides the default 404-page path (/404).",
"not_found": {
"en": "/not-found",
"de": "/de/nicht-gefunden"
}
},
"cors": {
"origins": "*",
"loglevel": "info"
},
"_sass": "Optional configuration to compile Sass.",
"sass": {
"_dir": "Asset directory path.",
"dir": "assets",
"_entrypoint": "Main sass file.",
"entrypoint": "style.scss",
"_out": "Compiled output CSS file path.",
"out": "static/style.css",
"_out_source_map": "CSS map file (optional).",
"out_source_map": "static/style.css.map",
"_watch": "Re-compile files when changed.",
"watch": true
},
"_js": "Optional configuration to compile js/ts (see Sass configuration for reference).",
"js": {
"dir": "assets",
"entrypoint": "entrypoint.js",
"out": "static/bundle.js",
"source_map": true,
"watch": true
},
"_analytics": "Optional analytics configuration.",
"analytics": {
"provider": "pirsch",
"_client_id": "Optional when using an access key (recommended) instead of oAuth.",
"client_id": "...",
"_client_secret": "Required.",
"client_secret": "...",
"_subnets": "Optional subnet configuration.",
"subnets": [
"10.1.0.0/16",
"10.2.0.0/8"
],
"_header": "Optional IP header configuration.",
"header": [
"X-Forwarded-For",
"Forwarded"
]
},
"ui": {
"_path": "The path where you can sign in to the admin UI.",
"path": "/admin",
"_admin_password": "The default admin user password.",
"admin_password": "${ADMIN_PASSWORD}"
}
}
Structuring Your Website
The directory structure is as follows:
| Directory | Description |
|------------|------------------------------------------------------|
| admin/tpl/ | The template configuration for the admin UI. |
| content/ | Recursive content files in JSON format. |
| static/ | Static content (will be served as is on /static/). |
| tpl/ | Recursive Golang template files. |
The JSON structure for a content file is as follows:
{
"_disable_cache": "Disables the static content cache (does not affect custom handlers).",
"disable_cache": false,
"_path": "A list of routes on which this page should be served. Routes support regular expressions and variables, such as /p/{var} or /p/{var:[0-9]+}. They are available as a string map called 'args'.",
"path": {
"_en": "Use /404 (default) as a special case serving the 404 not found page.",
"en": "/",
"de": "/de"
},
"sitemap": {
"_priority": "Default is 1.0.",
"priority": "1.0"
},
"_header": "Optional list of headers.",
"header": {
"X-Frame-Options": "deny"
},
"_handler": "Sets a custom handler defined on the backend.",
"handler": "custom_handler",
"_analytics": "Optional analytics meta data.",
"analytics": {
"tags": {
"key": "value"
},
"experiment": {
"_name": "A/B testing page variant.",
"name": "landing",
"variant": "a"
}
},
"content": {
"content": [
{
"_ref": "References to a standalone element (JSON file without extension, always lowercase).",
"ref": "head",
"data": {
"_": "Overwrites whatever is set in head.json."
},
"copy": {
"en": {
"_title": "Overrides the copy 'title' with the value 'Home'.",
"title": "Home"
}
}
},
{
"_tpl": "Template file (without extension, always lowercase).",
"tpl": "text",
"analytics": {
"experiment": {
"_name": "A/B testing experiment name and variant.",
"name": "experiment",
"variant": "a"
}
},
"_data": "Optional generic data object.",
"data": {
"numbers": [1, 2, 3]
},
"_copy": "Optional data used in the template.",
"copy": {
"en": {
"headline": "Welcome!",
"text": "To Shifu."
},
"de": {
"headline": "Willkommen!",
"text": "Bei Shifu."
}
},
"content": {
"children": [
{
"_": "..."
}
]
}
}
]
}
}
Standalone elements are use the same structure as pages, but do not specify paths. Here is an example:
{
"tpl": "head",
"data": {
"_": "..."
},
"copy": {
"en": {
"title": "Welcome to Shifu"
},
"de": {
"title": "Willkommen bei Shifu"
}
}
}
Custom handlers are implemented like this:
cms := shifu.NewCMS(cms.Options{
// ...
})
cms.SetHandler("blog", func(c *cms.CMS, page cms.Content, args map[string]string, w http.ResponseWriter, r *http.Request) {
// ...
c.RenderPage(w, r, strings.ToLower(r.URL.Path), args, &page)
})
Build-in Variables
When rendering a template, the following variables are always available. Click one of the links below to see the struct definitions.
CMS *CMS
Args map[string]string
Page *Content
Content *Content
- CMS is the CMS main struct that can be used to render elements and access general configuration
Argsare the router parameters when using dynamic route matching- Page is the page object, including the routes and other page attributes
- Content is the current element that is being rendered
Templa
Related Skills
docs-writer
99.6k`docs-writer` skill instructions As an expert technical writer and editor for the Gemini CLI project, you produce accurate, clear, and consistent documentation. When asked to write, edit, or revie
model-usage
341.8kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
ddd
Guía de Principios DDD para el Proyecto > 📚 Documento Complementario : Este documento define los principios y reglas de DDD. Para ver templates de código, ejemplos detallados y guías paso
arscontexta
2.9kClaude Code plugin that generates individualized knowledge systems from conversation. You describe how you think and work, have a conversation and get a complete second brain as markdown files you own.
