Hostityourself/docs/setup.md

433 lines
10 KiB
Markdown

# HostItYourself — Raspberry Pi Setup Guide
End-to-end guide to turn a fresh Raspberry Pi into a self-hosted PaaS.
After completing this guide you will have:
- Automatic HTTPS for every deployed app via Caddy + Cloudflare
- `git push → live` deploys triggered by GitHub webhooks
- A web dashboard at `hiy.yourdomain.com`
- Firewall, fail2ban, and SSH key-only hardening
---
## Hardware
| Component | Minimum | Recommended |
|---|---|---|
| Pi model | Raspberry Pi 4 4 GB | Raspberry Pi 4/5 8 GB |
| Storage | 32 GB microSD (A2) | 64 GB+ USB SSD |
| Network | Wi-Fi | Wired Ethernet |
| Power | Official USB-C PSU | PSU + UPS hat |
A USB SSD is strongly preferred — builds involve a lot of small file I/O that
kills microSD cards within months.
---
## 1. Install Raspberry Pi OS
1. Download **Raspberry Pi Imager** and flash **Raspberry Pi OS Lite (64-bit)**.
2. Before writing, click the gear icon (⚙) and:
- Enable SSH with a public-key (paste your `~/.ssh/id_ed25519.pub`)
- Set a hostname, e.g. `hiypi`
- Set your Wi-Fi credentials (or leave blank for wired)
3. Insert the SD/SSD and boot the Pi.
Verify you can SSH in:
```bash
ssh pi@hiypi.local
```
---
## 2. First boot — update and configure
```bash
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y git curl ufw fail2ban unattended-upgrades
```
### Static IP (optional but recommended)
Edit `/etc/dhcpcd.conf` and add at the bottom (adjust for your network):
```
interface eth0
static ip_address=192.168.1.50/24
static routers=192.168.1.1
static domain_name_servers=1.1.1.1 8.8.8.8
```
```bash
sudo reboot
```
---
## 3. Harden SSH
Disable password authentication so only your key can log in:
```bash
sudo nano /etc/ssh/sshd_config
```
Set / verify:
```
PasswordAuthentication no
PermitRootLogin no
```
```bash
sudo systemctl restart ssh
```
Test that you can still log in with your key before closing the current session.
---
## 4. Firewall
```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (redirect)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status
```
### fail2ban
The default config already jails repeated SSH failures. Restart to apply:
```bash
sudo systemctl enable fail2ban --now
```
---
## 5. Install Docker
> **Note:** Do **not** run `apt install docker-ce` directly — that package
> requires Docker's own apt repository to be added first.
> Use one of the two methods below.
**Option A — Official convenience script (recommended, installs latest Docker CE):**
```bash
curl -fsSL https://get.docker.com | sh
```
**Option B — Debian-packaged `docker.io` (older but simpler, no extra repo needed):**
```bash
sudo apt install -y docker.io
```
Then, regardless of which option you used:
```bash
sudo usermod -aG docker pi # allow running docker without sudo
newgrp docker # apply group without logging out
docker run --rm hello-world # verify
```
---
## 6. Domain and DNS (Cloudflare)
You need a domain whose DNS is managed by Cloudflare (the free plan is fine).
1. In Cloudflare, add two DNS records pointing to your **public home IP**:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | `hiy` | `<your-public-IP>` | Proxied (orange cloud) |
| A | `*` | `<your-public-IP>` | Proxied (orange cloud) |
The wildcard `*` record routes `<appname>.yourdomain.com` to the Pi.
2. In **Cloudflare → SSL/TLS → Overview**, set mode to **Full (strict)**.
3. Create a **Cloudflare API token** for Caddy's ACME DNS challenge:
- Go to My Profile → API Tokens → Create Token
- Use the **Edit zone DNS** template, scope it to your zone
- Copy the token — you will need it in step 8
### Port forwarding
Forward the following ports on your router to the Pi's static IP:
| External | Internal |
|---|---|
| 80/tcp | 80 |
| 443/tcp | 443 |
---
## 7. Clone the platform
```bash
git clone https://github.com/YOUR_USER/Hostityourself.git ~/hiy-platform
cd ~/hiy-platform
```
---
## 8. Configure the platform
Copy and edit the environment file:
```bash
cp infra/.env.example infra/.env # if it doesn't exist, create it
nano infra/.env
```
Minimum required variables:
```env
# Your domain (apps will be at <name>.yourdomain.com)
DOMAIN_SUFFIX=yourdomain.com
# Cloudflare API token for ACME DNS-01 challenge
CF_API_TOKEN=your_cloudflare_api_token
```
---
## 9. Caddy — automatic HTTPS
Caddy handles TLS via Cloudflare's DNS challenge (no port-80 HTTP challenge
needed, works even behind CGNAT).
Edit `proxy/caddy.json` and replace the top-level config with:
```json
{
"admin": { "listen": "0.0.0.0:2019" },
"apps": {
"tls": {
"automation": {
"policies": [{
"subjects": ["*.yourdomain.com", "yourdomain.com"],
"issuers": [{
"module": "acme",
"challenges": {
"dns": {
"provider": {
"name": "cloudflare",
"api_token": "{env.CF_API_TOKEN}"
}
}
}
}]
}]
}
},
"http": {
"servers": {
"hiy": {
"listen": [":80", ":443"],
"routes": [
{
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "server:3000"}]
}]
}
]
}
}
}
}
}
```
> **Note:** The Caddy image in `docker-compose.yml` needs the Cloudflare DNS
> plugin. Use `caddy:2-alpine` with `xcaddy` or replace the image with
> `ghcr.io/caddybuilds/caddy-cloudflare:latest`.
---
## 10. Start the platform
```bash
cd ~/hiy-platform
docker compose --env-file infra/.env up -d --build
docker compose logs -f # watch startup
```
When ready you should see:
```
hiy-server | Listening on http://0.0.0.0:3000
```
Open the dashboard: **https://hiy.yourdomain.com**
---
## 11. Deploy your first app
### From the dashboard
1. Open `https://hiy.yourdomain.com`
2. Fill in the **Add App** form:
- **Name** — a URL-safe slug, e.g. `my-api`
- **GitHub Repo URL** — `https://github.com/you/my-api.git`
- **Branch** — `main`
- **Container Port** — the port your app listens on (e.g. `3000`)
3. Click **Create App**, then **Deploy** on the new row.
4. Watch the build log in the app detail page.
5. Once done, your app is live at `https://my-api.yourdomain.com`.
### Build strategy detection
The build engine picks a strategy automatically:
| What's in the repo | Strategy |
|---|---|
| `Dockerfile` | `docker build` |
| `package.json` / `yarn.lock` | Cloud Native Buildpack (Node) |
| `requirements.txt` / `pyproject.toml` | Cloud Native Buildpack (Python) |
| `go.mod` | Cloud Native Buildpack (Go) |
| `static/` or `public/` directory | Caddy static file server |
A `Dockerfile` always takes precedence. For buildpack strategies, the `pack`
CLI must be installed on the Pi (see below).
### Install `pack` (for buildpack projects)
```bash
curl -sSL https://github.com/buildpacks/pack/releases/latest/download/pack-linux-arm64.tgz \
| sudo tar -xz -C /usr/local/bin
pack --version
```
---
## 12. Set up GitHub webhooks (auto-deploy on push)
For each app:
1. Go to the app detail page → **GitHub Webhook** section.
Copy the **Payload URL** and **Secret**.
2. Open the GitHub repo → **Settings → Webhooks → Add webhook**:
| Field | Value |
|---|---|
| Payload URL | `https://hiy.yourdomain.com/webhook/<app-id>` |
| Content type | `application/json` |
| Secret | *(from the app detail page)* |
| Events | Just the **push** event |
3. Click **Add webhook**. GitHub will send a ping — check **Recent Deliveries**
for a green tick.
After this, every `git push` to the configured branch triggers a deploy.
---
## 13. Backups
Create a daily backup cron job:
```bash
sudo nano /etc/cron.daily/hiy-backup
```
```bash
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/var/backups/hiy
DATE=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"
# Database
docker exec hiy-platform-server-1 sh -c \
'sqlite3 /data/hiy.db .dump' > "$BACKUP_DIR/db-$DATE.sql"
# Env files
tar -czf "$BACKUP_DIR/env-$DATE.tar.gz" \
-C /var/lib/docker/volumes/hiy-platform_hiy-data/_data env/ 2>/dev/null || true
# Keep 30 days
find "$BACKUP_DIR" -mtime +30 -delete
echo "HIY backup complete: $BACKUP_DIR"
```
```bash
sudo chmod +x /etc/cron.daily/hiy-backup
```
To copy backups off-Pi, add an `rclone copy` line pointing to S3, Backblaze B2,
or any rclone remote.
---
## 14. Monitoring (optional)
### Netdata — per-container resource metrics
```bash
curl https://my-netdata.io/kickstart.sh | bash
```
Access at `http://hiypi.local:19999` or add a Caddy route for
`netdata.yourdomain.com`.
### Gatus — HTTP uptime checks
Create `~/gatus/config.yaml`:
```yaml
endpoints:
- name: my-api
url: https://my-api.yourdomain.com/health
interval: 1m
conditions:
- "[STATUS] == 200"
alerts:
- type: email
description: "my-api is down"
```
```bash
docker run -d --name gatus \
-p 8080:8080 \
-v ~/gatus:/config \
twinproduction/gatus
```
---
## 15. Updating the platform itself
```bash
cd ~/hiy-platform
git pull origin main
docker compose --env-file infra/.env up -d --build
```
Zero-downtime: the old containers keep serving traffic while the new ones build.
Caddy never restarts.
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Dashboard not reachable | Compose not started / port 443 not forwarded | `docker compose logs server` |
| TLS certificate error | CF_API_TOKEN wrong or DNS not propagated | Check Caddy logs: `docker compose logs caddy` |
| Build fails — "docker not found" | Docker socket not mounted | Verify `docker-proxy` service is up |
| Build fails — "pack not found" | `pack` not installed | See step 11 |
| Webhook returns 401 | Secret mismatch | Regenerate app, re-copy secret to GitHub |
| Webhook returns 404 | Wrong app ID in URL | Check app ID on the dashboard |
| App runs but 502 from browser | Container port wrong | Check **Container Port** matches what the app listens on |
| Container shows "exited" | App crashed on startup | `docker logs hiy-<app-id>` |