1# Soft Serve
  2
  3<p>
  4    <img style="width: 451px" src="https://stuff.charm.sh/soft-serve/soft-serve-header.png?0" alt="A nice rendering of some melting ice cream with the words āCharm Soft Serveā next to it"><br>
  5    <a href="https://github.com/charmbracelet/soft-serve/releases"><img src="https://img.shields.io/github/release/charmbracelet/soft-serve.svg" alt="Latest Release"></a>
  6    <a href="https://pkg.go.dev/github.com/charmbracelet/soft-serve?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
  7    <a href="https://github.com/charmbracelet/soft-serve/actions"><img src="https://github.com/charmbracelet/soft-serve/workflows/build/badge.svg" alt="Build Status"></a>
  8    <a href="https://nightly.link/charmbracelet/soft-serve/workflows/nightly/main"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=appveyor"/></a>
  9</p>
 10
 11A tasty, self-hostable Git server for the command line. š¦
 12
 13<picture>
 14  <source media="(max-width: 750px)" srcset="https://stuff.charm.sh/soft-serve/soft-serve-demo.gif?0">
 15  <source media="(min-width: 750px)" width="750" srcset="https://stuff.charm.sh/soft-serve/soft-serve-demo.gif?0">
 16  <img src="https://stuff.charm.sh/soft-serve/soft-serve-demo.gif?0" alt="Soft Serve screencast">
 17</picture>
 18
 19- Easy to navigate TUI available over SSH
 20- Clone repos over SSH, HTTP, or Git protocol
 21- Manage repos with SSH
 22- Create repos on demand with SSH or `git push`
 23- Browse repos, files and commits with SSH-accessible
 24- Print files over SSH with or without syntax highlighting and line numbers
 25- Easy access control with SSH
 26  - Allow/disallow anonymous access
 27  - Add collaborators with SSH public keys
 28  - Repos can be public or private
 29
 30## Where can I see it?
 31
 32Just run `ssh git.charm.sh` for an example. You can also try some of the following commands:
 33
 34```bash
 35# Jump directly to a repo in the TUI
 36ssh git.charm.sh -t soft-serve
 37
 38# Print out a directory tree for a repo
 39ssh git.charm.sh repo tree soft-serve
 40
 41# Print a specific file
 42ssh git.charm.sh repo blob soft-serve cmd/soft/root.go
 43
 44# Print a file with syntax highlighting and line numbers
 45ssh git.charm.sh repo blob soft-serve cmd/soft/root.go -c -l
 46```
 47
 48## Installation
 49
 50Soft Serve is a single binary called `soft`. You can get it from a package
 51manager:
 52
 53```bash
 54# macOS or Linux
 55brew tap charmbracelet/tap && brew install charmbracelet/tap/soft-serve
 56
 57# Arch Linux
 58pacman -S soft-serve
 59
 60# Nix
 61nix-env -iA nixpkgs.soft-serve
 62
 63# Debian/Ubuntu
 64sudo mkdir -p /etc/apt/keyrings
 65curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
 66echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
 67sudo apt update && sudo apt install soft-serve
 68
 69# Fedora/RHEL
 70echo '[charm]
 71name=Charm
 72baseurl=https://repo.charm.sh/yum/
 73enabled=1
 74gpgcheck=1
 75gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo
 76sudo yum install soft-serve
 77```
 78
 79You can also download a binary from the [releases][releases] page. Packages are
 80available in Alpine, Debian, and RPM formats. Binaries are available for Linux,
 81macOS, and Windows.
 82
 83[releases]: https://github.com/charmbracelet/soft-serve/releases
 84
 85Or just install it with `go`:
 86
 87```bash
 88go install github.com/charmbracelet/soft-serve/cmd/soft@latest
 89```
 90
 91A [Docker image][docker] is also available.
 92
 93[docker]: https://github.com/charmbracelet/soft-serve/blob/main/docker.md
 94
 95## Setting up a server
 96
 97Make sure `git` is installed, then run `soft serve`. Thatās it.
 98
 99This will create a `data` directory that will store all the repos, ssh keys,
100and database.
101
102To change the default data path use `SOFT_SERVE_DATA_PATH` environment variable.
103
104```sh
105SOFT_SERVE_DATA_PATH=/var/lib/soft-serve soft serve
106```
107
108When you run Soft Serve for the first time, make sure you have the
109`SOFT_SERVE_INITIAL_ADMIN_KEYS` environment variable is set to your ssh
110authorized key. Any added key to this variable will be treated as admin with
111full privileges.
112
113Using this environment variable, Soft Serve will create a new `admin` user that
114has full privileges. You can rename and change the user settings later.
115
116### Server Settings
117
118Once you start the server for the first time, the settings will be in
119`config.yaml` under your data directory. The default `config.yaml` is
120self-explanatory and will look like this:
121
122```yaml
123# Soft Serve Server configurations
124
125# The name of the server.
126# This is the name that will be displayed in the UI.
127name: "Soft Serve"
128
129# Log format to use. Valid values are "json", "logfmt", and "text".
130log_format: "text"
131
132# The SSH server configuration.
133ssh:
134  # The address on which the SSH server will listen.
135  listen_addr: ":23231"
136
137  # The public URL of the SSH server.
138  # This is the address that will be used to clone repositories.
139  public_url: "ssh://localhost:23231"
140
141  # The path to the SSH server's private key.
142  key_path: "ssh/soft_serve_host"
143
144  # The path to the SSH server's client private key.
145  # This key will be used to authenticate the server to make git requests to
146  # ssh remotes.
147  client_key_path: "ssh/soft_serve_client"
148
149  # The maximum number of seconds a connection can take.
150  # A value of 0 means no timeout.
151  max_timeout: 0
152
153  # The number of seconds a connection can be idle before it is closed.
154  idle_timeout: 120
155
156# The Git daemon configuration.
157git:
158  # The address on which the Git daemon will listen.
159  listen_addr: ":9418"
160
161  # The maximum number of seconds a connection can take.
162  # A value of 0 means no timeout.
163  max_timeout: 0
164
165  # The number of seconds a connection can be idle before it is closed.
166  idle_timeout: 3
167
168  # The maximum number of concurrent connections.
169  max_connections: 32
170
171# The HTTP server configuration.
172http:
173  # The address on which the HTTP server will listen.
174  listen_addr: ":8080"
175
176  # The path to the TLS private key.
177  tls_key_path: ""
178
179  # The path to the TLS certificate.
180  tls_cert_path: ""
181
182  # The public URL of the HTTP server.
183  # This is the address that will be used to clone repositories.
184  # Make sure to use https:// if you are using TLS.
185  public_url: "http://localhost:8080"
186
187# The stats server configuration.
188stats:
189  # The address on which the stats server will listen.
190  listen_addr: ":8081"
191
192# Additional admin keys.
193#initial_admin_keys:
194#  - "ssh-rsa AAAAB3NzaC1yc2..."
195
196```
197
198You can also use environment variables, to override these settings. All server
199settings environment variables start with `SOFT_SERVE_` followed by the setting
200name all in uppercase. Here are some examples:
201
202- `SOFT_SERVE_NAME`: The name of the server that will appear in the TUI
203- `SOFT_SERVE_SSH_LISTEN_ADDR`: SSH listen address
204- `SOFT_SERVE_SSH_KEY_PATH`: SSH host key-pair path
205- `SOFT_SERVE_HTTP_LISTEN_ADDR`: HTTP listen address
206- `SOFT_SERVE_HTTP_PUBLIC_URL`: HTTP public URL used for cloning
207- `SOFT_SERVE_GIT_MAX_CONNECTIONS`: The number of simultaneous connections to git daemon
208
209## Configuration
210
211Configuring Soft Serve is simple and straightforward. Use the SSH command-line
212interface to manage access settings, users, and repos.
213
214Try `ssh localhost -i ~/.ssh/id_ed25519 -p 23231 help` for more info. Make sure
215you use your key here.
216
217For ease of use, instead of specifying the key, port, and hostname every time
218you SSH into Soft Serve, add your own Soft Serve instance entry to your SSH
219config. For instance, to use `ssh soft` instead of typing `ssh localhost -i
220~/.ssh/id_ed25519 -p 23231`, we can define a `soft` entry in our SSH config
221file `~/.ssh/config`.
222
223```conf
224Host soft
225  HostName localhost
226  Port 23231
227  IdentityFile ~/.ssh/id_ed25519
228```
229
230Now, we can do `ssh soft` to SSH into Soft Serve. Since `git` is also aware of
231this config, you can use `soft` as the hostname for your clone commands.
232
233```sh
234git clone ssh://soft/dotfiles
235# make changes
236# add & commit
237git push origin main
238```
239
240> **Note** The `-i` part will be omitted in the examples below for brevity. You
241> can add your server settings to your sshconfig for quicker access.
242
243### Access Levels
244
245Soft Serve offers a simple access control. There are four access levels,
246no-access, read-only, read-write, and admin-access.
247
248`admin-access` has full control of the server and can make changes to users and repos.
249
250`read-write` access gets full control of repos.
251
252`read-only` can read public repos.
253
254`no-access` denies access to all repos.
255
256### Authentication
257
258Everything that needs authentication is done using SSH. Make sure you have
259added an entry for your Soft Serve instance in your `~/.ssh/config` file.
260
261By default, Soft Serve gives ready-only permission to anonymous connections to
262any of the above protocols. This is controlled by two settings `anon-access`
263and `allow-keyless`.
264
265- `anon-access`: Defines the access level for anonymous users. Available
266  options are `no-access`, `read-only`, `read-write`, and `admin-access`.
267  Default is `read-only`.
268- `allow-keyless`: Whether to allow connections that doesn't use keys to pass.
269  Setting this to `false` would disable access to SSH keyboard-interactive,
270  HTTP, and Git protocol connections. Default is `true`.
271
272```sh
273$ ssh -p 23231 localhost settings
274Manage server settings
275
276Usage:
277  ssh -p 23231 localhost settings [command]
278
279Available Commands:
280  allow-keyless Set or get allow keyless access to repositories
281  anon-access   Set or get the default access level for anonymous users
282
283Flags:
284  -h, --help   help for settings
285
286Use "ssh -p 23231 localhost settings [command] --help" for more information about a command.
287```
288
289> **Note** These settings can only be changed by admins.
290
291When `allow-keyless` is disabled, connections that don't use SSH Public Key
292authentication will get denied. This means cloning repos over HTTP(s) or git://
293will get denied.
294
295Meanwhile, `anon-access` controls the access level granted to connections that
296use SSH Public Key authentication but are not registered users. The default
297setting for this is `read-only`. This will grant anonymous connections that use
298SSH Public Key authentication `read-only` access to public repos.
299
300`anon-access` is also used in combination with `allow-keyless` to determine the
301access level for HTTP(s) and git:// clone requests.
302
303## Authorization
304
305Admins can manage users and their keys using the `user` command. Once a user is
306created and has access to the server, they can manage their own keys and
307settings.
308
309To create a new user simply use `user create`:
310
311```sh
312# Create a new user
313ssh -p 23231 localhost user create beatrice
314
315# Add user keys
316ssh -p 23231 localhost user add-pubkey beatrice ssh-rsa AAAAB3Nz...
317ssh -p 23231 localhost user add-pubkey beatrice ssh-ed25519 AAAA...
318
319# Create another user with public key
320ssh -p 23231 localhost user create frankie '-k "ssh-ed25519 AAAATzN..."'
321
322# Need help?
323ssh -p 23231 localhost user help
324```
325
326Once a user is created, they get `read-only` access to public repositories.
327They can also create new repositories on the server.
328
329Users can manage their keys using the `pubkey` command:
330
331```sh
332# List user keys
333ssh -p 23231 localhost pubkey list
334
335# Add key
336ssh -p 23231 localhost pubkey add ssh-ed25519 AAAA...
337
338# Wanna change your username?
339ssh -p 23231 localhost set-username yolo
340
341# To display user info
342ssh -p 23231 localhost info
343```
344
345## Repositories
346
347You can manage repositories using the `repo` command.
348
349```sh
350# Run repo help
351$ ssh -p 23231 localhost repo help
352Manage repositories
353
354Usage:
355  ssh -p 23231 localhost repo [command]
356
357Aliases:
358  repo, repos, repository, repositories
359
360Available Commands:
361  blob         Print out the contents of file at path
362  branch       Manage repository branches
363  collab       Manage collaborators
364  create       Create a new repository
365  delete       Delete a repository
366  description  Set or get the description for a repository
367  hide         Hide or unhide a repository
368  import       Import a new repository from remote
369  info         Get information about a repository
370  is-mirror    Whether a repository is a mirror
371  list         List repositories
372  private      Set or get a repository private property
373  project-name Set or get the project name for a repository
374  rename       Rename an existing repository
375  tag          Manage repository tags
376  tree         Print repository tree at path
377
378Flags:
379  -h, --help   help for repo
380
381Use "ssh -p 23231 localhost repo [command] --help" for more information about a command.
382```
383
384To use any of the above `repo` commands, a user must be a collaborator in the repository. More on this below.
385
386### Creating Repositories
387
388To create a repository, first make sure you are a registered user. Use the
389`repo create <repo>` command to create a new repository:
390
391```sh
392# Create a new repository
393ssh -p 23231 localhost repo create icecream
394
395# Create a repo with description
396ssh -p 23231 localhost repo create icecream '-d "This is an Ice Cream description"'
397
398# ... and project name
399ssh -p 23231 localhost repo create icecream '-d "This is an Ice Cream description"' '-n "Ice Cream"'
400
401# I need my repository private!
402ssh -p 23231 localhost repo create icecream -p '-d "This is an Ice Cream description"' '-n "Ice Cream"'
403
404# Help?
405ssh -p 23231 localhost repo create -h
406```
407
408Or you can add your Soft Serve server as a remote to any existing repo, given
409you have write access, and push to remote:
410
411```
412git remote add origin ssh://localhost:23231/icecream
413```
414
415After youāve added the remote just go ahead and push. If the repo doesnāt exist
416on the server itāll be created.
417
418```
419git push origin main
420```
421
422Repositories can be nested too:
423
424```sh
425# Create a new nested repository
426ssh -p 23231 localhost repo create charmbracelet/icecream
427
428# Or ...
429git remote add charm ssh://localhost:23231/charmbracelet/icecream
430git push charm main
431```
432
433### Deleting Repositories
434
435You can delete repositories using the `repo delete <repo>` command.
436
437```sh
438ssh -p 23231 localhost repo delete icecream
439```
440
441### Renaming Repositories
442
443Use the `repo rename <old> <new>` command to rename existing repositories.
444
445```sh
446ssh -p 23231 localhost repo rename icecream vanilla
447```
448
449### Repository Collaborators
450
451Sometimes you want to restrict write access to certain repositories. This can
452be achieved by adding a collaborator to your repository.
453
454Use the `repo collab <command> <repo>` command to manage repo collaborators.
455
456```sh
457# Add collaborator to soft-serve
458ssh -p 23231 localhost repo collab add soft-serve frankie
459
460# Remove collaborator
461ssh -p 23231 localhost repo collab remove soft-serve beatrice
462
463# List collaborators
464ssh -p 23231 localhost repo collab list soft-serve
465```
466
467### Repository metadata
468
469You can also change the repo's description, project name, whether it's private,
470etc using the `repo <command>` command.
471
472```sh
473# Set description for repo
474ssh -p 23231 localhost repo description icecream "This is a new description"
475
476# Hide repo from listing
477ssh -p 23231 localhost repo hidden icecream true
478
479# List repository info (branches, tags, description, etc)
480ssh -p 23231 localhost repo icecream info
481```
482
483To make a repository private, use `repo private <repo> [true|false]`. Private
484repos can only be accessed by admins and collaborators.
485
486```sh
487ssh -p 23231 localhost repo icecream private true
488```
489
490### Repository Branches & Tags
491
492Use `repo branch` and `repo tag` to list, and delete branches or tags. You can
493also use `repo branch default` to set or get the repository default branch.
494
495### Repository Tree
496
497To print a file tree for the project, just use the `repo tree` command along with
498the repo name as the SSH command to your Soft Serve server:
499
500```sh
501ssh -p 23231 localhost repo tree soft-serve
502```
503
504You can also specify the sub-path and a specific reference or branch.
505
506```sh
507ssh -p 23231 localhost repo tree soft-serve server/config
508ssh -p 23231 localhost repo tree soft-serve main server/config
509```
510
511From there, you can print individual files using the `repo blob` command:
512
513```sh
514ssh -p 23231 localhost repo blob soft-serve cmd/soft/root.go
515```
516
517You can add the `-c` flag to enable syntax coloring and `-l` to print line
518numbers:
519
520```sh
521ssh -p 23231 localhost repo blob soft-serve cmd/soft/root.go -c -l
522
523```
524
525Use `--raw` to print raw file contents. This is useful for dumping binary data.
526
527## The Soft Serve TUI
528
529<img src="https://stuff.charm.sh/soft-serve/soft-serve-demo-commit.png" width="750" alt="TUI example showing a diff">
530
531Soft Serve serves a TUI over SSH for browsing repos, viewing files and commits,
532and grabbing clone commands:
533
534```sh
535ssh localhost -p 23231
536```
537
538It's also possible to ālinkā to a specific repo:
539
540```sh
541ssh -p 23231 localhost -t soft-serve
542```
543
544You can copy text to your clipboard over SSH. For instance, you can press
545<kbd>c</kbd> on the highlighted repo in the menu to copy the clone command
546[^osc52].
547
548[^osc52]:
549    Copying over SSH depends on your terminal support of OSC52. Refer to
550    [go-osc52](https://github.com/aymanbagabas/go-osc52) for more information.
551
552## Hooks
553
554Soft Serve supports git server-side hooks `pre-receive`, `update`,
555`post-update`, and `post-receive`. This means you can define your own hooks to
556run on repository push events. Hooks can be defined as a per-repository hook,
557and/or global hooks that run for all repositories.
558
559You can find per-repository hooks under the repository `hooks` directory.
560
561Globs hooks can be found in your `SOFT_SERVE_DATA_PATH` directory under
562`hooks`. Defining global hooks is useful if you want to run CI/CD for example.
563
564Here's an example of sending a message after receiving a push event. Create an
565executable file `<data path>/hooks/update`:
566
567```sh
568#!/bin/sh
569#
570# An example hook script to echo information about the push
571# and send it to the client.
572
573refname="$1"
574oldrev="$2"
575newrev="$3"
576
577# Safety check
578if [ -z "$GIT_DIR" ]; then
579        echo "Don't run this script from the command line." >&2
580        echo " (if you want, you could supply GIT_DIR then run" >&2
581        echo "  $0 <ref> <oldrev> <newrev>)" >&2
582        exit 1
583fi
584
585if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
586        echo "usage: $0 <ref> <oldrev> <newrev>" >&2
587        exit 1
588fi
589
590# Check types
591# if $newrev is 0000...0000, it's a commit to delete a ref.
592zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
593if [ "$newrev" = "$zero" ]; then
594        newrev_type=delete
595else
596        newrev_type=$(git cat-file -t $newrev)
597fi
598
599echo "Hi from Soft Serve update hook!"
600echo
601echo "RefName: $refname"
602echo "Change Type: $newrev_type"
603echo "Old SHA1: $oldrev"
604echo "New SHA1: $newrev"
605
606exit 0
607```
608
609Now, you should get a message after pushing changes to any repository.
610
611## A note about RSA keys
612
613Unfortunately, due to a shortcoming in Goās `x/crypto/ssh` package, Soft Serve
614does not currently support access via new SSH RSA keys: only the old SHA-1
615ones will work.
616
617Until we sort this out youāll either need an SHA-1 RSA key or a key with
618another algorithm, e.g. Ed25519. Not sure what type of keys you have?
619You can check with the following:
620
621```sh
622$ find ~/.ssh/id_*.pub -exec ssh-keygen -l -f {} \;
623```
624
625If youāre curious about the inner workings of this problem have a look at:
626
627- https://github.com/golang/go/issues/37278
628- https://go-review.googlesource.com/c/crypto/+/220037
629- https://github.com/golang/crypto/pull/197
630
631## Feedback
632
633Weād love to hear your thoughts on this project. Feel free to drop us a note!
634
635- [Twitter](https://twitter.com/charmcli)
636- [The Fediverse](https://mastodon.social/@charmcli)
637- [Discord](https://charm.sh/chat)
638
639## License
640
641[MIT](https://github.com/charmbracelet/soft-serve/raw/main/LICENSE)
642
643---
644
645Part of [Charm](https://charm.sh).
646
647<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
648
649Charmēē±å¼ęŗ ⢠Charm loves open source