==========
Enabling SSH
sudo apt update
sudo apt install openssh-server
sudo systemctl status ssh
# allow in firewall
sudo ufw allow ssh
==========
Change username
Disable auto login
Enable ssh
Enable root:
sudo passwd
sudo nano /etc/ssh/sshd_config
PermitRootLogin yes
sudo service ssh restart
Log-in with root
usermod -l <new_user> pi
usermod -m -d /home/<new_user> <new_user>
==========
Enable remote desktop
sudo apt-get install xrdp
==========
Static IP address for host (wlan0,eth0) /etc/dhcpcd.conf
static ip_address=192.168.1.254/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
interface wlan0
static ip_address=192.168.1.253/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
==========
==========
Enabling SSH
raspi-config
==========
Renew SSH key
ssh-keygen -R <host>
==========
Automatically mount drive
To ensure your internal hard drive mounts automatically at boot on a Raspberry Pi 5, you can configure it in /etc/fstab using its UUID. This method is reliable because device names like /dev/sda1 can change after reboots.
Identify the Drive
List block devices and UUIDs:
sudo blkid
Check filesystem type:
sudo lsblk -f
Create a Mount Point
sudo mkdir /mnt/mydrive
sudo chmod 770 /mnt/mydrive
Replace /mnt/mydrive with your preferred path.
Edit fstab for Auto-Mount
Backup the current file:
sudo cp /etc/fstab /etc/fstab.backup
Edit fstab:
sudo nano /etc/fstab
Add a line using the UUID and filesystem type: For ext4:
UUID=9c10aaa9-97eb-473c-b89a-a60e96d1145a /mnt/mydrive ext4 defaults,noatime,nofail,x-systemd.device-timeout=30 0 2
Important:
nofail prevents boot failure if the drive is missing.
x-systemd.device-timeout=30 avoids long boot delays.
Test Without Reboot
sudo mount -a
Check if the drive is mounted:
df -h
Best Practices
Always use UUID instead of /dev/sda1 to avoid device name changes.
If using NTFS, install driver support:
sudo apt install ntfs-3g
Keep a backup of /etc/fstab to recover from misconfigurations.
Once configured, your Raspberry Pi 5 will mount the internal HDD automatically at every boot without manual intervention.
==========
Install Docker
https://docs.docker.com/engine/install/debian/
==========
Install Caddy
https://caddyserver.com/docs/install#debian-ubuntu-raspbian
# After editing dockerfile
sudo systemctl reload caddy
==========
Transfer files/directory using PSCP
pscp -r -P 22 -pw password C:\source\\ user@server.local:/destination/
==========
Configure sleep pattern
systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target
vi /etc/systemd/logind.conf
==========
Configure Samba
sudo apt-get update
sudo apt-get install samba
whereis samba or samba --version
optional: mkdir /home/<username>/sambashare/
sudo nano /etc/samba/smb.conf
Append text:
[sambashare]
comment = Samba on Ubuntu
path = /home/username/sambashare
read only = no
guest ok = yes
writable = yes
browsable = yes
OR: uncomment [homes] share definition
sudo service smbd restart
sudo ufw allow samba
sudo smbpasswd -a username
===========
Clear SSH keys
ssh-keygen -R <host>
===========
https://docs.docker.com/engine/install/ubuntu/
docker compose up -d
================
Install Docker (Alternative)
sudo apt-get install docker.io
OR
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
=================
Add user to the Docker group
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
=================
Pods:
https://github.com/the-markp/wd-pod-svc.git
https://github.com/the-markp/wd-pod.git
=================
Delete all docker images in host machine
sudo docker rmi $(sudo docker images -q)
=================
Delete all docker volumes
docker volume rm $(sudo docker volume ls -q)
==========
Install home assistant
docker run -d --name="home-assistant" -v /home/ha_config:/config -v /home/foxriver:/media -v /etc/localtime:/etc/localtime:ro --net=host homeassistant/home-assistant:stable
docker run --init -d --name="home-assistant" -e "TZ=America/New_York" -v /home/ha_config:/config --net=host homeassistant/raspberrypi3-homeassistant:stable
docker run --init -d --name="home-assistant" -e "TZ=America/New_York" -v /PATH_TO_YOUR_CONFIG:/config --net=host homeassistant/raspberrypi4-homeassistant:stable
sudo docker update --restart always home-assistant
==========
New Install home assistant
sudo docker run -d \
--name homeassistant \
--privileged \
--restart=unless-stopped \
-e TZ=Asia/Manila \
-v /home/foxriver/ha_config:/config \
--network=host \
ghcr.io/home-assistant/home-assistant:stable
==========
==========
Inflate/deflate compressed file
unzip "file"
zip -r output.zip file1 input_directory
==========
Mount disk using fsdisk
blkid
sudo nano /etc/fstab
Append: "UUID=<UUID of disk>" /mountpoint ntfs defaults 2 0
==========
Install Git
Step 1: Update your system
sudo apt update && sudo apt full-upgrade
This ensures your Raspberry Pi OS is up to date before installing new software .
Step 2: Install Git
sudo apt install git
This installs Git from the official Raspberry Pi OS repositories .
Step 3: Verify installation
git --version
You should see the installed Git version displayed.
Step 4: Configure Git
git config --global user.name "Your Name"
git config --global user.email "youremail@example.com"
This sets your identity for commits .
==========
Git commit
git add <files>
git commit -m "comment"
git push
==========
Git config and clone
git config --global github.user YOUR_USERNAME
git config --global github.token YOURTOKEN
git clone git@github.com:YOUR_USERNAME/YOUR_PROJECT.git
==========
Git branch
# Show all local and remote branches
git branch -a
# Delete branch
git branch -d branch_name
==========
Zip folder
Compress-Archive -Path C:\source\ -DestinationPath C:\destination\
==========
Sleep CLI Windows 11
powercfg -h off
rundll32.exe powrprof.dll, SetSuspendState Sleep
==========
Delete specific file type within subfolders
get-childitem -path C:\QQQ -include *.raw -recurse | remove-item
==========
Extended private:
[Channel 1] [EPVT]: 430.025, CTCSS 100Hz
Planned frequencies (private UHF):
[Channel 3] [UPVT1]: 432.025MHz, CDCSS 703N
[Channel 5] [UPVT2]: 439.975MHz, CDCSS 703N
GMRS interop:
[Channel 2] [GMRS2]: 462.5875MHz
[Channel 4] [GMRS4]: 462.6375MHz
[Channel 6] [GMRS6]: 462.6875MHz
[Channel 7-10]: BF-888 default ON BF
BF interop ON CIG:
[Channel 7] [BF888_7]: 462.725MHz, CTCSS 136.5Hz
[Channel 8] [BF888_8]: 462.825MHz, CTCSS 162.2Hz
Planned frequencies (private VHF) ON CIG and other VHF:
[Channel 9] [VPVT1]: 142.025MHz, CTCSS 100Hz
[Channel 10] [VPVT2]: 144.025MHz, CDCSS 703N
[Channel 11] [DX3F_U]: 434.9200MHz, +5M, CTCSS 88.5Hz
[Channel 12] [DX3F_U]: 434.0250MHz, +5M, CTCSS 88.5Hz
[Channel 13] [DX2N_U]: 434.5000MHz, +5M, CTCSS 114.8Hz
[Channel 14] [DX3F_V]: 144.9400MHz, -0.6M, CTCSS 88.5Hz
[Channel 15] [DX2LA_V]: 144.9000MHz, -0.6M
[Channel 16] [TAR_V]: 144.7400MHz, -0.6M
[Channel 17] [DX3NE_V]: 144.120MHz, +0.6M, CTCSS 88.5Hz
[Channel 18] [DX2N_V]: 144.8600MHz, -0.6M, CTCSS 103.5Hz
Communications satellites
AO-91 (RadFxSat / Fox-1B), UL: 435.250 MHz:CTCSS 67Hz, DL: 145.960 MHz [Channel 19]
SO-50 (SaudiSat-1C), UL: 145.850 MHz:CTCSS 67Hz, DL: 436.795 MHz [Channel 20]
PO-101 (Diwata-2), UL: 437.500 MHz: CTCSS 141.3Hz, DL: 145.900 MHz, [Channel 21]
CAS-3H (LilacSat-2), UL: 144.350 MHz, DL: 437.200 MHz [Channel 22]
IO-86 (LAPAN-A2), UL: 145.880 MHz:CTCSS 88.5Hz, DL: 435.880 MHz [Channel 23]
ISS, UL: 145.990 MHz:CTCSS 67Hz, DL: 437.800 MHz [Channel 24]
Aeronautical radionavigation:
117.795-137.000
Space-to-Earth:
137.000-138.000
Government simplex SRD:
138.025-140.000
Military:
140.025-142.000
Non-government:
142.025-143.600
Space research simplex:
143.600-144.000
PH Amateur:
144.000-146.000
Distress:
145.000MHz
Maritime distress:
156.800MHz
PH Amateur:
430.000-440.000
Install Caddy
touch .env
DB_PASSWORD=0918
SECRET_KEY=f3a8b2c91d7e4f5a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c6d8e0f2a
docker compose -f docker-compose.db.yml up -d
Restore database from backup, if needed
docker compose -f docker-compose.app.yml up -d
Edit Caddyfile
This covers a production-grade local deployment with automatic backups and a tested restore procedure.
Ubuntu 22.04 / 24.04 LTS (or any systemd-based distro)
At least 2 GB RAM, 20 GB disk
You have sudo access
bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker # apply group without logout
# Install Docker Compose v2
sudo apt install -y docker-compose-plugin
# Verify
docker --version
docker compose version
bash
# Create a dedicated app user (no login shell — security best practice)
sudo useradd -r -m -d /opt/gtracker -s /usr/sbin/nologin gtracker
sudo usermod -aG docker gtracker
# Create directory structure
sudo mkdir -p /opt/gtracker/{app,backups,logs}
sudo chown -R gtracker:gtracker /opt/gtracker
# Copy your app files
sudo cp -r resort-assets/* /opt/gtracker/app/
sudo chown -R gtracker:gtracker /opt/gtracker/app
Replace the development compose file with this hardened version:
bash
sudo nano /opt/gtracker/app/docker-compose.yml
yaml
version: "3.9"
services:
db:
image: postgres:16-alpine
container_name: gtracker_db
environment:
POSTGRES_DB: palma_assets
POSTGRES_USER: palma
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- postgres_data:/var/lib/postgresql/data
- /opt/gtracker/backups:/backups # backup scripts write here
healthcheck:
test: ["CMD-SHELL", "pg_isready -U palma -d palma_assets"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
restart: unless-stopped
networks:
- gtracker_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
app:
build:
context: .
dockerfile: Dockerfile
container_name: gtracker_app
environment:
DATABASE_URL: "postgresql+asyncpg://palma@db:5432/palma_assets"
SECRET_KEY_FILE: /run/secrets/app_secret
TOKEN_EXPIRE_MINUTES: "480"
secrets:
- db_password
- app_secret
ports:
- "127.0.0.1:8000:8000" # bind to localhost only — Nginx handles public access
depends_on:
db:
condition: service_healthy
networks:
- gtracker_net
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
secrets:
db_password:
file: /opt/gtracker/secrets/db_password.txt
app_secret:
file: /opt/gtracker/secrets/app_secret.txt
volumes:
postgres_data:
networks:
gtracker_net:
driver: bridge
Note: The app currently reads DATABASE_URL and SECRET_KEY as plain env vars. The _FILE pattern above requires a small change to app/database.py and app/auth.py to read those files — see step 5 below.
bash
# Create secrets directory (readable only by root/gtracker)
sudo mkdir -p /opt/gtracker/secrets
sudo chmod 700 /opt/gtracker/secrets
# Generate a strong DB password
openssl rand -base64 32 | sudo tee /opt/gtracker/secrets/db_password.txt
# Generate a strong JWT secret
openssl rand -base64 64 | sudo tee /opt/gtracker/secrets/app_secret.txt
# Lock down permissions
sudo chmod 600 /opt/gtracker/secrets/*.txt
sudo chown root:gtracker /opt/gtracker/secrets/*.txt
Then update app/database.py and app/auth.py to support the _FILE convention:
python
# Utility — add to a new file: app/config.py
import os
def read_secret(env_var: str, file_env_var: str = None) -> str:
"""Read a value from an env var, or from a file pointed to by <env_var>_FILE."""
if file_env_var:
path = os.getenv(file_env_var)
if path and os.path.exists(path):
return open(path).read().strip()
return os.getenv(env_var, "")
Then in database.py:
python
from app.config import read_secret
DATABASE_URL = read_secret("DATABASE_URL", "DATABASE_URL_FILE")
And in auth.py:
python
from app.config import read_secret
SECRET_KEY = read_secret("SECRET_KEY", "SECRET_KEY_FILE")
For now, the simplest approach is to just use plain env vars in an .env file with tight permissions:
bash
sudo nano /opt/gtracker/app/.env
DATABASE_URL=postgresql+asyncpg://palma:YOUR_PASSWORD@db:5432/palma_assets
SECRET_KEY=YOUR_64_CHAR_SECRET
TOKEN_EXPIRE_MINUTES=480
bash
sudo chmod 600 /opt/gtracker/app/.env
sudo chown gtracker:gtracker /opt/gtracker/app/.env
Never expose the app port directly. Use Nginx to terminate HTTPS and proxy to Docker.
bash
sudo apt install -y nginx
# Create site config
sudo nano /etc/nginx/sites-available/gtracker
nginx
server {
listen 80;
server_name your-server-ip-or-hostname;
# Redirect HTTP → HTTPS (uncomment after setting up SSL)
# return 301 https://$host$request_uri;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
client_max_body_size 10M;
}
}
bash
sudo ln -s /etc/nginx/sites-available/gtracker /etc/nginx/sites-enabled/
sudo nginx -t # test config
sudo systemctl reload nginx
Optional — free HTTPS with Let's Encrypt (if your server has a domain name):
bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
Make Docker Compose start automatically on boot and restart on failure.
bash
sudo nano /etc/systemd/system/gtracker.service
ini
[Unit]
Description=G-Tracker Asset Management
After=docker.service network-online.target
Requires=docker.service
StartLimitIntervalSec=60
StartLimitBurst=3
[Service]
Type=oneshot
RemainAfterExit=yes
User=gtracker
Group=gtracker
WorkingDirectory=/opt/gtracker/app
ExecStart=/usr/bin/docker compose up -d --build
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose pull && /usr/bin/docker compose up -d
Restart=on-failure
RestartSec=10s
StandardOutput=append:/opt/gtracker/logs/service.log
StandardError=append:/opt/gtracker/logs/service.log
[Install]
WantedBy=multi-user.target
bash
sudo systemctl daemon-reload
sudo systemctl enable gtracker
sudo systemctl start gtracker
sudo systemctl status gtracker
Automated daily backup script
bash
sudo nano /opt/gtracker/backups/backup.sh
bash
#!/usr/bin/env bash
# backup.sh — Daily PostgreSQL dump + Docker volume snapshot
set -euo pipefail
BACKUP_DIR="/opt/gtracker/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DB_CONTAINER="gtracker_db"
DB_USER="palma"
DB_NAME="palma_assets"
RETAIN_DAYS=30 # keep 30 days of backups
echo "[$(date)] Starting backup..."
# ── 1. PostgreSQL logical dump (plain SQL, easiest to restore) ────────────────
DUMP_FILE="$BACKUP_DIR/db_${TIMESTAMP}.sql.gz"
docker exec "$DB_CONTAINER" \
pg_dump -U "$DB_USER" "$DB_NAME" \
| gzip > "$DUMP_FILE"
echo "[$(date)] DB dump written: $DUMP_FILE ($(du -sh $DUMP_FILE | cut -f1))"
# ── 2. Verify the dump is non-empty ──────────────────────────────────────────
if [ ! -s "$DUMP_FILE" ]; then
echo "[$(date)] ERROR: Dump file is empty! Backup failed." >&2
exit 1
fi
# ── 3. Rotate old backups ─────────────────────────────────────────────────────
find "$BACKUP_DIR" -name "db_*.sql.gz" -mtime +${RETAIN_DAYS} -delete
echo "[$(date)] Old backups cleaned (kept last ${RETAIN_DAYS} days)"
# ── 4. Log summary ────────────────────────────────────────────────────────────
BACKUP_COUNT=$(find "$BACKUP_DIR" -name "db_*.sql.gz" | wc -l)
echo "[$(date)] Backup complete. Total backups on disk: $BACKUP_COUNT"
bash
sudo chmod +x /opt/gtracker/backups/backup.sh
sudo chown gtracker:gtracker /opt/gtracker/backups/backup.sh
Schedule with cron (runs daily at 2:00 AM):
bash
sudo crontab -u gtracker -e
0 2 * * * /opt/gtracker/backups/backup.sh >> /opt/gtracker/logs/backup.log 2>&1
Test it immediately:
bash
sudo -u gtracker /opt/gtracker/backups/backup.sh
ls -lh /opt/gtracker/backups/
This is the part most people skip documenting — don't. Practice this before you need it.
Restore the database from a dump
bash
# ── Step 1: Identify the backup to restore ────────────────────────────────────
ls -lht /opt/gtracker/backups/db_*.sql.gz | head -10
# Pick a file, e.g.:
RESTORE_FILE="/opt/gtracker/backups/db_20250605_020000.sql.gz"
# ── Step 2: Stop the app (prevent writes during restore) ─────────────────────
sudo systemctl stop gtracker
# ── Step 3: Start only the database ──────────────────────────────────────────
cd /opt/gtracker/app
docker compose up -d db
sleep 5 # wait for postgres to be ready
# ── Step 4: Drop and recreate the database ────────────────────────────────────
docker exec -it gtracker_db \
psql -U palma -c "DROP DATABASE IF EXISTS palma_assets;"
docker exec -it gtracker_db \
psql -U palma -c "CREATE DATABASE palma_assets;"
# ── Step 5: Restore the dump ─────────────────────────────────────────────────
gunzip -c "$RESTORE_FILE" | \
docker exec -i gtracker_db psql -U palma palma_assets
echo "Restore complete."
# ── Step 6: Verify row counts ─────────────────────────────────────────────────
docker exec -it gtracker_db \
psql -U palma palma_assets -c \
"SELECT 'assets' AS table, COUNT(*) FROM assets
UNION ALL
SELECT 'users', COUNT(*) FROM users
UNION ALL
SELECT 'categories', COUNT(*) FROM categories;"
# ── Step 7: Bring the full app back up ────────────────────────────────────────
sudo systemctl start gtracker
Local backups protect against software failure but not disk failure. Copy backups to another location nightly.
Option A — rsync to another machine:
bash
# Add to crontab (runs 30 min after backup, so dump is ready)
30 2 * * * rsync -az /opt/gtracker/backups/ \
backup-user@192.168.1.200:/backups/gtracker/
Option B — AWS S3 (cheapest cloud option, ~$0.02/GB/month):
bash
sudo apt install -y awscli
aws configure # enter your AWS key, secret, region
# Add to crontab
30 2 * * * aws s3 sync /opt/gtracker/backups/ \
s3://your-bucket/gtracker-backups/ \
--storage-class STANDARD_IA # infrequent access = cheaper
bash
# View live logs
docker compose -f /opt/gtracker/app/docker-compose.yml logs -f
# Restart app only (not DB)
docker compose -f /opt/gtracker/app/docker-compose.yml restart app
# Deploy a new version
cd /opt/gtracker/app
git pull # if using git
sudo systemctl reload gtracker # triggers compose pull + up
# Check disk usage (backups + DB volume)
du -sh /opt/gtracker/backups/
docker system df
# Manual backup right now
sudo -u gtracker /opt/gtracker/backups/backup.sh
# Check backup log
tail -50 /opt/gtracker/logs/backup.log
#
Task
Command to verify
✅
Docker installed
docker --version
✅
App running
systemctl status gtracker
✅
Nginx proxying
curl -I http://localhost
✅
systemd auto-start
systemctl is-enabled gtracker
✅
Daily backup cron
crontab -u gtracker -l
✅
Backup file present
ls /opt/gtracker/backups/
✅
Restore tested
Do a test restore to verify
✅
Offsite backup
Check S3 or remote server
The most important step is item 7 — test a restore before you need it. A backup you've never restored is a backup you can't trust.