291 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # [GoServ](https://git.coolaj86.com/coolaj86/goserv)
 | |
| 
 | |
| 
 | |
| 
 | |
| > Boilerplate for how I like to write a backend web service
 | |
| 
 | |
| ## Build
 | |
| 
 | |
| #### Goreleaser
 | |
| 
 | |
| See <https://webinstall.dev/goreleaser>
 | |
| 
 | |
| Local-only
 | |
| 
 | |
| ```bash
 | |
| goreleaser --snapshot --skip-publish --rm-dist
 | |
| ```
 | |
| 
 | |
| Publish
 | |
| 
 | |
| ```bash
 | |
| # Get a token at https://github.com/settings/tokens
 | |
| export GITHUB_TOKEN=xxxxxxx
 | |
| 
 | |
| # Remove --snapshot to error on non-clean releases
 | |
| goreleaser --snapshot --rm-dist
 | |
| ```
 | |
| 
 | |
| The platform, publish URL, and token file can be changed in `.goreleaser.yml`:
 | |
| 
 | |
| ```yml
 | |
| env_files:
 | |
|     gitea_token: ~/.config/goreleaser/gitea_token
 | |
| gitea_urls:
 | |
|     api: https://try.gitea.io/api/v1/
 | |
| ```
 | |
| 
 | |
| #### Manually
 | |
| 
 | |
| ```bash
 | |
| git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/goserv.git ./goserv
 | |
| pushd ./goserv
 | |
| bash ./examples/build.sh
 | |
| ```
 | |
| 
 | |
| ```bash
 | |
| export GOFLAGS="-mod=vendor"
 | |
| go mod tidy
 | |
| go mod vendor
 | |
| go generate -mod=vendor ./...
 | |
| go build -mod=vendor -o dist/goserv .
 | |
| ```
 | |
| 
 | |
| To build for another platform (such as Raspberry Pi) set `GOOS` and GOARCH`:
 | |
| 
 | |
| ```bash
 | |
| GOOS=linux GOARCH=arm64 go build -mod=vendor -o dist/goserv-linux-arm64 .
 | |
| ```
 | |
| 
 | |
| #### Run
 | |
| 
 | |
| ```bash
 | |
| ./dist/goserv run --listen :3000 --trust-proxy --serve-path ./overrides
 | |
| ```
 | |
| 
 | |
| ## Examples and Config Templates
 | |
| 
 | |
| The example files are located in `./examples`
 | |
| 
 | |
| -   Caddyfile (web server config)
 | |
| -   .env (environment variables)
 | |
| -   build.sh
 | |
| 
 | |
| ```bash
 | |
| export BASE_URL=https://example.com
 | |
| ```
 | |
| 
 | |
| ### Authentication
 | |
| 
 | |
| You can use an OIDC provider or sign your own tokens.
 | |
| 
 | |
| ```bash
 | |
| go install -mod=vendor git.rootprojects.org/root/keypairs/cmd/keypairs
 | |
| 
 | |
| # Generate a keypair
 | |
| keypairs gen -o key.jwk.json --pub pub.jwk.json
 | |
| ```
 | |
| 
 | |
| Create an Admin token:
 | |
| 
 | |
| ```bash
 | |
| # Sign an Admin token
 | |
| echo '{ "sub": "random_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' \
 | |
|     > admin.claims.json
 | |
| keypairs sign --exp 1h ./key.jwk.json ./admin.claims.json > admin.jwt.txt 2> admin.jws.json
 | |
| 
 | |
| # verify the Admin token
 | |
| keypairs verify ./pub.jwk.json ./admin.jwt.txt
 | |
| ```
 | |
| 
 | |
| Create a User token:
 | |
| 
 | |
| ```bash
 | |
| # Sign a User token
 | |
| echo '{ "sub": "random_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' \
 | |
|     > user.claims.json
 | |
| keypairs sign --exp 1h ./key.jwk.json ./user.claims.json > user.jwt.txt 2> user.jws.json
 | |
| 
 | |
| # verify the User token
 | |
| keypairs verify ./pub.jwk.json ./user.jwt.txt
 | |
| ```
 | |
| 
 | |
| **Impersonation** can be accomplished by appending the token `sub` (aka PPID) to the url as
 | |
| `?user_id=`.
 | |
| 
 | |
| ## REST API
 | |
| 
 | |
| All routes require authentication, except for those at `/api/public`.
 | |
| 
 | |
| ```txt
 | |
| Authentication: Bearer <token>
 | |
| ```
 | |
| 
 | |
| Here's the API, in brief:
 | |
| 
 | |
| Base URL looks like `https://example.com/api`.
 | |
| 
 | |
| ```txt
 | |
| # Demo Mode Only
 | |
| DELETE /public/reset                                    Drop database and re-initialize
 | |
| 
 | |
| # Public
 | |
| GET  /public/ping                                       Health Check
 | |
| POST /public/setup                  <= (none)           Bootstrap
 | |
| 
 | |
| # Admin-only
 | |
| GET  /admin/ping                                        (authenticated) Health Check
 | |
| 
 | |
| # User
 | |
| GET  /user/ping                                         (authenticated) Health Check
 | |
| ```
 | |
| 
 | |
| When you `GET` anything, it will be wrapped in the `result`.
 | |
| 
 | |
| #### Bootstrapping
 | |
| 
 | |
| The first user to hit the `/api/setup` endpoint will be the **admin**:
 | |
| 
 | |
| ```bash
 | |
| export TOKEN=$(cat admin.jwt.txt)
 | |
| ```
 | |
| 
 | |
| ```bash
 | |
| curl -X POST "${BASE_URL}/api/public/setup" -H "Authorization: Bearer ${TOKEN}"
 | |
| ```
 | |
| 
 | |
| Then the setup endpoint will be disabled:
 | |
| 
 | |
| ```bash
 | |
| curl -X POST "${BASE_URL}/api/public/setup" -H "Authorization: Bearer ${TOKEN}"
 | |
| ```
 | |
| 
 | |
| ## GoDoc
 | |
| 
 | |
| If the documentation is not public hosted you can view it with GoDoc:
 | |
| 
 | |
| ```bash
 | |
| godoc --http :6060
 | |
| ```
 | |
| 
 | |
| -   <http://localhost:6060/pkg/git.example.com/example/goserv/>
 | |
| -   <http://localhost:6060/pkg/git.example.com/example/goserv/internal/api>
 | |
| -   <http://localhost:6060/pkg/git.example.com/example/goserv/internal/db>
 | |
| 
 | |
| You can see abbreviated documentation with `go`'s built-in `doc`, for example:
 | |
| 
 | |
| ```bash
 | |
| go doc git.example.com/example/goserv/internal/api
 | |
| ```
 | |
| 
 | |
| <http://localhost:6060>
 | |
| 
 | |
| ## Test
 | |
| 
 | |
| Create a test database. It must have `test` in the name.
 | |
| 
 | |
| ```sql
 | |
| DROP DATABASE "goserv_test"; CREATE DATABASE "goserv_test";
 | |
| ```
 | |
| 
 | |
| Run the tests:
 | |
| 
 | |
| ```bash
 | |
| export TEST_DATABASE_URL='postgres://postgres:postgres@localhost:5432/goserv_test'
 | |
| go test -mod=vendor ./...
 | |
| ```
 | |
| 
 | |
| ## Dependencies
 | |
| 
 | |
| This setup can be run on a VPS, such as Digital Ocean, OVH, or Scaleway
 | |
| for \$5/month with minimal dependencies:
 | |
| 
 | |
| -   VPS
 | |
| -   Caddy
 | |
| -   PostgreSQL
 | |
| -   Serviceman
 | |
| 
 | |
| **Mac**, **Linux**:
 | |
| 
 | |
| ```bash
 | |
| curl -fsS https://webinstall.dev | bash
 | |
| export PATH="$HOME:/.local/bin:$PATH"
 | |
| ```
 | |
| 
 | |
| ```bash
 | |
| webi caddy serviceman postgres
 | |
| ```
 | |
| 
 | |
| ### VPS Setup
 | |
| 
 | |
| You should have a domain pointing to a VPS and create a user account named `app`
 | |
| (because that's a common convention). This script will create an `app` user,
 | |
| copying the `authorized_keys` from the root account.
 | |
| 
 | |
| ```bash
 | |
| my_vps='example.com'
 | |
| ssh root@"$my_vps" 'curl -sS https://webinstall.dev/ssh-adduser | bash'
 | |
| ```
 | |
| 
 | |
| You can then login as a normal user.
 | |
| 
 | |
| ```bash
 | |
| ssh app@"$my_vps"
 | |
| ```
 | |
| 
 | |
| It is now safe to disable the root account.
 | |
| 
 | |
| ### Caddy (Automatic HTTPS Server)
 | |
| 
 | |
| ```bash
 | |
| curl -fsS https://webinstall.dev/caddy | bash
 | |
| ```
 | |
| 
 | |
| You can start Caddy as a system service under the app user like this:
 | |
| 
 | |
| ```bash
 | |
| sudo setcap 'cap_net_bind_service=+ep' "$(readlink $(command -v caddy))"
 | |
| 
 | |
| sudo env PATH="$PATH" \
 | |
|     serviceman add --name caddy --username app \
 | |
|     caddy run --config ./Caddyfile
 | |
| ```
 | |
| 
 | |
| See the Cheat Sheets at https://webinstall.dev/caddy
 | |
| and https://webinstall.dev/serviceman
 | |
| 
 | |
| ### PostgreSQL (Database)
 | |
| 
 | |
| ```bash
 | |
| curl -fsS https://webinstall.dev/postgres | bash
 | |
| ```
 | |
| 
 | |
| You can start Postgres as a system service under the app user like this:
 | |
| 
 | |
| ```bash
 | |
| sudo env PATH="$PATH" \
 | |
|     serviceman add --name postgres --username app -- \
 | |
|     postgres -D /home/app/.local/share/postgres/var -p 5432
 | |
| ```
 | |
| 
 | |
| Username and password are set to 'postgres' by default:
 | |
| 
 | |
| ```bash
 | |
| psql 'postgres://postgres:postgres@localhost:5432/postgres'
 | |
| ```
 | |
| 
 | |
| See the Cheat Sheets at https://webinstall.dev/postgres
 | |
| and https://webinstall.dev/serviceman
 | |
| 
 | |
| ## Licenses
 | |
| 
 | |
| Copyright 2020 The GoServ Authors. All rights reserved.
 | |
| 
 | |
| ### Exceptions
 | |
| 
 | |
| -   `countries.json` LGPL, taken from <https://github.com/stefangabos/world_countries>
 | |
| -   `flags.json` MIT, taken from <https://github.com/matiassingers/emoji-flags>
 | |
| 
 | |
| These are probably also in the Public Domain. \
 | |
| (gathering the official data from any source would yield the same dataset)
 |