# Caddy Reverse Proxy

- [Caddy Reverse Proxy](#caddy-reverse-proxy)
  - [Custom Caddy Image](#custom-caddy-image)
  - [Install Caddy](#install-caddy)
    - [Ansible](#ansible)
    - [Manual](#manual)
  - [Adding a new Caddy Record](#adding-a-new-caddy-record)
  - [Logs](#logs)
  - [Caddy WAF](#caddy-waf)

## Custom Caddy Image

This repo builds a custom caddy image with route53 DNS certbot support.

```bash
podman image pull gitea.reeseapps.com/services/caddy:latest
```

To upgrade the image, check [the caddy-dns route53
project](https://github.com/caddy-dns/route53/tags) releases and update the
`Containerfile` with the new version.

## Install Caddy

### Ansible

You'll need a secrets/Caddyfile with your caddy config.

`secrets/Caddyfile` example:

```conf
https://something.reeseapps.com:443 {
    reverse_proxy internal.reeselink.com:8000
}

https://something-else.reeseapps.com:443 {
    reverse_proxy internal-other.reeselink.com:8080
}
```

Make sure to add [your route53 configuration](https://github.com/caddy-dns/route53?tab=readme-ov-file#configuration)

```conf
tls {
    dns route53 {
        access_key_id "..."
        secret_access_key "..."
        region "us-east-1"
        wait_for_route53_sync true
        skip_route53_sync_on_delete true
        route53_max_wait 2m
        max_retries 5
    }
}
```

The playbook limits the installer to `hosts: caddy` so make sure you have a caddy
host in your inventory.

Now you can install the Caddy service with something like:

```bash
# Base Proxy
ansible-playbook \
-i ansible/inventory.yaml \
active/container_caddy/install_caddy_proxy.yaml

# Deskwork (AI) Proxy
ansible-playbook \
-i ansible/inventory.yaml \
active/container_caddy/install_caddy_deskwork.yaml

# Toybox (AI) Proxy
ansible-playbook \
-i ansible/inventory.yaml \
active/container_caddy/install_caddy_toybox.yaml
```

See ansible playbook [install_caddy.yaml](/active/container_caddy/install_caddy.yaml)

### Manual

As root

```bash
mkdir /etc/caddy
vim /etc/caddy/Caddyfile
```

Caddy will automatically provision certificates if the server DNS points to the correct IP
and is accessible on the ports specifified. All you need to do is put `https` in the caddy conf.

Example:

```conf
# Gitea
https://gitea.reeseapps.com:443 {
    reverse_proxy podman.reeselink.com:3000
}

# Jellyfin
https://jellyfin.reeseapps.com:443 {
    reverse_proxy podman.reeselink.com:8096
}
```

```bash
vim /etc/containers/systemd/caddy.container
```

```conf
[Unit]
Description=Caddy

[Container]
AddCapability=NET_ADMIN
ContainerName=caddy
Image=docker.io/caddy:2
Network=host
SecurityLabelDisable=true
Volume=/etc/caddy:/etc/caddy
Volume=caddy_data:/data
Volume=caddy_config:/config

[Service]
Restart=always

[Install]
WantedBy=default.target
```

```bash
systemctl daemon-reload
systemctl restart caddy
```

## Adding a new Caddy Record

Before you can create a Caddyfile you need records that point to your server.

You can either create them manually in your DNS provider of choice or use the provided
ddns service:

1. Update the [ddns caddy records](/active/container_ddns/secrets/caddy_records.yaml)
2. (Optional) Update the Caddyfile at `active/container_caddy/secrets/Caddyfile`
3. Run the [caddy ansible playbook](/active/container_caddy/caddy.md#install-caddy)

## Logs

```bash
# Follow remote connections
podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host}'

# Filter out noisy hosts
podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host} | select(.host != "gitea.reeseapps.com")'

# Focus on user agents
podman logs -f caddy | grep -e '^{' | jq -c '
  {
    "User-Agent":   .request.headers["User-Agent"],
    remote_ip:      .request.remote_ip,
    host:           .request.host,
    status:         .status
  }
'
```

## Caddy WAF

<https://github.com/fabriziosalmi/caddy-waf>

1. Copy the rules.json to `/etc/caddy/rules.json`
2. Update the Caddyfile to something like this:

    ```Caddyfile
    gitea.reeseapps.com:443 {
        log {
            output stdout
            format json {
                message_key     msg           # Key for the log message
                level_key      severity       # Key for the log level
                time_key       timestamp      # Key for the timestamp
                name_key       logger         # Key for the logger name
                caller_key     function       # Key for the caller information
                stacktrace_key stack          # Key for error stacktraces
                time_format    "2006-01-02 15:04:05 MST"  # RFC3339-like format
                time_local                    # Use local timezone
                duration_format "ms"          # Show durations in milliseconds
                level_format   "upper"        # Uppercase log levels
            }
        }
        route {
            waf {
                metrics_endpoint   /waf_metrics
                rule_file          rules.json
            }

            @wafmetrics {
                path /waf_metrics
            }

            handle @wafmetrics { }   # empty → let the WAF serve the metrics

            handle {
                reverse_proxy gitea.reeselink.com:3000
            }
        }
    }
    ```
