diff options
| -rw-r--r-- | README.md | 202 | ||||
| -rw-r--r-- | SECURITY.md | 2 | ||||
| -rw-r--r-- | cmd/ship/host/init.go | 24 | ||||
| -rw-r--r-- | internal/templates/templates.go | 91 |
4 files changed, 151 insertions, 168 deletions
| @@ -1,13 +1,14 @@ | |||
| 1 | # Ship | 1 | # Ship |
| 2 | 2 | ||
| 3 | Ship 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. | 3 | Ship 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 | ||
| 5 | There are two deployment modes: | 5 | The 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. | 7 | Not 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 | ||
| 10 | If 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. | 9 | Ship also supports direct deploys (SCP a binary or rsync a static directory) for cases where git push isn't the right fit. |
| 10 | |||
| 11 | Ship 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 | |||
| 29 | ship host init --host user@your-vps --base-domain example.com | 30 | ship host init --host user@your-vps --base-domain example.com |
| 30 | ``` | 31 | ``` |
| 31 | 32 | ||
| 32 | This 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. | 33 | This 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 | |||
| 34 | If you don't need git-push deploys or vanity imports, omit `--base-domain`: | ||
| 35 | |||
| 36 | ``` | ||
| 37 | ship 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 | ``` |
| 45 | ship init myapp | 40 | ship init myapp |
| 46 | ``` | ||
| 47 | |||
| 48 | This 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 | ``` | ||
| 51 | git add .ship/ Dockerfile | 41 | git add .ship/ Dockerfile |
| 52 | git commit -m "initial deploy" | 42 | git commit -m "initial deploy" |
| 53 | git push origin main | 43 | git push origin main |
| 54 | ``` | 44 | ``` |
| 55 | 45 | ||
| 56 | The 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" | |||
| 64 | git push origin main | 52 | git push origin main |
| 65 | ``` | 53 | ``` |
| 66 | 54 | ||
| 67 | **Direct (pre-built binary):** | 55 | **Git push (library / Go module):** |
| 68 | 56 | ||
| 69 | ``` | 57 | ``` |
| 70 | GOOS=linux GOARCH=amd64 go build -o myapp | 58 | ship init mylib --public |
| 71 | ship --binary ./myapp --domain api.example.com | 59 | git add . |
| 60 | git commit -m "initial" | ||
| 61 | git push origin main | ||
| 72 | ``` | 62 | ``` |
| 73 | 63 | ||
| 74 | **Direct (static site):** | 64 | No 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 | ``` |
| 77 | ship --static --dir ./dist --domain example.com | 69 | GOOS=linux GOARCH=amd64 go build -o myapp |
| 70 | ship --binary ./myapp --domain api.example.com | ||
| 78 | ``` | 71 | ``` |
| 79 | 72 | ||
| 73 | You 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 | |||
| 84 | Create a bare git repo on the VPS and generate local `.ship/` config files. | 79 | Create a bare git repo on the VPS and generate local `.ship/` config files. |
| 85 | 80 | ||
| 86 | ``` | 81 | ``` |
| 87 | ship init myapp # Docker-based app | 82 | ship init myapp # Docker-based app |
| 88 | ship init mysite --static # static site | 83 | ship init mysite --static # static site |
| 89 | ship init myapp --domain custom.example.com # custom domain | 84 | ship init myapp --domain custom.example.com # custom domain |
| 90 | ship init mylib --public # publicly cloneable (for go get) | 85 | ship init mylib --public # publicly cloneable (for go get) |
| 91 | ``` | 86 | ``` |
| 92 | 87 | ||
| 93 | Flags: | ||
| 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 | ||
| 100 | Manually rebuild and deploy a git-deployed app. Runs the same steps as the post-receive hook: checkout, install configs, docker build, restart. | 90 | Manually rebuild and deploy a git-deployed app. |
| 101 | |||
| 102 | ``` | ||
| 103 | ship deploy myapp | ||
| 104 | ``` | ||
| 105 | 91 | ||
| 106 | ### `ship [deploy flags]` | 92 | ### `ship [deploy flags]` |
| 107 | 93 | ||
| 108 | Deploy a pre-built binary or static directory directly. | 94 | Deploy a pre-built binary or static directory directly. |
| 109 | 95 | ||
| 110 | ``` | 96 | ``` |
| 111 | # App | ||
| 112 | ship --binary ./myapp --domain api.example.com | 97 | ship --binary ./myapp --domain api.example.com |
| 113 | ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost --env API_KEY=secret | 98 | ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost |
| 114 | ship --binary ./myapp --domain api.example.com --env-file .env.production | ||
| 115 | ship --binary ./myapp --name myapi --memory 512M --cpu 50% | ||
| 116 | |||
| 117 | # Static site | ||
| 118 | ship --static --dir ./dist --domain example.com | 99 | ship --static --dir ./dist --domain example.com |
| 119 | 100 | ship --name myapi --memory 512M --cpu 50% | |
| 120 | # Config update (no binary, just change settings) | ||
| 121 | ship --name myapi --memory 1G | ||
| 122 | ship --name myapi --env DEBUG=true | ||
| 123 | ``` | 101 | ``` |
| 124 | 102 | ||
| 125 | Flags: | 103 | Flags: `--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 | ||
| 140 | List all deployments on the default host. | 107 | List all deployments on the default host. |
| 141 | 108 | ||
| 142 | ``` | 109 | ### `ship status/logs/restart/remove <name>` |
| 143 | NAME TYPE VISIBILITY DOMAIN PORT | ||
| 144 | myapp git-app private myapp.example.com :8001 | ||
| 145 | mysite git-static public mysite.example.com | ||
| 146 | api app api.example.com :8002 | ||
| 147 | ``` | ||
| 148 | |||
| 149 | ### `ship status <name>` | ||
| 150 | |||
| 151 | Show systemd service status for an app. | ||
| 152 | |||
| 153 | ### `ship logs <name>` | ||
| 154 | |||
| 155 | Show service logs (via journalctl). | ||
| 156 | |||
| 157 | ### `ship restart <name>` | ||
| 158 | |||
| 159 | Restart an app's systemd service. | ||
| 160 | |||
| 161 | ### `ship remove <name>` | ||
| 162 | 110 | ||
| 163 | Remove a deployment. Stops the service, removes files, configs, and state. | 111 | Manage a deployment's systemd service. |
| 164 | 112 | ||
| 165 | ### `ship env` | 113 | ### `ship env` |
| 166 | 114 | ||
| 167 | Manage environment variables for an app. | ||
| 168 | |||
| 169 | ``` | 115 | ``` |
| 170 | ship env list myapp # show env vars (secrets masked) | 116 | ship env list myapp |
| 171 | ship env set myapp KEY=VALUE # set variable(s) | 117 | ship env set myapp KEY=VALUE |
| 172 | ship env set myapp -f .env # load from file | 118 | ship env unset myapp KEY |
| 173 | ship env unset myapp KEY # remove a variable | ||
| 174 | ``` | 119 | ``` |
| 175 | 120 | ||
| 176 | ### `ship host` | 121 | ### `ship host` |
| 177 | 122 | ||
| 178 | Manage the VPS. | ||
| 179 | |||
| 180 | ``` | 123 | ``` |
| 181 | ship host init --host user@vps --base-domain example.com # one-time setup | 124 | ship host init --host user@vps --base-domain example.com |
| 182 | ship host status # uptime, disk, memory, load | 125 | ship host status |
| 183 | ship host update # apt update && upgrade | 126 | ship host update |
| 184 | ship host ssh # open an SSH session | 127 | ship host ssh |
| 185 | ship host set-domain example.com # change base domain | ||
| 186 | ``` | 128 | ``` |
| 187 | 129 | ||
| 188 | ### `ship ui` | 130 | ### `ship ui` |
| 189 | 131 | ||
| 190 | Launch a local web UI for viewing deployments. | 132 | Launch a local web UI for viewing deployments. |
| 191 | 133 | ||
| 192 | ``` | ||
| 193 | ship ui # http://localhost:8080 | ||
| 194 | ship ui -p 3000 # custom port | ||
| 195 | ``` | ||
| 196 | |||
| 197 | ### `ship version` | ||
| 198 | |||
| 199 | Show version, commit, and build date. | ||
| 200 | |||
| 201 | ## How it works | ||
| 202 | |||
| 203 | ### Architecture | ||
| 204 | |||
| 205 | Ship 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 | |||
| 209 | 1. `ship init` creates a bare repo at `/srv/git/<name>.git` with a post-receive hook | ||
| 210 | 2. `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 | ||
| 216 | 3. 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`) | ||
| 220 | 4. Caddy reverse-proxies HTTPS traffic to the container | ||
| 221 | |||
| 222 | ### Direct deploy flow | ||
| 223 | |||
| 224 | 1. Binary uploaded via SCP to `/usr/local/bin/<name>` | ||
| 225 | 2. A dedicated system user is created | ||
| 226 | 3. Ship generates and installs a systemd unit and Caddy config | ||
| 227 | 4. Service is started, Caddy is reloaded | ||
| 228 | |||
| 229 | ### Vanity imports and git cloning | ||
| 230 | |||
| 231 | When 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 | |||
| 236 | This is handled by Caddy's `templates` directive and `fcgiwrap` + `git-http-backend`, with no custom server. | ||
| 237 | |||
| 238 | ### Port allocation | ||
| 239 | |||
| 240 | Ports 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 | |||
| 266 | For direct-deployed Go apps, the binary must: | ||
| 267 | |||
| 268 | 1. Listen on HTTP (Caddy handles HTTPS) | ||
| 269 | 2. Read the port from the `PORT` environment variable or a `--port` flag | ||
| 270 | 3. Bind to `127.0.0.1` (not `0.0.0.0`) | ||
| 271 | |||
| 272 | For 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 | ||
| 276 | VPS: Ubuntu 20.04+ or Debian 11+ | 154 | VPS: 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 | |||
| 26 | The `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. | 26 | The `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 |
| 29 | Repos 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. | 29 | Repos 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 |
| 32 | The `.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. | 32 | The `.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 | ||
| 258 | var cgitrcTemplate = `virtual-root=/ | ||
| 259 | css=/cgit/cgit.css | ||
| 260 | logo=/cgit/cgit.png | ||
| 261 | header=/opt/ship/cgit-header.html | ||
| 262 | scan-path=/srv/git/ | ||
| 263 | export-ok=git-daemon-export-ok | ||
| 264 | enable-http-clone=0 | ||
| 265 | clone-url=https://{{.BaseDomain}}/$CGIT_REPO_URL | ||
| 266 | root-title={{.BaseDomain}} | ||
| 267 | root-desc= | ||
| 268 | remove-suffix=.git | ||
| 269 | ` | ||
| 270 | |||
| 271 | var cgitHeaderTemplate = `<style> | ||
| 272 | body, table, td, th, div#cgit { background: #1a1a2e; color: #ccc; } | ||
| 273 | a { color: #7aa2f7; } | ||
| 274 | a:hover { color: #9ecbff; } | ||
| 275 | table.list tr:hover td { background: #222244; } | ||
| 276 | table.list td, table.list th { border-bottom: 1px solid #333; } | ||
| 277 | th { background: #16213e; } | ||
| 278 | td.commitgraph .column1 { color: #7aa2f7; } | ||
| 279 | td.commitgraph .column2 { color: #9ece6a; } | ||
| 280 | td.logheader { background: #16213e; } | ||
| 281 | div#header { background: #16213e; border-bottom: 1px solid #333; } | ||
| 282 | div#header .sub { color: #888; } | ||
| 283 | table.tabs { border-bottom: 1px solid #333; } | ||
| 284 | table.tabs td a { color: #ccc; } | ||
| 285 | table.tabs td a.active { color: #fff; background: #1a1a2e; border: 1px solid #333; border-bottom: 1px solid #1a1a2e; } | ||
| 286 | div.footer { color: #555; } | ||
| 287 | div.footer a { color: #555; } | ||
| 288 | div.diffstat-header { background: #16213e; } | ||
| 289 | table.diffstat { border-bottom: 1px solid #333; } | ||
| 290 | table.diffstat td.graph span.graph-moreremoved { background: #f7768e; } | ||
| 291 | table.diffstat td.graph span.graph-moreadded { background: #9ece6a; } | ||
| 292 | table.diffstat td.graph span.graph-removed { background: #f7768e; } | ||
| 293 | table.diffstat td.graph span.graph-added { background: #9ece6a; } | ||
| 294 | table.diff { background: #131320; border: 1px solid #333; } | ||
| 295 | div.diff td { font-family: monospace; } | ||
| 296 | div.head { color: #ccc; background: #16213e; padding: 2px 4px; } | ||
| 297 | div.hunk { color: #7aa2f7; background: #1a1a3e; padding: 2px 4px; } | ||
| 298 | div.add { color: #9ece6a; background: #1a2e1a; padding: 2px 4px; } | ||
| 299 | div.del { color: #f7768e; background: #2e1a1a; padding: 2px 4px; } | ||
| 300 | table.diff td.add { color: #9ece6a; background: #1a2e1a; } | ||
| 301 | table.diff td.del { color: #f7768e; background: #2e1a1a; } | ||
| 302 | table.diff td.hunk { color: #7aa2f7; background: #1a1a3e; } | ||
| 303 | table.diff td { border: none; background: #1a1a2e; } | ||
| 304 | table.blob td.lines { color: #ccc; } | ||
| 305 | table.blob td.linenumbers { background: #16213e; } | ||
| 306 | table.blob td.linenumbers a { color: #555; } | ||
| 307 | table.blob td.linenumbers a:hover { color: #7aa2f7; } | ||
| 308 | table.ssdiff td.add { color: #9ece6a; background: #1a2e1a; } | ||
| 309 | table.ssdiff td.del { color: #f7768e; background: #2e1a1a; } | ||
| 310 | table.ssdiff td { border-right: 1px solid #333; } | ||
| 311 | table.ssdiff td.hunk { color: #7aa2f7; background: #1a1a3e; } | ||
| 312 | table.ssdiff td.head { background: #16213e; border-bottom: 1px solid #333; } | ||
| 313 | table.ssdiff td.foot { background: #16213e; border-top: 1px solid #333; } | ||
| 314 | table.ssdiff td.lineno { background: #16213e; color: #555; } | ||
| 315 | pre { color: #ccc; } | ||
| 316 | input, textarea, select { background: #222; color: #ccc; border: 1px solid #444; } | ||
| 317 | img#logo { display: none; } | ||
| 318 | </style> | ||
| 319 | ` | ||
| 320 | |||
| 321 | // CgitRC generates the /etc/cgitrc config file | ||
| 322 | func 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) | ||
| 327 | func 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 |
| 243 | func DockerService(data map[string]string) (string, error) { | 332 | func DockerService(data map[string]string) (string, error) { |
| 244 | return renderTemplate("docker-service", dockerServiceTemplate, data) | 333 | return renderTemplate("docker-service", dockerServiceTemplate, data) |
