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