Use this guide if you'd rather not pipe a script into bash, or if the installer doesn't fit
your setup (a different layout, an existing Docker host, a non-standard user). It does by
hand exactly what install.sh does, so reading that script is the best
reference if something here is unclear.
For the one-command route, see the Quick start in the README.
- A 64-bit Linux machine with working Bluetooth (
systemctl is-active bluetoothprintsactive), within range of your sensors. git, Docker Engine, and the Docker Compose plugin (docker compose versionworks — not the olddocker-compose). Your user must be able to rundockerwithoutsudo.
git clone https://github.com/alfarme/canopy.git ~/canopy
cd ~/canopyYou need three values:
- Bot token — message @BotFather in Telegram, send
/newbot, and copy the token. - Your user id — message @userinfobot.
- Chat id — where digests go. Your own user id works for a private chat (send the bot a message first so it may write to you); for a group, add the bot and use the group's negative id.
cp .env.example .env
chmod 600 .envEdit .env and set:
| Variable | Value |
|---|---|
DATA_DIR |
Absolute path without spaces for all persistent data, e.g. /home/you/canopy-data |
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_ADMIN_USER_ID |
The three values from step 3 |
POSTGRES_PASSWORD, GF_SECURITY_ADMIN_PASSWORD |
Any strong passwords (letters and digits are safest). Generate one with head -c 24 /dev/urandom | base64 | tr -dc A-Za-z0-9 |
HOST_UID, HOST_GID |
Add these two lines yourself: the output of id -u and id -g. Grafana, the Telegram bot and Canopy UI run as this user so they can write to DATA_DIR. |
Optional: GRAFANA_PORT, CANOPY_UI_PORT, MQTT_HOST, BLE_SCAN_TIME,
BLE_TIME_BETWEEN_SCANS (defaults are fine), and the plant-ID keys
PLANTNET_API_KEY, OPENPLANTBOOK_CLIENT_ID, OPENPLANTBOOK_CLIENT_SECRET (see the README).
set -a; source .env; set +a
mkdir -p "$DATA_DIR"/{pgdata,backups,mosquitto/config,mosquitto/data,mosquitto/log,grafana,identify-photos,canopy-cache,deploy-history,env}
cat > "$DATA_DIR/mosquitto/config/mosquitto.conf" <<'MOSQ'
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
MOSQThe broker allows anonymous clients, so keep port 1883 on your local network.
The containers read their secrets from files under $DATA_DIR/env/. Still in the same shell:
DB_URL="postgresql://canopy:${POSTGRES_PASSWORD}@timescaledb:5432/canopy"
printf 'POSTGRES_PASSWORD=%s\n' "$POSTGRES_PASSWORD" > "$DATA_DIR/env/timescaledb.env"
printf 'DATABASE_URL=%s\nMQTT_HOST=mosquitto\nMQTT_PORT=1883\nLOG_LEVEL=INFO\n' "$DB_URL" \
> "$DATA_DIR/env/ingest.env"
printf 'DATABASE_URL=%s\nLOG_LEVEL=INFO\n' "$DB_URL" > "$DATA_DIR/env/decision.env"
printf 'DATABASE_URL=%s\n' "$DB_URL" > "$DATA_DIR/env/canopy-ui.env"
cat > "$DATA_DIR/env/telegram.env" <<EOT
DATABASE_URL=${DB_URL}
TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
TELEGRAM_ADMIN_USER_ID=${TELEGRAM_ADMIN_USER_ID}
PLANTNET_API_KEY=${PLANTNET_API_KEY:-}
OPENPLANTBOOK_CLIENT_ID=${OPENPLANTBOOK_CLIENT_ID:-}
OPENPLANTBOOK_CLIENT_SECRET=${OPENPLANTBOOK_CLIENT_SECRET:-}
LOG_LEVEL=INFO
EOT
cat > "$DATA_DIR/env/grafana.env" <<EOT
GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
GF_TS_PASSWORD=${POSTGRES_PASSWORD}
TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
EOT
chmod 600 "$DATA_DIR"/env/*.envGrafana needs the Telegram token and chat id in its own file as well. Without them it refuses
to start. These files are generated from .env: change .env and redo this step rather than
editing them.
./deploy.shThis builds the images on the machine (the first build takes several minutes on a Pi), starts the stack, and checks that every container is running. Without the script, the equivalent is:
set -a; source .env; set +a # compose reads its variables from the environment
docker compose -f compose/docker-compose.yml up -d --build
docker restart cp-grafana # pick up bind-mounted provisioning changes( crontab -l 2>/dev/null; echo "0 3 * * * DATA_DIR=$DATA_DIR $HOME/canopy/tools/backup_db.sh >> $DATA_DIR/backups/backup.log 2>&1" ) | crontab -Dumps land in $DATA_DIR/backups; the last 14 days are kept.
Canopy UI is at http://<host>:8090, Grafana at http://<host>:3000 (user admin, password
from .env). Then follow the first-run checklist.
cd ~/canopy
git pull --ff-only
# redo step 6 if .env changed, then:
./deploy.shcd ~/canopy
set -a; source .env; set +a
docker compose -f compose/docker-compose.yml down
crontab -l | grep -vF tools/backup_db.sh | crontab - # remove the backup jobYour data stays in $DATA_DIR; delete that directory to remove it for good.