SkillAgentSearch skills...

Shifu

Shifu is a simple Git-based content management system (CMS) and framework using the Go template syntax.

Install / Use

/learn @emvi/Shifu
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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 directory
  • shifu init <path> will initialize a new project in the given directory
  • shifu version will print the version number of Shifu
  • shifu pull will pull changed static and content files from a remote server if configured
  • shifu push will push changed static and content files 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
  • Args are 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

View on GitHub
GitHub Stars6
CategoryContent
Updated12d ago
Forks1

Languages

JavaScript

Security Score

85/100

Audited on Mar 18, 2026

No findings