Enhance cookbook and integrations #150

Merged
quentin merged 6 commits from doc/new-pages into main 2021-11-08 13:31:30 +00:00
7 changed files with 443 additions and 38 deletions

View file

@ -7,6 +7,8 @@
- [Cookbook](./cookbook/index.md)
- [Building from source](./cookbook/from_source.md)
- [Integration with systemd](./cookbook/systemd.md)
- [Gateways](./cookbook/gateways.md)
- [Exposing buckets as websites](./cookbook/exposing_websites.md)
- [Configuring a reverse proxy](./cookbook/reverse_proxy.md)
- [Production Deployment](./cookbook/real_world.md)
- [Recovering from failures](./cookbook/recovering.md)

View file

@ -253,35 +253,7 @@ Make sure you (will) have a corresponding DNS entry for them.
Now we will configure a reverse proxy in front of Garage.
This is required as we have no other way to serve CORS headers yet.
For our example, we will use nginx:
```nginx
server {
# In production you should use TLS instead of plain HTTP
listen [::]:80;
server_name peertube-video.web.garage peertube-playlist.web.garage;
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Expose-Headers Content-Length;
add_header Access-Control-Allow-Headers Range;
# We do not forward OPTIONS request to Garage
# as it does not know how to interpret them.
# Instead, we simply answers 200.
if ($request_method !~ ^(GET|HEAD)$ ) {
return 200;
}
# If your do not have a Garage instance on the reverse proxy, change the URL here.
proxy_pass http://127.0.0.1:3902;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
```
Check the [Configuring a reverse proxy](/cookbook/reverse_proxy.html) section to know how.
Now make sure that your 2 dns entries are pointing to your reverse proxy.
@ -346,13 +318,114 @@ https://docs.joinmastodon.org/admin/config/#cdn
## Matrix
### synapse-s3-storage-provider
Matrix is a chat communication protocol. Its main stable server implementation, [Synapse](https://matrix-org.github.io/synapse/latest/), provides a module to store media on a S3 backend. Additionally, a server independent media store supporting S3 has been developped by the community, it has been made possible thanks to how the matrix API has been designed and will work with implementations like Conduit, Dendrite, etc.
https://github.com/matrix-org/synapse-s3-storage-provider
### synapse-s3-storage-provider (synapse only)
### matrix-media-repo
Supposing you have a working synapse installation, you can add the module with pip:
https://github.com/turt2live/matrix-media-repo
```bash
pip3 install --user git+https://github.com/matrix-org/synapse-s3-storage-provider.git
```
Now create a bucket and a key for your matrix instance (note your Key ID and Secret Key somewhere, they will be needed later):
```bash
garage key new --name matrix-key
garage bucket create matrix
garage bucket allow matrix --read --write --key matrix-key
```
Then you must edit your server configuration (eg. `/etc/matrix-synapse/homeserver.yaml`) and add the `media_storage_providers` root key:
```yaml
media_storage_providers:
- module: s3_storage_provider.S3StorageProviderBackend
store_local: True # do we want to store on S3 media created by our users?
store_remote: True # do we want to store on S3 media created
# by users of others servers federated to ours?
store_synchronous: True # do we want to wait that the file has been written before returning?
config:
bucket: matrix # the name of our bucket, we chose matrix earlier
region_name: garage # only "garage" is supported for the region field
endpoint_url: http://localhost:3900 # the path to the S3 endpoint
access_key_id: "GKxxx" # your Key ID
secret_access_key: "xxxx" # your Secret Key
```
Note that uploaded media will also be stored locally and this behavior can not be deactivated, it is even required for
some operations like resizing images.
In fact, your local filesysem is considered as a cache but without any automated way to garbage collect it.
We can build our garbage collector with `s3_media_upload`, a tool provided with the module.
If you installed the module with the command provided before, you should be able to bring it in your path:
```
PATH=$HOME/.local/bin/:$PATH
command -v s3_media_upload
```
Now we can write a simple script (eg `~/.local/bin/matrix-cache-gc`):
```bash
#!/bin/bash
## CONFIGURATION ##
AWS_ACCESS_KEY_ID=GKxxx
AWS_SECRET_ACCESS_KEY=xxxx
S3_ENDPOINT=http://localhost:3900
S3_BUCKET=matrix
MEDIA_STORE=/var/lib/matrix-synapse/media
PG_USER=matrix
PG_PASS=xxxx
PG_DB=synapse
PG_HOST=localhost
PG_PORT=5432
## CODE ##
PATH=$HOME/.local/bin/:$PATH
cat > database.yaml <<EOF
user: $PG_USER
password: $PG_PASS
database: $PG_DB
host: $PG_HOST
port: $PG_PORT
EOF
s3_media_upload update-db 1d
s3_media_upload --no-progress check-deleted $MEDIA_STORE
s3_media_upload --no-progress upload $MEDIA_STORE $S3_BUCKET --delete --endpoint-url $S3_ENDPOINT
```
This script will list all the medias that were not accessed in the 24 hours according to your database.
It will check if, in this list, the file still exists in the local media store.
For files that are still in the cache, it will upload them to S3 if they are not already present (in case of a crash or an initial synchronisation).
Finally, the script will delete these files from the cache.
Make this script executable and check that it works:
```bash
chmod +x $HOME/.local/bin/matrix-cache-gc
matrix-cache-gc
```
Add it to your crontab. Open the editor with:
```bash
crontab -e
```
And add a new line. For example, to run it every 10 minutes:
```cron
*/10 * * * * $HOME/.local/bin/matrix-cache-gc
```
*External link:* [Github > matrix-org/synapse-s3-storage-provider](https://github.com/matrix-org/synapse-s3-storage-provider)
### matrix-media-repo (server independent)
*External link:* [matrix-media-repo Documentation > S3](https://docs.t2bot.io/matrix-media-repo/configuration/s3-datastore.html)
## Pixelfed

View file

@ -1 +1,77 @@
# Websites (Hugo, Jekyll, Publii...)
Garage is also suitable to host static websites.
While they can be deployed with traditional CLI tools, some static website generators have integrated options to ease your workflow.
## Hugo
Add to your `config.toml` the following section:
```toml
[[deployment.targets]]
URL = "s3://<bucket>?endpoint=<endpoint>&disableSSL=<bool>&s3ForcePathStyle=true&region=garage"
```
For example:
```toml
[[deployment.targets]]
URL = "s3://my-blog?endpoint=localhost:9000&disableSSL=true&s3ForcePathStyle=true&region=garage"
```
Then inform hugo of your credentials:
```bash
export AWS_ACCESS_KEY_ID=GKxxx
export AWS_SECRET_ACCESS_KEY=xxx
```
And finally deploy your website:
```bsh
hugo deploy
```
*External links:*
- [gocloud.dev > aws > Supported URL parameters](https://pkg.go.dev/gocloud.dev/aws?utm_source=godoc#ConfigFromURLParams)
- [Hugo Documentation > hugo deploy](https://gohugo.io/hosting-and-deployment/hugo-deploy/)
## Publii
It would require a patch either on Garage or on Publii to make both systems work.
Currently, the proposed workaround is to deploy your website manually:
- On the left menu, click on Server, choose Manual Deployment (the logo looks like a compressed file)
- Set your website URL, keep Output type as "Non-compressed catalog"
- Click on Save changes
- Click on Sync your website (bottom left of the app)
- On the new page, click again on Sync your website
- Click on Get website files
- You need to synchronize the output folder you see in your file explorer, we will use minio client.
Be sure that you [configured minio client](cli.html#minio-client-recommended).
Then copy this output folder
```bash
mc mirror --overwrite output garage/my-site
```
## Generic (eg. Jekyll)
Some tools do not support sending to a S3 backend but output a compiled folder on your system.
We can then use any CLI tool to upload this content to our S3 target.
First, start by [configuring minio client](cli.html#minio-client-recommended).
Then build your website:
```bash
jekyll build
```
And copy jekyll's output folder on S3:
```bash
mc mirror --overwrite _site garage/my-site
```

View file

@ -0,0 +1,48 @@
# Exposing buckets as websites
You can expose your bucket as a website with this simple command:
```bash
garage bucket website --allow my-website
```
Now it will be **publicly** exposed on the web endpoint (by default listening on port 3902).
Our website serving logic is as follow:
- Supports only static websites (no support for PHP or other languages)
- Does not support directory listing
- The index is defined in your `garage.toml`. ([ref](/reference_manual/configuration.html#index))
Now we need to infer the URL of your website through your bucket name.
Let assume:
- we set `root_domain = ".web.example.com"` in `garage.toml` ([ref](/reference_manual/configuration.html#root_domain))
- our bucket name is `garagehq.deuxfleurs.fr`.
Our bucket will be served if the Host field matches one of these 2 values (the port is ignored):
- `garagehq.deuxfleurs.fr.web.example.com`: you can dedicate a subdomain to your users (here `web.example.com`).
- `garagehq.deuxfleurs.fr`: your users can bring their own domain name, they just need to point them to your Garage cluster.
You can try this logic locally, without configuring any DNS, thanks to `curl`:
```bash
# prepare your test
echo hello world > /tmp/index.html
mc cp /tmp/index.html garage/garagehq.deuxfleurs.fr
curl -H 'Host: garagehq.deuxfleurs.fr' http://localhost:3902
# should print "hello world"
curl -H 'Host: garagehq.deuxfleurs.fr.web.example.com' http://localhost:3902
# should also print "hello world"
```
Now that you understand how website logic works on Garage, you can:
- make the website endpoint listens on port 80 (instead of 3902)
- use iptables to redirect the port 80 to the port 3902:
`iptables -t nat -A PREROUTING -p tcp -dport 80 -j REDIRECT -to-port 3902`
- or configure a [reverse proxy](reverse_proxy.html) in front of Garage to add TLS (HTTPS), CORS support, etc.
You can also take a look at [Website Integration](/connect/websites.html) to see how you can add Garage to your workflow.

View file

@ -0,0 +1,31 @@
# Gateways
Gateways allow you to expose Garage endpoints (S3 API and websites) without storing data on the node.
## Benefits
You can configure Garage as a gateway on all nodes that will consume your S3 API, it will provide you the following benefits:
- **It removes 1 or 2 network RTT** Instead of (querying your reverse proxy then) querying a random node of the cluster that will forward your request to the nodes effectively storing the data, your local gateway will directly knows which node to query.
- **It ease server management** Instead of tracking in your reverse proxy and DNS what are the current Garage nodes, your gateway being part of the cluster keeps this information for you. In your software, you will always specify `http://localhost:3900`.
- **It simplifies security** Instead of having to maintain and renew a TLS certificate, you leverage the Secret Handshake protocol we use for our cluster. The S3 API protocol will be in plain text but limited to your local machine.
## Limitations
Currently it will not work with minio client. Follow issue [#64](https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/64) for more information.
## Spawn a Gateway
The instructions are similar to a regular node, the only option that is different is while configuring the node, you must set the `--gateway` parameter:
```bash
garage node configure --gateway --tag gw1 xxxx
```
Then use `http://localhost:3900` when a S3 endpoint is required:
```bash
aws --endpoint-url http://127.0.0.1:3900 s3 ls
```

View file

@ -1 +1,165 @@
# Configuring a reverse proxy
The main reason to add a reverse proxy in front of Garage is to provide TLS to your users.
In production you will likely need your certificates signed by a certificate authority.
The most automated way is to use a provider supporting the [ACME protocol](https://datatracker.ietf.org/doc/html/rfc8555)
such as [Let's Encrypt](https://letsencrypt.org/), [ZeroSSL](https://zerossl.com/) or [Buypass Go SSL](https://www.buypass.com/ssl/products/acme).
If you are only testing Garage, you can generate a self-signed certificate to follow the documentation:
```bash
openssl req \
-new \
-x509 \
-keyout /tmp/garage.key \
-out /tmp/garage.crt \
-nodes \
-subj "/C=XX/ST=XX/L=XX/O=XX/OU=XX/CN=localhost/emailAddress=X@X.XX" \
-addext "subjectAltName = DNS:localhost, IP:127.0.0.1"
cat /tmp/garage.key /tmp/garage.crt > /tmp/garage.pem
```
Be careful as you will need to allow self signed certificates in your client.
For example, with minio, you must add the `--insecure` flag.
An example:
```bash
mc ls --insecure garage/
```
## socat (only for testing purposes)
If you want to test Garage with a TLS frontend, socat can do it for you in a single command:
```bash
socat \
"openssl-listen:443,\
reuseaddr,\
fork,\
verify=0,\
cert=/tmp/garage.pem" \
tcp4-connect:localhost:3900
```
## Nginx
Nginx is a well-known reverse proxy suitable for production.
We do the configuration in 3 steps: first we define the upstream blocks ("the backends")
then we define the server blocks ("the frontends") for the S3 endpoint and finally for the web endpoint.
The following configuration blocks can be all put in the same `/etc/nginx/sites-available/garage.conf`.
To make your configuration active, run `ln -s /etc/nginx/sites-available/garage.conf /etc/nginx/sites-enabled/`.
If you directly put the instructions in the root `nginx.conf`, keep in mind that these configurations must be enclosed inside a `http { }` block.
And do not forget to reload nginx with `systemctl reload nginx` or `nginx -s reload`.
### Defining backends
First, we need to tell to nginx how to access our Garage cluster.
Because we have multiple nodes, we want to leverage all of them by spreading the load.
In nginx, we can do that with the upstream directive.
Because we have 2 endpoints: one for the S3 API and one to serve websites,
we create 2 backends named respectively `s3_backend` and `web_backend`.
A documented example for the `s3_backend` assuming you chose port 3900:
```nginx
upstream s3_backend {
# if you have a garage instance locally
server 127.0.0.1:3900;
# you can also put your other instances
server 192.168.1.3:3900;
# domain names also work
server garage1.example.com:3900;
# you can assign weights if you have some servers
# that are more powerful than others
server garage2.example.com:3900 weight=2;
}
```
A similar example for the `web_backend` assuming you chose port 3902:
```nginx
upstream web_backend {
server 127.0.0.1:3902;
server 192.168.1.3:3902;
server garage1.example.com:3902;
server garage2.example.com:3902 weight=2;
}
```
### Exposing the S3 API
The configuration section for the S3 API is simple as we only support path-access style yet.
We simply configure the TLS parameters and forward all the requests to the backend:
```nginx
server {
listen [::]:443 http2 ssl;
ssl_certificate /tmp/garage.crt;
ssl_certificate_key /tmp/garage.key;
# should be the endpoint you want
# aws uses s3.amazonaws.com for example
server_name garage.example.com;
location / {
proxy_pass http://s3_backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
```
### Exposing the web endpoint
The web endpoint is a bit more complicated to configure as it listens on many different `Host` fields.
To better understand the logic involved, you can refer to the [Exposing buckets as websites](/cookbook/exposing_websites.html) section.
Also, for some applications, you may need to serve CORS headers: Garage can not serve them directly but we show how we can use nginx to serve them.
You can use the following example as your starting point:
```nginx
server {
listen [::]:443 http2 ssl;
ssl_certificate /tmp/garage.crt;
ssl_certificate_key /tmp/garage.key;
# We list all the Hosts fields that can access our buckets
server_name *.web.garage
example.com
my-site.tld
;
location / {
# Add these headers only if you want to allow CORS requests
# For production use, more specific rules would be better for your security
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Expose-Headers Content-Length;
add_header Access-Control-Allow-Headers Range;
# We do not forward OPTIONS requests to Garage
# as it does not support them but they are needed for CORS.
if ($request_method = OPTIONS) {
return 200;
}
proxy_pass http://web_backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
```
## Apache httpd
@TODO
## Traefik
@TODO

View file

@ -1,9 +1,14 @@
# Starting Garage with systemd instead of Docker
# Starting Garage with systemd
We make some assumptions for this systemd deployment.
- Your garage binary is located at `/usr/local/bin/garage`.
- Your configuration file is located at `/etc/garage.toml`.
- Your `garage.toml` must be set with `metadata_dir=/var/lib/garage/meta` and `data_dir=/var/lib/garage/data`. This is mandatory to use `systemd` hardening feature [Dynamic User](https://0pointer.net/blog/dynamic-users-with-systemd.html). Note that in your host filesystem, Garage data will be held in `/var/lib/private/garage`.
NOTE: This guide is incomplete. Typicall you would also want to create a separate
Unix user to run Garage.
Make sure you have the Garage binary installed on your system (see [quick start](../quick_start/index.md)), e.g. at `/usr/local/bin/garage`.
Create a file named `/etc/systemd/system/garage.service`:
@ -15,12 +20,18 @@ Wants=network-online.target
[Service]
Environment='RUST_LOG=garage=info' 'RUST_BACKTRACE=1'
ExecStart=/usr/local/bin/garage server -c /etc/garage/garage.toml
ExecStart=/usr/local/bin/garage server
StateDirectory=garage
DynamicUser=true
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
```
*A note on hardening: garage will be run as a non privileged user, its user id is dynamically allocated by systemd. It cannot access (read or write) home folders (/home, /root and /run/user), the rest of the filesystem can only be read but not written, only the path seen as /var/lib/garage is writable as seen by the service (mapped to /var/lib/private/garage on your host). Additionnaly, the process can not gain new privileges over time.*
To start the service then automatically enable it at boot:
```bash