diff options
| author | bndw <ben@bdw.to> | 2026-02-08 12:32:59 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-08 12:32:59 -0800 |
| commit | a8ad8e934d15d2bf84f942414a89af1d2691adbc (patch) | |
| tree | 82e6765c9d35968b27ac7ee17f5c201a421dc1d3 /internal/templates/templates.go | |
| parent | af109c04a3edd4dcd4e7b16242052442fb4a3b24 (diff) | |
Add git-centric deployment with Docker builds and vanity imports
New deployment model where projects start with a git remote on the VPS.
Pushing to the remote triggers automatic docker build and deploy via
post-receive hooks. The base domain serves Go vanity imports and git
HTTPS cloning via Caddy + fcgiwrap.
- Add `ship init <name>` command to create bare repos and .ship/ config
- Add `ship deploy <name>` command for manual rebuilds
- Extend `ship host init --base-domain` to set up Docker, git user,
fcgiwrap, sudoers, and vanity import infrastructure
- Add git-app and git-static types alongside existing app and static
- Update remove, status, logs, restart, list, and config-update to
handle new types
Diffstat (limited to 'internal/templates/templates.go')
| -rw-r--r-- | internal/templates/templates.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/internal/templates/templates.go b/internal/templates/templates.go index ce1cbe5..8615117 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go | |||
| @@ -86,3 +86,171 @@ func StaticCaddy(data map[string]string) (string, error) { | |||
| 86 | 86 | ||
| 87 | return buf.String(), nil | 87 | return buf.String(), nil |
| 88 | } | 88 | } |
| 89 | |||
| 90 | var postReceiveHookTemplate = `#!/bin/bash | ||
| 91 | set -euo pipefail | ||
| 92 | |||
| 93 | REPO=/srv/git/{{.Name}}.git | ||
| 94 | SRC=/var/lib/{{.Name}}/src | ||
| 95 | NAME={{.Name}} | ||
| 96 | |||
| 97 | while read oldrev newrev refname; do | ||
| 98 | branch=$(git rev-parse --symbolic --abbrev-ref "$refname") | ||
| 99 | [ "$branch" = "main" ] || { echo "Pushed to $branch, skipping deploy."; exit 0; } | ||
| 100 | done | ||
| 101 | |||
| 102 | echo "==> Checking out code..." | ||
| 103 | git --work-tree="$SRC" --git-dir="$REPO" checkout -f main | ||
| 104 | |||
| 105 | cd "$SRC" | ||
| 106 | |||
| 107 | # Install deployment config from repo | ||
| 108 | if [ -f .ship/service ]; then | ||
| 109 | echo "==> Installing systemd unit..." | ||
| 110 | sudo cp .ship/service /etc/systemd/system/${NAME}.service | ||
| 111 | sudo systemctl daemon-reload | ||
| 112 | fi | ||
| 113 | if [ -f .ship/Caddyfile ]; then | ||
| 114 | echo "==> Installing Caddy config..." | ||
| 115 | sudo cp .ship/Caddyfile /etc/caddy/sites-enabled/${NAME}.caddy | ||
| 116 | sudo systemctl reload caddy | ||
| 117 | fi | ||
| 118 | |||
| 119 | echo "==> Building Docker image..." | ||
| 120 | docker build -t ${NAME}:latest . | ||
| 121 | |||
| 122 | echo "==> Restarting service..." | ||
| 123 | sudo systemctl restart ${NAME} | ||
| 124 | |||
| 125 | echo "==> Deploy complete!" | ||
| 126 | ` | ||
| 127 | |||
| 128 | var postReceiveHookStaticTemplate = `#!/bin/bash | ||
| 129 | set -euo pipefail | ||
| 130 | |||
| 131 | REPO=/srv/git/{{.Name}}.git | ||
| 132 | WEBROOT=/var/www/{{.Name}} | ||
| 133 | NAME={{.Name}} | ||
| 134 | |||
| 135 | while read oldrev newrev refname; do | ||
| 136 | branch=$(git rev-parse --symbolic --abbrev-ref "$refname") | ||
| 137 | [ "$branch" = "main" ] || { echo "Pushed to $branch, skipping deploy."; exit 0; } | ||
| 138 | done | ||
| 139 | |||
| 140 | echo "==> Deploying static site..." | ||
| 141 | git --work-tree="$WEBROOT" --git-dir="$REPO" checkout -f main | ||
| 142 | |||
| 143 | if [ -f "$WEBROOT/.ship/Caddyfile" ]; then | ||
| 144 | echo "==> Installing Caddy config..." | ||
| 145 | sudo cp "$WEBROOT/.ship/Caddyfile" /etc/caddy/sites-enabled/${NAME}.caddy | ||
| 146 | sudo systemctl reload caddy | ||
| 147 | fi | ||
| 148 | |||
| 149 | echo "==> Deploy complete!" | ||
| 150 | ` | ||
| 151 | |||
| 152 | var codeCaddyTemplate = `{{.BaseDomain}} { | ||
| 153 | @goget query go-get=1 | ||
| 154 | handle @goget { | ||
| 155 | root * /opt/ship/vanity | ||
| 156 | templates | ||
| 157 | rewrite * /index.html | ||
| 158 | file_server | ||
| 159 | } | ||
| 160 | |||
| 161 | @git path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack|git-receive-pack)$" | ||
| 162 | handle @git { | ||
| 163 | reverse_proxy unix//run/fcgiwrap.socket { | ||
| 164 | transport fastcgi { | ||
| 165 | env SCRIPT_FILENAME /usr/lib/git-core/git-http-backend | ||
| 166 | env GIT_PROJECT_ROOT /srv/git | ||
| 167 | env GIT_HTTP_EXPORT_ALL 1 | ||
| 168 | env REQUEST_METHOD {method} | ||
| 169 | env QUERY_STRING {query} | ||
| 170 | env PATH_INFO {path} | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | handle { | ||
| 176 | respond "not found" 404 | ||
| 177 | } | ||
| 178 | } | ||
| 179 | ` | ||
| 180 | |||
| 181 | var dockerServiceTemplate = `[Unit] | ||
| 182 | Description={{.Name}} | ||
| 183 | After=network.target docker.service | ||
| 184 | Requires=docker.service | ||
| 185 | |||
| 186 | [Service] | ||
| 187 | Type=simple | ||
| 188 | ExecStartPre=-/usr/bin/docker rm -f {{.Name}} | ||
| 189 | ExecStart=/usr/bin/docker run --rm --name {{.Name}} \ | ||
| 190 | -p 127.0.0.1:{{.Port}}:{{.Port}} \ | ||
| 191 | --env-file /etc/ship/env/{{.Name}}.env \ | ||
| 192 | -v /var/lib/{{.Name}}/data:/data \ | ||
| 193 | {{.Name}}:latest | ||
| 194 | ExecStop=/usr/bin/docker stop -t 10 {{.Name}} | ||
| 195 | Restart=always | ||
| 196 | RestartSec=5s | ||
| 197 | |||
| 198 | [Install] | ||
| 199 | WantedBy=multi-user.target | ||
| 200 | ` | ||
| 201 | |||
| 202 | var defaultAppCaddyTemplate = `{{.Domain}} { | ||
| 203 | reverse_proxy 127.0.0.1:{{.Port}} | ||
| 204 | } | ||
| 205 | ` | ||
| 206 | |||
| 207 | var defaultStaticCaddyTemplate = `{{.Domain}} { | ||
| 208 | root * /var/www/{{.Name}} | ||
| 209 | file_server | ||
| 210 | encode gzip | ||
| 211 | } | ||
| 212 | ` | ||
| 213 | |||
| 214 | // PostReceiveHook generates a post-receive hook for git-app repos | ||
| 215 | func PostReceiveHook(data map[string]string) (string, error) { | ||
| 216 | return renderTemplate("post-receive", postReceiveHookTemplate, data) | ||
| 217 | } | ||
| 218 | |||
| 219 | // PostReceiveHookStatic generates a post-receive hook for git-static repos | ||
| 220 | func PostReceiveHookStatic(data map[string]string) (string, error) { | ||
| 221 | return renderTemplate("post-receive-static", postReceiveHookStaticTemplate, data) | ||
| 222 | } | ||
| 223 | |||
| 224 | // CodeCaddy generates the base domain Caddy config for vanity imports + git HTTP | ||
| 225 | func CodeCaddy(data map[string]string) (string, error) { | ||
| 226 | return renderTemplate("code-caddy", codeCaddyTemplate, data) | ||
| 227 | } | ||
| 228 | |||
| 229 | // DockerService generates a systemd unit for a Docker-based app | ||
| 230 | func DockerService(data map[string]string) (string, error) { | ||
| 231 | return renderTemplate("docker-service", dockerServiceTemplate, data) | ||
| 232 | } | ||
| 233 | |||
| 234 | // DefaultAppCaddy generates a default Caddyfile for a git-app | ||
| 235 | func DefaultAppCaddy(data map[string]string) (string, error) { | ||
| 236 | return renderTemplate("default-app-caddy", defaultAppCaddyTemplate, data) | ||
| 237 | } | ||
| 238 | |||
| 239 | // DefaultStaticCaddy generates a default Caddyfile for a git-static site | ||
| 240 | func DefaultStaticCaddy(data map[string]string) (string, error) { | ||
| 241 | return renderTemplate("default-static-caddy", defaultStaticCaddyTemplate, data) | ||
| 242 | } | ||
| 243 | |||
| 244 | func renderTemplate(name, tmplStr string, data map[string]string) (string, error) { | ||
| 245 | tmpl, err := template.New(name).Parse(tmplStr) | ||
| 246 | if err != nil { | ||
| 247 | return "", err | ||
| 248 | } | ||
| 249 | |||
| 250 | var buf bytes.Buffer | ||
| 251 | if err := tmpl.Execute(&buf, data); err != nil { | ||
| 252 | return "", err | ||
| 253 | } | ||
| 254 | |||
| 255 | return buf.String(), nil | ||
| 256 | } | ||
