Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 120 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,122 @@
# cloud
Cloud Setup using Docker for FindMyCat
# FindMyCat Cloud

Docker-based cloud stack for [FindMyCat](https://www.findmycat.io) — runs the MQTT broker, GPS tracking server, reverse proxy, and web app on a single VPS.

Please checkout the latest docs on how to use this repository to setup FindMyCat cloud: https://www.findmycat.io/docs/CloudSetup
## Stack

| Service | Compose file | Purpose |
|---|---|---|
| EMQX | `docker-compose.emqx.yml` | MQTT / MQTT-SN broker for device communication |
| Traccar | `docker-compose.traccar.yml` | GPS tracking server and REST + WebSocket API |
| Nginx Proxy Manager | `docker-compose.ngnix-proxy.yml` | TLS termination, Let's Encrypt certificates, reverse proxy |
| Web app | `docker-compose.webapp.yml` | Browser-based tracker UI (local dev only; production is served by Traccar) |

All services share the `cloud_emqx-bridge` Docker network.

---

## Prerequisites

- A Linux VPS (tested on Ubuntu 22.04+)
- Docker and Docker Compose installed
- A domain name pointing at the server
- An EMQX Enterprise licence (`tmp/emqx.lic`)
- A Hologram account with an API key and organisation ID (for device commands)

---

## Deployment

### 1. Clone and configure

```bash
git clone https://github.com/FindMyCat/cloud
cd cloud
```

Place your EMQX licence at `tmp/emqx.lic`.

### 2. Start Nginx Proxy Manager

```bash
docker compose -f docker-compose.ngnix-proxy.yml up -d
```

Open `http://your-server:81` and create a Proxy Host for your domain pointing at `traccar:8082`. Enable **Websockets Support** — required for real-time GPS updates.

### 3. Start EMQX

```bash
docker compose -f docker-compose.emqx.yml up -d
```

Dashboard: `http://your-server:18083` (default credentials: `admin` / `public` — change immediately).

Ports used by devices:
- `1883` — MQTT TCP
- `1885/udp` — MQTT-SN (UDP, used by FindMyCat trackers)

### 4. Start Traccar

```bash
docker compose -f docker-compose.traccar.yml up -d
```

Traccar reads `traccar.xml` from the repo root. The default config uses an embedded H2 database stored in `/var/docker/traccar/data`. Key settings already in place:

- `server.sessionTimeout` = 604800 (7-day session, keeps the web app and iOS app logged in)

Traccar's web UI is reachable at `https://your-domain.com` once NPM is configured.

### 5. Deploy the web app

See [web-app/DEPLOY.md](web-app/DEPLOY.md) for the full guide. Summary:

```bash
./deploy-webapp.sh user@your-server
```

This rsyncs the web app files to `/var/docker/traccar/web/cat/` — Traccar serves them at `https://your-domain.com/cat/`. Create `config.js` on the server from the example and fill in your Hologram credentials.

You also need a `/hologram/` custom location in NPM to proxy the Hologram API for Sound / Lost-mode — details in [web-app/DEPLOY.md](web-app/DEPLOY.md).

---

## Configuration files

| File | Purpose | Committed |
|---|---|---|
| `traccar.xml` | Traccar server config | Yes |
| `emqx_conf/emqx_sn.conf` | MQTT-SN plugin config | Yes |
| `tmp/emqx.lic` | EMQX Enterprise licence | **No** — add manually |
| `web-app/config.js` | Web app runtime config (API keys, tile URL) | **No** — create from `config.example.js` |

---

## Updating

Each service can be updated independently:

```bash
# Traccar
docker compose -f docker-compose.traccar.yml pull && docker compose -f docker-compose.traccar.yml up -d

# EMQX (pinned to 4.4.16 — do not upgrade without checking MQTT-SN compatibility)
docker compose -f docker-compose.emqx.yml up -d

# Web app (no container restart needed — Traccar serves files from disk)
./deploy-webapp.sh user@your-server
```

---

## Local development

```bash
cp web-app/config.example.js web-app/config.js # fill in Hologram values
docker compose -f docker-compose.webapp.yml up -d
```

Open `http://localhost:8080/cat/`. This uses an nginx container that proxies `/api/` to the `traccar` container and `/hologram/` to `dashboard.hologram.io`. Requires the Traccar stack to be running locally.

For UI-only work (no login needed): `./web-app/dev.sh`
37 changes: 37 additions & 0 deletions deploy-webapp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Deploy the FindMyCat web app to the server.
# Usage: ./deploy-webapp.sh user@your-server

set -euo pipefail

SERVER="${1:?Usage: $0 user@your-server}"
REMOTE_DIR="/var/docker/traccar/web/cat"
LOCAL_DIR="$(dirname "$0")/web-app"

echo "==> Creating remote directory..."
ssh "$SERVER" "mkdir -p $REMOTE_DIR"

echo "==> Syncing web app files..."
rsync -av --delete \
--exclude='config.js' \
--exclude='*.md' \
--exclude='nginx-local.conf' \
--exclude='dev.sh' \
"$LOCAL_DIR/" "$SERVER:$REMOTE_DIR/"

echo ""
echo "==> Done. Checking config.js on server..."
if ssh "$SERVER" "test -f $REMOTE_DIR/config.js"; then
echo " config.js exists — no changes made."
else
echo " config.js MISSING. Creating from template..."
scp "$LOCAL_DIR/config.example.js" "$SERVER:$REMOTE_DIR/config.js"
echo ""
echo " *** ACTION REQUIRED ***"
echo " Edit config.js on the server and fill in your Hologram credentials:"
echo " ssh $SERVER"
echo " nano $REMOTE_DIR/config.js"
fi

echo ""
echo "==> Web app live at: https://<your-domain>/cat/"
1 change: 1 addition & 0 deletions docker-compose.traccar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
- "/opt/traccar/logs:/opt/traccar/logs"
- "./traccar.xml:/opt/traccar/conf/traccar.xml:ro"
- "/var/docker/traccar/data:/opt/traccar/data:rw"
- "/var/docker/traccar/web/cat:/opt/traccar/web/cat:ro"
networks:
emqx-bridge:
aliases:
Expand Down
18 changes: 18 additions & 0 deletions docker-compose.webapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: '3.8'
services:
webapp:
image: nginx:alpine
container_name: webapp
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./web-app:/usr/share/nginx/html:ro
- ./web-app/nginx-local.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- emqx-bridge

networks:
emqx-bridge:
external: true
name: cloud_emqx-bridge
2 changes: 2 additions & 0 deletions traccar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@
<entry key='database.user'>sa</entry>
<entry key='database.password'></entry>

<entry key='server.sessionTimeout'>604800</entry>

</properties>
161 changes: 161 additions & 0 deletions web-app/DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# FindMyCat Web App — Deployment Guide

## Where does the web app live?

**Bundled inside the cloud repo** (`github.com/FindMyCat/cloud`) as a `web-app/` subdirectory.

Why: the deployment step *is* a cloud repo operation — one volume line in `docker-compose.traccar.yml` and a copy command on the server. Keeping both in the same repo means one clone, one place to make changes, and a single deploy script. There's no build step, so the source files are the artifact.

If you ever intend to open-source just the web app (not the infra config), extract it to its own repo at that point.

---

## Two ways to run it

| Mode | What serves the app | Use for |
|---|---|---|
| **Production** | Traccar's Jetty (volume mount) behind Nginx Proxy Manager | The real deployment |
| **Local** | `docker-compose.webapp.yml` (nginx:alpine container) | Testing against a locally running Traccar stack |

`dev.sh` also exists for pure UI work (static file server, no `/api` or `/hologram` proxy — login and commands will not work).

---

# Production deployment

## Prerequisites

- The cloud stack is already running (Traccar reachable at `https://your-domain.com`)
- SSH access to the server
- NPM admin UI accessible at `http://your-server:81`
- Traccar is behind NPM with HTTPS and a Let's Encrypt cert

## Step 1 — Volume mount (already in the repo)

`docker-compose.traccar.yml` already mounts the host directory into Traccar's Jetty web root, so the app is served at `/cat/` by the same server that handles `/api/` — same-origin, no CORS config needed:

```yaml
- "/var/docker/traccar/web/cat:/opt/traccar/web/cat:ro"
```

If your running stack predates this line, apply it: `docker compose -f docker-compose.traccar.yml up -d`

## Step 2 — Deploy the files

From the repo root:

```bash
./deploy-webapp.sh user@your-server
```

This rsyncs `web-app/` to `/var/docker/traccar/web/cat/` (excluding `config.js`, docs, and local-dev files) and creates `config.js` from the template if it's missing.

## Step 3 — Configure config.js on the server

`config.js` is gitignored and lives only on the server. It is the only file that differs between environments.

```javascript
window.FINDMYCAT_CONFIG = {
traccarHost: "", // leave BLANK — served same-origin, no host needed
hologramApiKey: "", // leave blank if NPM injects the auth header (recommended, see Step 5)
hologramOrgId: 12345, // your Hologram organisation ID
hologramPort: 12345, // UDP port the tracker listens on for cloud messages
tileUrl: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
tileAttribution: "&copy; <a href='https://openstreetmap.org'>OpenStreetMap</a> contributors",
};
```

`traccarHost` must be blank (empty string) when the web app is served from the same domain as Traccar. All `/api/` calls become relative — same-origin, cookie sent automatically.

## Step 4 — Enable WebSocket support in NPM

The real-time position stream uses WebSocket (`wss://`). NPM must forward the `Upgrade` header or the connection silently fails (devices load once but never update).

1. Open NPM admin: `http://your-server:81`
2. Find the Proxy Host for your Traccar domain
3. Edit → **Websockets Support: ON**
4. Save

## Step 5 — Hologram proxy (REQUIRED for Sound / Lost mode)

The web app calls Hologram via relative `/hologram/` paths (the browser cannot call `dashboard.hologram.io` directly — CORS). Without this proxy the Sound and Lost-mode buttons return 404.

On the Traccar proxy host in NPM, add custom Nginx config in the **Advanced** tab:

```nginx
location /hologram/ {
rewrite ^/hologram/(.*)$ /api/1/$1 break;
proxy_pass https://dashboard.hologram.io;
proxy_ssl_server_name on;
proxy_set_header Host dashboard.hologram.io;
# Recommended: inject the key server-side so it never ships to browsers.
# base64 of "apikey:YOUR_HOLOGRAM_API_KEY":
proxy_set_header Authorization "Basic YOUR_BASE64_VALUE_HERE";
}
```

With the `Authorization` line present, leave `hologramApiKey` blank in `config.js` — the key stays off the client entirely. Without it, the browser sends the header itself using `hologramApiKey` from `config.js` (works, but anyone who can reach `/cat/config.js` can read the key).

## Step 6 — Verify

Open `https://your-domain.com/cat/` in a browser.

- Login screen appears; after login the map loads and devices appear as emoji markers
- Positions update in real time (DevTools → Network → WS tab shows an open `/api/socket` connection)
- Sound / Lost-mode buttons return success toasts (DevTools → Network: `/hologram/devices/...` returns 200)
- On mobile: "Add to Home Screen" installs the PWA

## Updating the web app

```bash
./deploy-webapp.sh user@your-server
```

No container restart needed — Jetty serves static files directly from disk. The service worker cache name (`findmycat-vN` in `service-worker.js`) is bumped on app changes so clients pick up new assets.

---

# Local deployment (docker compose)

Runs the app in an `nginx:alpine` container that serves `/cat/` and proxies `/api/` + `/api/socket` to the `traccar` container and `/hologram/` to Hologram (config in `web-app/nginx-local.conf`).

```bash
cp web-app/config.example.js web-app/config.js # then fill in Hologram values
docker compose -f docker-compose.webapp.yml up -d
```

Open `http://localhost:8080/cat/`.

Assumptions:

- The Traccar stack is running and was started **from this repo directory** (the compose file joins the external network `cloud_emqx-bridge`; Docker Compose prefixes network names with the project name, which defaults to the directory name — clone the repo as anything other than `cloud` and you must adjust the `name:` under `networks:` in `docker-compose.webapp.yml`).
- Service workers require HTTPS **or localhost** — the PWA/offline features work at `http://localhost:8080` but not over plain HTTP from another machine.
- For Hologram commands, either set `hologramApiKey` in `config.js` (browser sends the auth header through the proxy) or hardcode the `Authorization` header in `nginx-local.conf` as the comment there shows.

---

# Operational notes

## Traccar session timeout

The repo's `traccar.xml` already sets a 7-day session (`server.sessionTimeout` = 604800) so an emergency login from a phone browser isn't lost when the tab closes. Note this applies to **all** Traccar clients, including the iOS app.

## Tile source

The map uses the public OSM tile CDN by default (`tileUrl` in `config.js`). For offline/rural use, point `tileUrl` at a self-hosted tile server.

---

# Feature status vs the native iOS app

Implemented and at parity: login with session restore, live map with emoji markers, real-time WebSocket updates, device drawer with battery % and relative last-seen (refreshed every 20 s like iOS), ping/lost mode via Hologram, logout (web-only — iOS has none), offline banner, loading/error states, installable PWA.

| Not implemented | Notes |
|---|---|
| Add / edit / delete device | iOS has full CRUD with an emoji picker. Web calls would be `POST/PUT/DELETE /api/devices`. |
| Reverse-geocoded address per device | iOS uses Apple CLGeocoder client-side. Web equivalent would need Nominatim or Traccar's `address` field. |
| BLE pairing / UWB Precise Find | Not possible in browsers (Web Bluetooth is Chromium-desktop only; no NearbyInteraction). |
| Push notifications | Requires a VAPID push server. |
| Satellite map toggle | Add a second raster source + toggle. |

The app fully covers the primary use case: **open URL → log in → see your cat's location on a map in real time, and trigger lost mode**.
Loading