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