summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-14 07:56:22 -0800
committerbndw <ben@bdw.to>2026-02-14 07:56:22 -0800
commitf0dfabe5b7f1f8d23169c6e62a2f0c27bd6c5463 (patch)
treee1f3b1f32b4f810cf957fef53d8fcec4d1fead18
parentf5b667c80e49117c94481d49c5b0c77dbcf2804a (diff)
Add cgit web interface for browsing repos
Adds cgit as a web frontend for browsing git repositories. Visiting the base domain now shows a cgit repo index with trees, commits, diffs, and blame views. Public repos (marked with git-daemon-export-ok) are browsable and cloneable over HTTPS. - Install cgit during host init - Configure cgit with dark theme and base domain integration - Add cgit CGI handler to base domain Caddyfile - Update README to emphasize git-centric workflow with cgit frontend
-rw-r--r--README.md202
-rw-r--r--SECURITY.md2
-rw-r--r--cmd/ship/host/init.go24
-rw-r--r--internal/templates/templates.go91
4 files changed, 151 insertions, 168 deletions
diff --git a/README.md b/README.md
index 54be72f..cdd4127 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
1# Ship 1# Ship
2 2
3Ship deploys apps and static sites to a VPS over SSH. It handles HTTPS certificates, port allocation, systemd services, and reverse proxying — all with zero dependencies beyond SSH access. 3Ship turns a VPS into a self-hosted git server with a web frontend — similar to running cgit on your own domain. Visiting your base domain in a browser shows a cgit repo index; clicking through shows trees, commit logs, diffs, and blame. Public repos are cloneable over HTTPS. If you host Go code, `go get` works with your domain out of the box. This is the read-only, public-facing side, and it works exactly the way cgit users expect.
4 4
5There are two deployment modes: 5The difference is what happens on the write side. When you `git push` over SSH, Ship doesn't just update the bare repo — it builds and deploys your code. A post-receive hook checks out the repo, runs `docker build`, installs a systemd service and Caddy reverse-proxy config, and restarts the app. Your deployment config (the systemd unit, the Caddyfile) lives in `.ship/` in your repo and is versioned alongside your code. Push to main and it's live; push to any other branch and nothing happens.
6 6
7- **Git push** — push to a bare repo on the VPS, which triggers a Docker build and deploy via post-receive hooks. Deployment config (systemd unit, Caddyfile) lives in `.ship/` in your repo and is versioned alongside code. 7Not every repo needs to be a running service. If there's no Dockerfile, the push is accepted but the deploy step is skipped — the repo just sits there as a browsable, cloneable library. This makes Ship useful for Go modules that only need vanity imports and a public source view, alongside apps that need the full build-and-deploy pipeline. The same base domain serves both.
8- **Direct** — SCP a pre-built binary or rsync a static directory to the VPS. Ship generates and installs the systemd unit and Caddy config on your behalf.
9 8
10If a base domain is configured, Ship also serves **Go vanity imports** and **git HTTPS cloning** from the same domain, so `go get yourdomain.com/foo` works with zero extra setup. 9Ship also supports direct deploys (SCP a binary or rsync a static directory) for cases where git push isn't the right fit.
10
11Ship is a client-side CLI. All state lives on your laptop at `~/.config/ship/state.json`. The VPS is configured entirely over SSH — no agent or daemon runs on the server.
11 12
12## Install 13## Install
13 14
@@ -29,13 +30,7 @@ go build -o ship ./cmd/ship
29ship host init --host user@your-vps --base-domain example.com 30ship host init --host user@your-vps --base-domain example.com
30``` 31```
31 32
32This installs Caddy, Docker, git, and fcgiwrap. It creates a `git` user for push access, configures sudoers for deploy hooks, sets up vanity import serving, and enables automatic HTTPS. The host becomes the default for subsequent commands. 33This installs Caddy, Docker, git, fcgiwrap, and cgit. It creates a `git` user for push access, configures sudoers for deploy hooks, and enables automatic HTTPS. The host becomes the default for subsequent commands.
33
34If you don't need git-push deploys or vanity imports, omit `--base-domain`:
35
36```
37ship host init --host user@your-vps
38```
39 34
40### 2. Deploy 35### 2. Deploy
41 36
@@ -43,18 +38,11 @@ ship host init --host user@your-vps
43 38
44``` 39```
45ship init myapp 40ship init myapp
46```
47
48This creates a bare git repo on the VPS, generates `.ship/Caddyfile` and `.ship/service` locally, initializes a local git repo if needed, and adds an `origin` remote.
49
50```
51git add .ship/ Dockerfile 41git add .ship/ Dockerfile
52git commit -m "initial deploy" 42git commit -m "initial deploy"
53git push origin main 43git push origin main
54``` 44```
55 45
56The post-receive hook checks out code, installs `.ship/` configs, runs `docker build`, and restarts the service. If no Dockerfile is present, the push is accepted but deploy is skipped — useful for Go modules and libraries that only need vanity imports.
57
58**Git push (static site):** 46**Git push (static site):**
59 47
60``` 48```
@@ -64,19 +52,26 @@ git commit -m "initial deploy"
64git push origin main 52git push origin main
65``` 53```
66 54
67**Direct (pre-built binary):** 55**Git push (library / Go module):**
68 56
69``` 57```
70GOOS=linux GOARCH=amd64 go build -o myapp 58ship init mylib --public
71ship --binary ./myapp --domain api.example.com 59git add .
60git commit -m "initial"
61git push origin main
72``` 62```
73 63
74**Direct (static site):** 64No Dockerfile, so nothing is deployed — the repo is just browsable and cloneable at `https://example.com/mylib`.
65
66**Direct (pre-built binary):**
75 67
76``` 68```
77ship --static --dir ./dist --domain example.com 69GOOS=linux GOARCH=amd64 go build -o myapp
70ship --binary ./myapp --domain api.example.com
78``` 71```
79 72
73You can version control `.ship/` or add it to `.gitignore` — it's your choice.
74
80## Commands 75## Commands
81 76
82### `ship init <name>` 77### `ship init <name>`
@@ -84,193 +79,76 @@ ship --static --dir ./dist --domain example.com
84Create a bare git repo on the VPS and generate local `.ship/` config files. 79Create a bare git repo on the VPS and generate local `.ship/` config files.
85 80
86``` 81```
87ship init myapp # Docker-based app 82ship init myapp # Docker-based app
88ship init mysite --static # static site 83ship init mysite --static # static site
89ship init myapp --domain custom.example.com # custom domain 84ship init myapp --domain custom.example.com # custom domain
90ship init mylib --public # publicly cloneable (for go get) 85ship init mylib --public # publicly cloneable (for go get)
91``` 86```
92 87
93Flags:
94- `--static` — initialize as a static site instead of a Docker app
95- `--public` — make the repo publicly cloneable over HTTPS
96- `--domain` — custom domain (default: `name.basedomain`)
97
98### `ship deploy <name>` 88### `ship deploy <name>`
99 89
100Manually rebuild and deploy a git-deployed app. Runs the same steps as the post-receive hook: checkout, install configs, docker build, restart. 90Manually rebuild and deploy a git-deployed app.
101
102```
103ship deploy myapp
104```
105 91
106### `ship [deploy flags]` 92### `ship [deploy flags]`
107 93
108Deploy a pre-built binary or static directory directly. 94Deploy a pre-built binary or static directory directly.
109 95
110``` 96```
111# App
112ship --binary ./myapp --domain api.example.com 97ship --binary ./myapp --domain api.example.com
113ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost --env API_KEY=secret 98ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost
114ship --binary ./myapp --domain api.example.com --env-file .env.production
115ship --binary ./myapp --name myapi --memory 512M --cpu 50%
116
117# Static site
118ship --static --dir ./dist --domain example.com 99ship --static --dir ./dist --domain example.com
119 100ship --name myapi --memory 512M --cpu 50%
120# Config update (no binary, just change settings)
121ship --name myapi --memory 1G
122ship --name myapi --env DEBUG=true
123``` 101```
124 102
125Flags: 103Flags: `--binary`, `--static`, `--dir`, `--domain`, `--name`, `--env`, `--env-file`, `--args`, `--file`, `--memory`, `--cpu`
126- `--binary` — path to a compiled binary
127- `--static` — deploy as a static site
128- `--dir` — directory to deploy (default: `.`)
129- `--domain` — custom domain
130- `--name` — app name (default: inferred from binary or directory)
131- `--env KEY=VALUE` — environment variable (repeatable)
132- `--env-file` — path to a `.env` file
133- `--args` — arguments passed to the binary
134- `--file` — config file to upload to working directory (repeatable)
135- `--memory` — memory limit (e.g., `512M`, `1G`)
136- `--cpu` — CPU limit (e.g., `50%`, `200%` for 2 cores)
137 104
138### `ship list` 105### `ship list`
139 106
140List all deployments on the default host. 107List all deployments on the default host.
141 108
142``` 109### `ship status/logs/restart/remove <name>`
143NAME TYPE VISIBILITY DOMAIN PORT
144myapp git-app private myapp.example.com :8001
145mysite git-static public mysite.example.com
146api app api.example.com :8002
147```
148
149### `ship status <name>`
150
151Show systemd service status for an app.
152
153### `ship logs <name>`
154
155Show service logs (via journalctl).
156
157### `ship restart <name>`
158
159Restart an app's systemd service.
160
161### `ship remove <name>`
162 110
163Remove a deployment. Stops the service, removes files, configs, and state. 111Manage a deployment's systemd service.
164 112
165### `ship env` 113### `ship env`
166 114
167Manage environment variables for an app.
168
169``` 115```
170ship env list myapp # show env vars (secrets masked) 116ship env list myapp
171ship env set myapp KEY=VALUE # set variable(s) 117ship env set myapp KEY=VALUE
172ship env set myapp -f .env # load from file 118ship env unset myapp KEY
173ship env unset myapp KEY # remove a variable
174``` 119```
175 120
176### `ship host` 121### `ship host`
177 122
178Manage the VPS.
179
180``` 123```
181ship host init --host user@vps --base-domain example.com # one-time setup 124ship host init --host user@vps --base-domain example.com
182ship host status # uptime, disk, memory, load 125ship host status
183ship host update # apt update && upgrade 126ship host update
184ship host ssh # open an SSH session 127ship host ssh
185ship host set-domain example.com # change base domain
186``` 128```
187 129
188### `ship ui` 130### `ship ui`
189 131
190Launch a local web UI for viewing deployments. 132Launch a local web UI for viewing deployments.
191 133
192```
193ship ui # http://localhost:8080
194ship ui -p 3000 # custom port
195```
196
197### `ship version`
198
199Show version, commit, and build date.
200
201## How it works
202
203### Architecture
204
205Ship is a client-side CLI. All state lives on your laptop at `~/.config/ship/state.json`. The VPS is configured entirely over SSH — no agent or daemon runs on the server. This means the VPS is stateless and easily recreatable from local state.
206
207### Git push flow
208
2091. `ship init` creates a bare repo at `/srv/git/<name>.git` with a post-receive hook
2102. `git push` triggers the hook, which:
211 - Checks out code to `/var/lib/<name>/src`
212 - Copies `.ship/service` to `/etc/systemd/system/<name>.service`
213 - Copies `.ship/Caddyfile` to `/etc/caddy/sites-enabled/<name>.caddy`
214 - Runs `docker build` (skipped if no Dockerfile)
215 - Restarts the systemd service
2163. The Docker container runs with:
217 - Port bound to `127.0.0.1:<port>`
218 - Env vars from `/etc/ship/env/<name>.env`
219 - Persistent data volume at `/var/lib/<name>/data` (mounted as `/data`)
2204. Caddy reverse-proxies HTTPS traffic to the container
221
222### Direct deploy flow
223
2241. Binary uploaded via SCP to `/usr/local/bin/<name>`
2252. A dedicated system user is created
2263. Ship generates and installs a systemd unit and Caddy config
2274. Service is started, Caddy is reloaded
228
229### Vanity imports and git cloning
230
231When a base domain is configured, Ship sets up the base domain to serve:
232
233- **Go vanity imports** — `go get example.com/myapp` returns the correct `<meta go-import>` tag pointing to `https://example.com/myapp.git`
234- **Git HTTPS cloning** — `git clone https://example.com/myapp.git` works for public repos (those created with `--public`)
235
236This is handled by Caddy's `templates` directive and `fcgiwrap` + `git-http-backend`, with no custom server.
237
238### Port allocation
239
240Ports are allocated automatically starting from 8001 and never reused. You don't need to track which ports are in use.
241
242## VPS file layout 134## VPS file layout
243 135
244``` 136```
245/srv/git/<name>.git/ # bare git repos 137/srv/git/<name>.git/ # bare git repos
246/srv/git/<name>.git/hooks/post-receive # auto-deploy hook 138/srv/git/<name>.git/hooks/post-receive # auto-deploy hook
247
248/var/lib/<name>/src/ # checked-out source (for docker build) 139/var/lib/<name>/src/ # checked-out source (for docker build)
249/var/lib/<name>/data/ # persistent data volume 140/var/lib/<name>/data/ # persistent data volume
250/etc/systemd/system/<name>.service # systemd unit
251/etc/caddy/sites-enabled/<name>.caddy # Caddy config
252/etc/ship/env/<name>.env # environment variables
253
254/var/www/<name>/ # static site files 141/var/www/<name>/ # static site files
255 142/etc/systemd/system/<name>.service # systemd unit
256/usr/local/bin/<name> # direct-deployed binaries 143/etc/caddy/sites-enabled/<name>.caddy # per-app Caddy config
257
258/opt/ship/vanity/index.html # vanity import template
259/etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config 144/etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config
145/etc/cgitrc # cgit configuration
146/etc/ship/env/<name>.env # environment variables
260/etc/sudoers.d/ship-git # sudo rules for git user 147/etc/sudoers.d/ship-git # sudo rules for git user
148/opt/ship/vanity/index.html # vanity import template
261/home/git/.ssh/authorized_keys # SSH keys for git push 149/home/git/.ssh/authorized_keys # SSH keys for git push
262``` 150```
263 151
264## App requirements
265
266For direct-deployed Go apps, the binary must:
267
2681. Listen on HTTP (Caddy handles HTTPS)
2692. Read the port from the `PORT` environment variable or a `--port` flag
2703. Bind to `127.0.0.1` (not `0.0.0.0`)
271
272For git-deployed Docker apps, the Dockerfile should expose a service that listens on the port specified by the `PORT` environment variable.
273
274## Supported platforms 152## Supported platforms
275 153
276VPS: Ubuntu 20.04+ or Debian 11+ 154VPS: Ubuntu 20.04+ or Debian 11+
diff --git a/SECURITY.md b/SECURITY.md
index ad04094..2d7a96e 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -26,7 +26,7 @@ Git's `safe.directory` is set only for the `www-data` user (not system-wide), pr
26The `git` user is in the `docker` group, which is root-equivalent (can mount the host filesystem). Additionally, `.ship/service` files pushed via git are installed as systemd units. Anyone with SSH push access effectively has root. This is intentional for a single-user tool. 26The `git` user is in the `docker` group, which is root-equivalent (can mount the host filesystem). Additionally, `.ship/service` files pushed via git are installed as systemd units. Anyone with SSH push access effectively has root. This is intentional for a single-user tool.
27 27
28### Git repo visibility 28### Git repo visibility
29Repos are private by default (not cloneable over HTTPS). Use `ship init --public` to make a repo publicly cloneable. This is controlled by the `git-daemon-export-ok` marker file in each bare repo. Only public repos are accessible via `go get` or `git clone` over HTTPS. 29Repos are private by default (not cloneable over HTTPS). Use `ship init --public` to make a repo publicly cloneable. This is controlled by the `git-daemon-export-ok` marker file in each bare repo. Only public repos are accessible via `go get` or `git clone` over HTTPS. The cgit web interface respects the same model — it is configured with `export-ok=git-daemon-export-ok`, so only public repos are browsable.
30 30
31### User-controlled systemd units 31### User-controlled systemd units
32The `.ship/service` file in each repo is copied to `/etc/systemd/system/` on push. A malicious service file could run arbitrary commands as root. This is equivalent to the Docker access risk above. 32The `.ship/service` file in each repo is copied to `/etc/systemd/system/` on push. A malicious service file could run arbitrary commands as root. This is equivalent to the Docker access risk above.
diff --git a/cmd/ship/host/init.go b/cmd/ship/host/init.go
index 0ec573c..cfa2795 100644
--- a/cmd/ship/host/init.go
+++ b/cmd/ship/host/init.go
@@ -153,18 +153,19 @@ func setupGitDeploy(client *ssh.Client, baseDomain string, hostState *state.Host
153 } 153 }
154 fmt.Println(" Docker installed") 154 fmt.Println(" Docker installed")
155 155
156 fmt.Println("-> Installing git and fcgiwrap...") 156 fmt.Println("-> Installing git, fcgiwrap, and cgit...")
157 if _, err := client.RunSudo("apt-get install -y git fcgiwrap"); err != nil { 157 if _, err := client.RunSudo("apt-get install -y git fcgiwrap cgit"); err != nil {
158 return fmt.Errorf("error installing git/fcgiwrap: %w", err) 158 return fmt.Errorf("error installing git/fcgiwrap/cgit: %w", err)
159 } 159 }
160 // Allow git-http-backend (runs as www-data) to access repos owned by git. 160 // Allow git-http-backend (runs as www-data) to access repos owned by git.
161 // Scoped to www-data only, not system-wide, to preserve CVE-2022-24765 protection. 161 // Scoped to www-data only, not system-wide, to preserve CVE-2022-24765 protection.
162 // www-data's home is /var/www; ensure it can write .gitconfig there. 162 // www-data's home is /var/www; ensure it can write .gitconfig there.
163 client.RunSudo("mkdir -p /var/www")
163 client.RunSudo("chown www-data:www-data /var/www") 164 client.RunSudo("chown www-data:www-data /var/www")
164 if _, err := client.RunSudo("sudo -u www-data git config --global --add safe.directory '*'"); err != nil { 165 if _, err := client.RunSudo("sudo -u www-data git config --global --add safe.directory '*'"); err != nil {
165 return fmt.Errorf("error setting git safe.directory: %w", err) 166 return fmt.Errorf("error setting git safe.directory: %w", err)
166 } 167 }
167 fmt.Println(" git and fcgiwrap installed") 168 fmt.Println(" git, fcgiwrap, and cgit installed")
168 169
169 fmt.Println("-> Creating git user...") 170 fmt.Println("-> Creating git user...")
170 // Create git user (ignore error if already exists) 171 // Create git user (ignore error if already exists)
@@ -261,6 +262,21 @@ git ALL=(ALL) NOPASSWD: \
261 } 262 }
262 fmt.Println(" base domain Caddy config written") 263 fmt.Println(" base domain Caddy config written")
263 264
265 fmt.Println("-> Writing cgit config...")
266 cgitrcContent, err := templates.CgitRC(map[string]string{
267 "BaseDomain": baseDomain,
268 })
269 if err != nil {
270 return fmt.Errorf("error generating cgitrc: %w", err)
271 }
272 if err := client.WriteSudoFile("/etc/cgitrc", cgitrcContent); err != nil {
273 return fmt.Errorf("error writing cgitrc: %w", err)
274 }
275 if err := client.WriteSudoFile("/opt/ship/cgit-header.html", templates.CgitHeader()); err != nil {
276 return fmt.Errorf("error writing cgit header: %w", err)
277 }
278 fmt.Println(" cgit config written")
279
264 fmt.Println("-> Starting Docker and fcgiwrap...") 280 fmt.Println("-> Starting Docker and fcgiwrap...")
265 if _, err := client.RunSudo("systemctl enable docker fcgiwrap"); err != nil { 281 if _, err := client.RunSudo("systemctl enable docker fcgiwrap"); err != nil {
266 return fmt.Errorf("error enabling services: %w", err) 282 return fmt.Errorf("error enabling services: %w", err)
diff --git a/internal/templates/templates.go b/internal/templates/templates.go
index 8f25f8f..b68a504 100644
--- a/internal/templates/templates.go
+++ b/internal/templates/templates.go
@@ -185,8 +185,24 @@ var codeCaddyTemplate = `{{.BaseDomain}} {
185 } 185 }
186 } 186 }
187 187
188 @cgitassets path /cgit/*
189 handle @cgitassets {
190 root * /usr/share/cgit
191 uri strip_prefix /cgit
192 file_server
193 }
194
188 handle { 195 handle {
189 respond "not found" 404 196 reverse_proxy unix//run/fcgiwrap.socket {
197 transport fastcgi {
198 env SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi
199 env QUERY_STRING {query}
200 env REQUEST_METHOD {method}
201 env PATH_INFO {path}
202 env HTTP_HOST {host}
203 env SERVER_NAME {host}
204 }
205 }
190 } 206 }
191} 207}
192` 208`
@@ -239,6 +255,79 @@ func CodeCaddy(data map[string]string) (string, error) {
239 return renderTemplate("code-caddy", codeCaddyTemplate, data) 255 return renderTemplate("code-caddy", codeCaddyTemplate, data)
240} 256}
241 257
258var cgitrcTemplate = `virtual-root=/
259css=/cgit/cgit.css
260logo=/cgit/cgit.png
261header=/opt/ship/cgit-header.html
262scan-path=/srv/git/
263export-ok=git-daemon-export-ok
264enable-http-clone=0
265clone-url=https://{{.BaseDomain}}/$CGIT_REPO_URL
266root-title={{.BaseDomain}}
267root-desc=
268remove-suffix=.git
269`
270
271var cgitHeaderTemplate = `<style>
272body, table, td, th, div#cgit { background: #1a1a2e; color: #ccc; }
273a { color: #7aa2f7; }
274a:hover { color: #9ecbff; }
275table.list tr:hover td { background: #222244; }
276table.list td, table.list th { border-bottom: 1px solid #333; }
277th { background: #16213e; }
278td.commitgraph .column1 { color: #7aa2f7; }
279td.commitgraph .column2 { color: #9ece6a; }
280td.logheader { background: #16213e; }
281div#header { background: #16213e; border-bottom: 1px solid #333; }
282div#header .sub { color: #888; }
283table.tabs { border-bottom: 1px solid #333; }
284table.tabs td a { color: #ccc; }
285table.tabs td a.active { color: #fff; background: #1a1a2e; border: 1px solid #333; border-bottom: 1px solid #1a1a2e; }
286div.footer { color: #555; }
287div.footer a { color: #555; }
288div.diffstat-header { background: #16213e; }
289table.diffstat { border-bottom: 1px solid #333; }
290table.diffstat td.graph span.graph-moreremoved { background: #f7768e; }
291table.diffstat td.graph span.graph-moreadded { background: #9ece6a; }
292table.diffstat td.graph span.graph-removed { background: #f7768e; }
293table.diffstat td.graph span.graph-added { background: #9ece6a; }
294table.diff { background: #131320; border: 1px solid #333; }
295div.diff td { font-family: monospace; }
296div.head { color: #ccc; background: #16213e; padding: 2px 4px; }
297div.hunk { color: #7aa2f7; background: #1a1a3e; padding: 2px 4px; }
298div.add { color: #9ece6a; background: #1a2e1a; padding: 2px 4px; }
299div.del { color: #f7768e; background: #2e1a1a; padding: 2px 4px; }
300table.diff td.add { color: #9ece6a; background: #1a2e1a; }
301table.diff td.del { color: #f7768e; background: #2e1a1a; }
302table.diff td.hunk { color: #7aa2f7; background: #1a1a3e; }
303table.diff td { border: none; background: #1a1a2e; }
304table.blob td.lines { color: #ccc; }
305table.blob td.linenumbers { background: #16213e; }
306table.blob td.linenumbers a { color: #555; }
307table.blob td.linenumbers a:hover { color: #7aa2f7; }
308table.ssdiff td.add { color: #9ece6a; background: #1a2e1a; }
309table.ssdiff td.del { color: #f7768e; background: #2e1a1a; }
310table.ssdiff td { border-right: 1px solid #333; }
311table.ssdiff td.hunk { color: #7aa2f7; background: #1a1a3e; }
312table.ssdiff td.head { background: #16213e; border-bottom: 1px solid #333; }
313table.ssdiff td.foot { background: #16213e; border-top: 1px solid #333; }
314table.ssdiff td.lineno { background: #16213e; color: #555; }
315pre { color: #ccc; }
316input, textarea, select { background: #222; color: #ccc; border: 1px solid #444; }
317img#logo { display: none; }
318</style>
319`
320
321// CgitRC generates the /etc/cgitrc config file
322func CgitRC(data map[string]string) (string, error) {
323 return renderTemplate("cgitrc", cgitrcTemplate, data)
324}
325
326// CgitHeader generates the cgit header HTML file (dark theme)
327func CgitHeader() string {
328 return cgitHeaderTemplate
329}
330
242// DockerService generates a systemd unit for a Docker-based app 331// DockerService generates a systemd unit for a Docker-based app
243func DockerService(data map[string]string) (string, error) { 332func DockerService(data map[string]string) (string, error) {
244 return renderTemplate("docker-service", dockerServiceTemplate, data) 333 return renderTemplate("docker-service", dockerServiceTemplate, data)