goserv/README.md

291 lines
6.3 KiB
Markdown

# [GoServ](https://git.coolaj86.com/coolaj86/goserv)
!["golang gopher with goggles"](https://golang.org/lib/godoc/images/footer-gopher.jpg)
> 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)