2020-10-11 00:03:16 +00:00
|
|
|
# [GoServ](https://git.coolaj86.com/coolaj86/goserv)
|
|
|
|
|
2022-04-14 18:49:59 +00:00
|
|
|
!["golang gopher with goggles"](https://i.imgur.com/S2nhiRK.jpg)
|
2020-09-20 03:55:46 +00:00
|
|
|
|
|
|
|
> Boilerplate for how I like to write a backend web service
|
2020-09-28 07:26:16 +00:00
|
|
|
|
2020-09-30 04:34:20 +00:00
|
|
|
## Build
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
#### 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:
|
2020-10-12 20:20:25 +00:00
|
|
|
gitea_token: ~/.config/goreleaser/gitea_token
|
2020-10-11 00:03:16 +00:00
|
|
|
gitea_urls:
|
2020-10-12 20:20:25 +00:00
|
|
|
api: https://try.gitea.io/api/v1/
|
2020-10-11 00:03:16 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
#### Manually
|
|
|
|
|
|
|
|
```bash
|
|
|
|
git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/goserv.git ./goserv
|
|
|
|
pushd ./goserv
|
|
|
|
bash ./examples/build.sh
|
|
|
|
```
|
|
|
|
|
2020-09-28 07:26:16 +00:00
|
|
|
```bash
|
2020-09-30 04:34:20 +00:00
|
|
|
export GOFLAGS="-mod=vendor"
|
2020-09-28 07:26:16 +00:00
|
|
|
go mod tidy
|
|
|
|
go mod vendor
|
|
|
|
go generate -mod=vendor ./...
|
2020-10-11 00:03:16 +00:00
|
|
|
go build -mod=vendor -o dist/goserv .
|
2020-09-28 07:26:16 +00:00
|
|
|
```
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
To build for another platform (such as Raspberry Pi) set `GOOS` and GOARCH`:
|
|
|
|
|
2020-09-28 07:26:16 +00:00
|
|
|
```bash
|
2020-10-11 00:03:16 +00:00
|
|
|
GOOS=linux GOARCH=arm64 go build -mod=vendor -o dist/goserv-linux-arm64 .
|
2020-09-28 07:26:16 +00:00
|
|
|
```
|
2020-09-29 03:17:10 +00:00
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
#### Run
|
|
|
|
|
|
|
|
```bash
|
|
|
|
./dist/goserv run --listen :3000 --trust-proxy --serve-path ./overrides
|
|
|
|
```
|
|
|
|
|
|
|
|
## Examples and Config Templates
|
2020-09-30 08:29:24 +00:00
|
|
|
|
|
|
|
The example files are located in `./examples`
|
|
|
|
|
2020-10-12 20:20:25 +00:00
|
|
|
- Caddyfile (web server config)
|
|
|
|
- .env (environment variables)
|
|
|
|
- build.sh
|
2020-10-11 00:03:16 +00:00
|
|
|
|
|
|
|
```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:
|
|
|
|
|
2020-10-12 20:20:25 +00:00
|
|
|
Base URL looks like `https://example.com/api`.
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
```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
|
|
|
|
```
|
|
|
|
|
2020-10-12 20:20:25 +00:00
|
|
|
- <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>
|
2020-10-11 00:03:16 +00:00
|
|
|
|
|
|
|
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 ./...
|
|
|
|
```
|
2020-09-30 08:29:24 +00:00
|
|
|
|
|
|
|
## Dependencies
|
|
|
|
|
|
|
|
This setup can be run on a VPS, such as Digital Ocean, OVH, or Scaleway
|
|
|
|
for \$5/month with minimal dependencies:
|
|
|
|
|
2020-10-12 20:20:25 +00:00
|
|
|
- VPS
|
|
|
|
- Caddy
|
|
|
|
- PostgreSQL
|
|
|
|
- Serviceman
|
2020-09-30 08:29:24 +00:00
|
|
|
|
|
|
|
**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
|
|
|
|
```
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
See the Cheat Sheets at https://webinstall.dev/caddy
|
2020-09-30 08:29:24 +00:00
|
|
|
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
|
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
## Licenses
|
|
|
|
|
|
|
|
Copyright 2020 The GoServ Authors. All rights reserved.
|
|
|
|
|
|
|
|
### Exceptions
|
|
|
|
|
2020-10-12 20:20:25 +00:00
|
|
|
- `countries.json` LGPL, taken from <https://github.com/stefangabos/world_countries>
|
|
|
|
- `flags.json` MIT, taken from <https://github.com/matiassingers/emoji-flags>
|
2020-09-29 03:17:10 +00:00
|
|
|
|
2020-10-11 00:03:16 +00:00
|
|
|
These are probably also in the Public Domain. \
|
|
|
|
(gathering the official data from any source would yield the same dataset)
|