package templates import ( "bytes" "text/template" ) var serviceTemplate = `[Unit] Description={{.Name}} After=network.target [Service] Type=simple User={{.User}} WorkingDirectory={{.WorkDir}} EnvironmentFile={{.EnvFile}} ExecStart={{.BinaryPath}} {{.Args}} Restart=always RestartSec=5s NoNewPrivileges=true PrivateTmp=true {{- if .Memory}} MemoryMax={{.Memory}} {{- end}} {{- if .CPU}} CPUQuota={{.CPU}} {{- end}} [Install] WantedBy=multi-user.target ` var appCaddyTemplate = `{{.Domain}} { reverse_proxy 127.0.0.1:{{.Port}} } ` var staticCaddyTemplate = `{{.Domain}} { root * {{.RootDir}} file_server encode gzip } ` // SystemdService generates a systemd service unit file func SystemdService(data map[string]string) (string, error) { tmpl, err := template.New("service").Parse(serviceTemplate) if err != nil { return "", err } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil } // AppCaddy generates a Caddy config for a Go app func AppCaddy(data map[string]string) (string, error) { tmpl, err := template.New("caddy").Parse(appCaddyTemplate) if err != nil { return "", err } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil } // StaticCaddy generates a Caddy config for a static site func StaticCaddy(data map[string]string) (string, error) { tmpl, err := template.New("caddy").Parse(staticCaddyTemplate) if err != nil { return "", err } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil } var postReceiveHookTemplate = `#!/bin/bash set -euo pipefail REPO=/srv/git/{{.Name}}.git SRC=/var/lib/{{.Name}}/src NAME={{.Name}} while read oldrev newrev refname; do branch=$(git rev-parse --symbolic --abbrev-ref "$refname") [ "$branch" = "main" ] || { echo "Pushed to $branch, skipping deploy."; exit 0; } done # Ensure checkout directory exists sudo /bin/mkdir -p "$SRC" sudo /bin/chown -R git:git "/var/lib/${NAME}" echo "==> Checking out code..." git --work-tree="$SRC" --git-dir="$REPO" checkout -f main cd "$SRC" # If no Dockerfile, nothing to deploy if [ ! -f Dockerfile ]; then echo "No Dockerfile found, skipping deploy." exit 0 fi # Install deployment config from repo (using full paths for sudoers) if [ -f "$SRC/.ship/service" ]; then echo "==> Installing systemd unit..." sudo /bin/cp "$SRC/.ship/service" "/etc/systemd/system/${NAME}.service" sudo systemctl daemon-reload fi if [ -f "$SRC/.ship/Caddyfile" ]; then echo "==> Installing Caddy config..." sudo /bin/cp "$SRC/.ship/Caddyfile" "/etc/caddy/sites-enabled/${NAME}.caddy" sudo systemctl reload caddy fi # Ensure data directory exists sudo /bin/mkdir -p "/var/lib/${NAME}/data" sudo /bin/chown -R git:git "/var/lib/${NAME}/data" echo "==> Building Docker image..." docker build -t ${NAME}:latest . echo "==> Restarting service..." sudo systemctl restart ${NAME} echo "==> Deploy complete!" ` var postReceiveHookStaticTemplate = `#!/bin/bash set -euo pipefail REPO=/srv/git/{{.Name}}.git WEBROOT=/var/www/{{.Name}} NAME={{.Name}} while read oldrev newrev refname; do branch=$(git rev-parse --symbolic --abbrev-ref "$refname") [ "$branch" = "main" ] || { echo "Pushed to $branch, skipping deploy."; exit 0; } done echo "==> Deploying static site..." git --work-tree="$WEBROOT" --git-dir="$REPO" checkout -f main if [ -f "$WEBROOT/.ship/Caddyfile" ]; then echo "==> Installing Caddy config..." sudo /bin/cp "$WEBROOT/.ship/Caddyfile" "/etc/caddy/sites-enabled/${NAME}.caddy" sudo systemctl reload caddy fi echo "==> Deploy complete!" ` var codeCaddyTemplate = `{{.BaseDomain}} { @goget query go-get=1 handle @goget { root * /opt/ship/vanity templates rewrite * /index.html file_server } @git path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack|git-receive-pack)$" handle @git { reverse_proxy unix//run/fcgiwrap.socket { transport fastcgi { env SCRIPT_FILENAME /usr/lib/git-core/git-http-backend env GIT_PROJECT_ROOT /srv/git env REQUEST_METHOD {method} env QUERY_STRING {query} env PATH_INFO {path} } } } @cgitassets path /cgit/* handle @cgitassets { root * /usr/share/cgit uri strip_prefix /cgit file_server } handle { reverse_proxy unix//run/fcgiwrap.socket { transport fastcgi { env SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi env QUERY_STRING {query} env REQUEST_METHOD {method} env PATH_INFO {path} env HTTP_HOST {host} env SERVER_NAME {host} } } } } ` var dockerServiceTemplate = `[Unit] Description={{.Name}} After=network.target docker.service Requires=docker.service [Service] Type=simple ExecStartPre=-/usr/bin/docker rm -f {{.Name}} ExecStart=/usr/bin/docker run --rm --name {{.Name}} \ -p 127.0.0.1:{{.Port}}:{{.ContainerPort}} \ --env-file /etc/ship/env/{{.Name}}.env \ -v /var/lib/{{.Name}}/data:/data \ {{.Name}}:latest ExecStop=/usr/bin/docker stop -t 10 {{.Name}} Restart=always RestartSec=5s [Install] WantedBy=multi-user.target ` var defaultAppCaddyTemplate = `{{.Domain}} { reverse_proxy 127.0.0.1:{{.Port}} } ` var defaultStaticCaddyTemplate = `{{.Domain}} { root * /var/www/{{.Name}} file_server encode gzip } ` // PostReceiveHook generates a post-receive hook for git-app repos func PostReceiveHook(data map[string]string) (string, error) { return renderTemplate("post-receive", postReceiveHookTemplate, data) } // PostReceiveHookStatic generates a post-receive hook for git-static repos func PostReceiveHookStatic(data map[string]string) (string, error) { return renderTemplate("post-receive-static", postReceiveHookStaticTemplate, data) } // CodeCaddy generates the base domain Caddy config for vanity imports + git HTTP func CodeCaddy(data map[string]string) (string, error) { return renderTemplate("code-caddy", codeCaddyTemplate, data) } var cgitrcTemplate = `virtual-root=/ css=/cgit/cgit.css logo=/cgit/cgit.png header=/opt/ship/cgit-header.html scan-path=/srv/git/ export-ok=git-daemon-export-ok enable-http-clone=0 clone-url=https://{{.BaseDomain}}/$CGIT_REPO_URL root-title={{.BaseDomain}} root-desc= remove-suffix=.git ` var cgitHeaderTemplate = ` ` // CgitRC generates the /etc/cgitrc config file func CgitRC(data map[string]string) (string, error) { return renderTemplate("cgitrc", cgitrcTemplate, data) } // CgitHeader generates the cgit header HTML file (dark theme) func CgitHeader() string { return cgitHeaderTemplate } // DockerService generates a systemd unit for a Docker-based app func DockerService(data map[string]string) (string, error) { return renderTemplate("docker-service", dockerServiceTemplate, data) } // DefaultAppCaddy generates a default Caddyfile for a git-app func DefaultAppCaddy(data map[string]string) (string, error) { return renderTemplate("default-app-caddy", defaultAppCaddyTemplate, data) } // DefaultStaticCaddy generates a default Caddyfile for a git-static site func DefaultStaticCaddy(data map[string]string) (string, error) { return renderTemplate("default-static-caddy", defaultStaticCaddyTemplate, data) } func renderTemplate(name, tmplStr string, data map[string]string) (string, error) { tmpl, err := template.New(name).Parse(tmplStr) if err != nil { return "", err } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil }