05/31 Update documents
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# PFMS — Server Deployment Guide
|
||||
> Ubuntu Server · Nginx · Gunicorn · MySQL · Groq API
|
||||
# PFM — Server Deployment Guide
|
||||
> Ubuntu Server · Nginx · Gunicorn · MySQL · Groq API · Certbot SSL
|
||||
|
||||
---
|
||||
|
||||
@@ -9,11 +9,16 @@
|
||||
|------|-------|
|
||||
| OS | Ubuntu 22.04 LTS |
|
||||
| App user | `pfm` |
|
||||
| App directory | `/home/pfm/app` |
|
||||
| Domain / IP | `pfm.ngodanguyen.tech` |
|
||||
| Home dir | `/home/pfm` |
|
||||
| App directory | `/home/pfm/web` |
|
||||
| Venv | `/home/pfm/venv` |
|
||||
| Uploads | `/home/pfm/web/uploads` |
|
||||
| Backups | `/home/pfm/web/backups` |
|
||||
| Domain | `pfm.ngodanguyen.tech` |
|
||||
| SSL | Let's Encrypt via Certbot |
|
||||
| MySQL version | 8.0 |
|
||||
| MySQL | 8.0 |
|
||||
| Python | 3.11+ |
|
||||
| Gitea | `gitea.ngodanguyen.tech` |
|
||||
|
||||
---
|
||||
|
||||
@@ -21,21 +26,22 @@
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install -y python3 python3-pip python3-venv python3-dev \
|
||||
sudo apt install -y \
|
||||
python3 python3-pip python3-venv python3-dev \
|
||||
build-essential libssl-dev libffi-dev \
|
||||
nginx mysql-server git curl wget \
|
||||
nginx mysql-server git curl wget unzip \
|
||||
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
|
||||
libffi-dev libjpeg-dev libopenjp2-7-dev
|
||||
libjpeg-dev libopenjp2-7-dev
|
||||
```
|
||||
|
||||
> `libpango*` and related libs are required by WeasyPrint for PDF generation.
|
||||
> `libpango*` libs required by WeasyPrint for PDF generation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create App User
|
||||
|
||||
```bash
|
||||
# Skip if user pfm already exists
|
||||
# Skip if pfm already exists
|
||||
id pfm &>/dev/null || sudo useradd -m -s /bin/bash pfm
|
||||
sudo usermod -aG www-data pfm
|
||||
```
|
||||
@@ -46,7 +52,7 @@ sudo usermod -aG www-data pfm
|
||||
|
||||
```bash
|
||||
sudo mysql_secure_installation
|
||||
# Follow prompts: set root password, remove anonymous users, disable remote root
|
||||
# Set root password, remove anonymous users, disable remote root
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -66,18 +72,21 @@ EXIT;
|
||||
## 4. App Directory & Virtual Environment
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /home/pfm/app
|
||||
sudo chown pfm:www-data /home/pfm/app
|
||||
sudo chmod 750 /home/pfm/app
|
||||
sudo mkdir -p /home/pfm/web
|
||||
sudo chown pfm:www-data /home/pfm/web
|
||||
sudo chmod 750 /home/pfm/web
|
||||
|
||||
sudo -u pfm bash
|
||||
cd /home/pfm/app
|
||||
cd /home/pfm
|
||||
|
||||
# Create venv at /home/pfm/venv
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Clone from your Gitea
|
||||
git clone https://gitea.yourdomain.com/youruser/pfm.git .
|
||||
# Clone from Gitea
|
||||
cd /home/pfm/web
|
||||
GIT_SSH_COMMAND='ssh -i /home/pfm/.ssh/gitea_deploy' \
|
||||
git clone git@gitea.ngodanguyen.tech:youruser/pfm.git .
|
||||
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
@@ -88,7 +97,7 @@ pip install -r requirements.txt
|
||||
## 5. Environment File
|
||||
|
||||
```bash
|
||||
sudo -u pfm nano /home/pfm/app/.env
|
||||
sudo -u pfm nano /home/pfm/web/.env
|
||||
```
|
||||
|
||||
```env
|
||||
@@ -96,43 +105,53 @@ SECRET_KEY=generate-a-long-random-string-here
|
||||
DATABASE_URL=mysql+pymysql://pfm_user:STRONG_PASSWORD_HERE@localhost/pfm_db
|
||||
GROQ_API_KEY=your-groq-api-key-here
|
||||
GROQ_MODEL=llama-3.3-70b-versatile
|
||||
UPLOAD_FOLDER=/home/pfm/app/uploads
|
||||
UPLOAD_FOLDER=/home/pfm/web/uploads
|
||||
MAX_CONTENT_LENGTH=10485760
|
||||
FLASK_ENV=production
|
||||
FLASK_APP=wsgi:app
|
||||
APP_CURRENCY=USD
|
||||
APP_CURRENCY_SYMBOL=$
|
||||
APP_TIMEZONE=America/New_York
|
||||
APP_TIMEZONE=Asia/Ho_Chi_Minh
|
||||
```
|
||||
|
||||
```bash
|
||||
chmod 600 /home/pfm/app/.env
|
||||
chmod 600 /home/pfm/web/.env
|
||||
```
|
||||
|
||||
Generate a secret key:
|
||||
Generate secret key:
|
||||
```bash
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Initialize Database
|
||||
## 6. Uploads & Backups Directories
|
||||
|
||||
```bash
|
||||
cd /home/pfm/app
|
||||
source venv/bin/activate
|
||||
|
||||
flask db upgrade # Run all migrations
|
||||
python scripts/init_db.py # Seed default categories + create admin user
|
||||
sudo mkdir -p /home/pfm/web/uploads
|
||||
sudo mkdir -p /home/pfm/web/backups
|
||||
sudo chown pfm:www-data /home/pfm/web/uploads
|
||||
sudo chown pfm:pfm /home/pfm/web/backups
|
||||
sudo chmod 750 /home/pfm/web/uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Upload Directory
|
||||
## 7. Initialize Database
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /home/pfm/app/uploads
|
||||
sudo chown pfm:www-data /home/pfm/app/uploads
|
||||
sudo chmod 750 /home/pfm/app/uploads
|
||||
cd /home/pfm/web
|
||||
source /home/pfm/venv/bin/activate
|
||||
|
||||
# Remove stray __init__.py from project root if present
|
||||
rm -f /home/pfm/web/__init__.py
|
||||
|
||||
flask db init
|
||||
flask db migrate -m "initial schema"
|
||||
flask db upgrade
|
||||
|
||||
# Seed categories + create admin user (interactive)
|
||||
python scripts/init_db.py
|
||||
```
|
||||
|
||||
---
|
||||
@@ -145,16 +164,17 @@ sudo nano /etc/systemd/system/pfm.service
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Personal Finance Management System
|
||||
Description=PFM — Personal Finance Management System
|
||||
After=network.target mysql.service
|
||||
|
||||
[Service]
|
||||
User=pfm
|
||||
Group=www-data
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/gunicorn \
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/gunicorn \
|
||||
--chdir /home/pfm/web \
|
||||
--workers 2 \
|
||||
--bind unix:/run/pfm/pfm.sock \
|
||||
--timeout 120 \
|
||||
@@ -173,7 +193,6 @@ WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
# Create log directory
|
||||
sudo mkdir -p /var/log/pfm
|
||||
sudo chown pfm:www-data /var/log/pfm
|
||||
|
||||
@@ -195,7 +214,6 @@ sudo nano /etc/nginx/sites-available/pfm
|
||||
server {
|
||||
listen 80;
|
||||
server_name pfm.ngodanguyen.tech;
|
||||
# Certbot will update this block automatically
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
@@ -203,7 +221,7 @@ server {
|
||||
listen 443 ssl http2;
|
||||
server_name pfm.ngodanguyen.tech;
|
||||
|
||||
# Certbot will populate these after running certbot
|
||||
# Certbot populates these:
|
||||
# ssl_certificate /etc/letsencrypt/live/pfm.ngodanguyen.tech/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/pfm.ngodanguyen.tech/privkey.pem;
|
||||
# include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
@@ -221,7 +239,7 @@ server {
|
||||
proxy_connect_timeout 10s;
|
||||
}
|
||||
|
||||
# AI chat uses SSE — disable buffering
|
||||
# AI chat SSE — disable buffering
|
||||
location /ai/stream {
|
||||
proxy_pass http://unix:/run/pfm/pfm.sock;
|
||||
proxy_set_header Host $host;
|
||||
@@ -235,13 +253,14 @@ server {
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
alias /home/pfm/app/app/static/;
|
||||
alias /home/pfm/web/app/static/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Receipts — served via Flask send_from_directory (internal)
|
||||
location /uploads/ {
|
||||
alias /home/pfm/app/uploads/;
|
||||
alias /home/pfm/web/uploads/;
|
||||
internal;
|
||||
}
|
||||
|
||||
@@ -261,78 +280,46 @@ sudo systemctl reload nginx
|
||||
## 9a. SSL — Certbot (Let's Encrypt)
|
||||
|
||||
```bash
|
||||
# Install Certbot + Nginx plugin
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
|
||||
# Issue certificate — auto-edits the Nginx config above
|
||||
# Issue cert — auto-edits Nginx config
|
||||
sudo certbot --nginx -d pfm.ngodanguyen.tech
|
||||
|
||||
# Follow prompts:
|
||||
# - Enter email for renewal notices
|
||||
# - Agree to TOS
|
||||
# - Choose redirect HTTP → HTTPS (recommended)
|
||||
|
||||
# Verify cert
|
||||
# Verify
|
||||
sudo certbot certificates
|
||||
```
|
||||
|
||||
Certbot automatically:
|
||||
- Populates the `ssl_certificate` lines in the Nginx config
|
||||
- Sets up HTTP → HTTPS redirect
|
||||
- Installs a systemd timer for auto-renewal
|
||||
|
||||
```bash
|
||||
# Test auto-renewal
|
||||
sudo certbot renew --dry-run
|
||||
|
||||
# Check renewal timer
|
||||
sudo systemctl status certbot.timer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Groq API Setup (Free AI)
|
||||
|
||||
No local installation needed. Groq runs in the cloud — just get a free API key.
|
||||
|
||||
### 10.1 Get API Key
|
||||
## 10. Groq API Setup
|
||||
|
||||
1. Sign up at **https://console.groq.com**
|
||||
2. Go to **API Keys** → **Create API key**
|
||||
3. Copy the key into `/home/pfm/app/.env` as `GROQ_API_KEY`
|
||||
2. API Keys → Create API key
|
||||
3. Add to `/home/pfm/web/.env` as `GROQ_API_KEY`
|
||||
|
||||
### 10.2 Free Tier Limits
|
||||
### Models in Use
|
||||
|
||||
| Metric | Limit |
|
||||
|--------|-------|
|
||||
| Requests/day | 14,400 |
|
||||
| Tokens/minute | 500,000 |
|
||||
| Tokens/day | 500,000 (varies by model) |
|
||||
| Cost | Free |
|
||||
| Model | Used For |
|
||||
|-------|----------|
|
||||
| `llama-3.3-70b-versatile` | Chat + daily insights (default) |
|
||||
| `llama-3.1-8b-instant` | Fast queries (user-selectable) |
|
||||
| `meta-llama/llama-4-scout-17b-16e-instruct` | Receipt OCR (vision) — hardcoded in ocr_service.py |
|
||||
|
||||
More than enough for a personal finance assistant.
|
||||
|
||||
### 10.3 Recommended Models
|
||||
|
||||
| Model | Speed | Quality | Best For |
|
||||
|-------|-------|---------|----------|
|
||||
| `llama-3.3-70b-versatile` | Fast | High | Default — chat + insights |
|
||||
| `llama-3.1-8b-instant` | Very fast | Good | Quick queries, daily summaries |
|
||||
|
||||
Set in `.env` via `GROQ_MODEL`. Switchable from Settings UI.
|
||||
|
||||
### 10.4 Verify Key Works
|
||||
### Free Tier Limits
|
||||
14,400 requests/day · 500,000 tokens/minute · Free
|
||||
|
||||
### Verify Key
|
||||
```bash
|
||||
curl https://api.groq.com/openai/v1/chat/completions -H "Authorization: Bearer YOUR_GROQ_API_KEY" -H "Content-Type: application/json" -d '{
|
||||
"model": "llama-3.3-70b-versatile",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"max_tokens": 10
|
||||
}'
|
||||
curl https://api.groq.com/openai/v1/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_GROQ_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"llama-3.3-70b-versatile","messages":[{"role":"user","content":"Hello"}],"max_tokens":10}'
|
||||
```
|
||||
|
||||
Should return a JSON response with a greeting.
|
||||
|
||||
---
|
||||
|
||||
## 11. Scheduled Jobs (systemd timers)
|
||||
@@ -342,28 +329,25 @@ Should return a JSON response with a greeting.
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-recurring.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Process Recurring Transactions
|
||||
Description=PFM — Process Recurring Transactions
|
||||
After=mysql.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/python scripts/process_recurring.py
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/python scripts/process_recurring.py
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-recurring.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Recurring Transactions Timer
|
||||
Description=PFM — Recurring Transactions Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 06:00:00
|
||||
@@ -373,32 +357,61 @@ Persistent=true
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### 11.2 Fetch Investment Prices (Daily 4PM weekdays)
|
||||
### 11.2 Fetch USD/VND Rate (Daily 8AM)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-prices.service
|
||||
sudo nano /etc/systemd/system/pfm-fxrate.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Fetch Investment Prices
|
||||
Description=PFM — Fetch USD/VND Exchange Rate
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/python scripts/fetch_prices.py
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/python scripts/fetch_fx_rate.py
|
||||
```
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-fxrate.timer
|
||||
```
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFM — FX Rate Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 08:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### 11.3 Fetch Investment Prices (Mon-Fri 4PM)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-prices.service
|
||||
```
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFM — Fetch Investment Prices
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/python scripts/fetch_prices.py
|
||||
```
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-prices.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Price Fetch Timer
|
||||
Description=PFM — Price Fetch Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=Mon-Fri *-*-* 16:00:00
|
||||
@@ -408,32 +421,29 @@ Persistent=true
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### 11.3 Net Worth Snapshot (1st of month)
|
||||
### 11.4 Net Worth Snapshot (1st of month)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-snapshot.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Net Worth Snapshot
|
||||
Description=PFM — Net Worth Snapshot
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/python scripts/daily_snapshot.py
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/python scripts/daily_snapshot.py
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-snapshot.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Net Worth Snapshot Timer
|
||||
Description=PFM — Snapshot Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-01 00:05:00
|
||||
@@ -443,33 +453,30 @@ Persistent=true
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### 11.4 AI Daily Insight (Midnight)
|
||||
### 11.5 AI Daily Insight (Midnight)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-aiinsight.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — AI Daily Insight
|
||||
Description=PFM — AI Daily Insight
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/python scripts/daily_ai_insight.py
|
||||
WorkingDirectory=/home/pfm/web
|
||||
Environment="PATH=/home/pfm/venv/bin"
|
||||
EnvironmentFile=/home/pfm/web/.env
|
||||
ExecStart=/home/pfm/venv/bin/python scripts/daily_ai_insight.py
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-aiinsight.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — AI Insight Timer
|
||||
Description=PFM — AI Insight Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00:01:00
|
||||
@@ -479,41 +486,6 @@ Persistent=true
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### 11.5 Fetch USD/VND Rate (Daily 8AM)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-fxrate.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Fetch USD/VND Exchange Rate
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
WorkingDirectory=/home/pfm/app
|
||||
Environment="PATH=/home/pfm/app/venv/bin"
|
||||
EnvironmentFile=/home/pfm/app/.env
|
||||
ExecStart=/home/pfm/app/venv/bin/python scripts/fetch_fx_rate.py
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-fxrate.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — FX Rate Fetch Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 08:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
### Enable All Timers
|
||||
|
||||
```bash
|
||||
@@ -530,12 +502,56 @@ sudo systemctl list-timers --all | grep pfm
|
||||
|
||||
---
|
||||
|
||||
## 12. Log Rotation
|
||||
## 12. Database Backup (Daily 2AM)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-backup.service
|
||||
```
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFM — MySQL Backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
ExecStart=/bin/bash -c 'mysqldump --defaults-file=/home/pfm/.my.cnf pfm_db | gzip > /home/pfm/web/backups/pfm_$(date +\%Y\%m\%d).sql.gz && find /home/pfm/web/backups -name "*.sql.gz" -mtime +30 -delete'
|
||||
```
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-backup.timer
|
||||
```
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFM — Backup Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 02:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now pfm-backup.timer
|
||||
```
|
||||
|
||||
Store MySQL credentials for backup job:
|
||||
```bash
|
||||
sudo -u pfm bash -c 'cat > /home/pfm/.my.cnf << EOF
|
||||
[client]
|
||||
user=pfm_user
|
||||
password=STRONG_PASSWORD_HERE
|
||||
EOF
|
||||
chmod 600 /home/pfm/.my.cnf'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Log Rotation
|
||||
|
||||
```bash
|
||||
sudo nano /etc/logrotate.d/pfm
|
||||
```
|
||||
|
||||
```
|
||||
/var/log/pfm/*.log {
|
||||
daily
|
||||
@@ -553,59 +569,6 @@ sudo nano /etc/logrotate.d/pfm
|
||||
|
||||
---
|
||||
|
||||
## 13. Database Backup (Daily)
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-backup.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — MySQL Backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=pfm
|
||||
ExecStart=/bin/bash -c 'mysqldump --defaults-file=/home/pfm/.my.cnf pfm_db | gzip > /home/pfm/app/backups/pfm_$(date +\%Y\%m\%d).sql.gz'
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/pfm-backup.timer
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=PFMS — Daily Backup Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 02:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /home/pfm/app/backups
|
||||
sudo chown pfm:pfm /home/pfm/app/backups
|
||||
sudo systemctl enable --now pfm-backup.timer
|
||||
```
|
||||
|
||||
> Store MySQL credentials securely for the backup job:
|
||||
> ```bash
|
||||
> sudo -u pfm bash -c 'cat > /home/pfm/.my.cnf << EOF
|
||||
> [client]
|
||||
> user=pfm_user
|
||||
> password=STRONG_PASSWORD_HERE
|
||||
> EOF
|
||||
> chmod 600 /home/pfm/.my.cnf'
|
||||
> ```
|
||||
|
||||
> Keep last 30 days. Add a cleanup cron to delete older files:
|
||||
> `find /home/pfm/app/backups -name "*.sql.gz" -mtime +30 -delete`
|
||||
|
||||
---
|
||||
|
||||
## 14. Firewall (UFW)
|
||||
|
||||
```bash
|
||||
@@ -616,21 +579,30 @@ sudo ufw enable
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
> MySQL (3306) is NOT opened — internal only. Groq calls go outbound over HTTPS (port 443).
|
||||
> MySQL (3306) — internal only. Groq/yfinance — outbound HTTPS (443).
|
||||
|
||||
---
|
||||
|
||||
## 15. Deploy Update Workflow
|
||||
## 15. Gitea Deploy Key
|
||||
|
||||
```bash
|
||||
sudo -u pfm ssh-keygen -t ed25519 -f /home/pfm/.ssh/gitea_deploy -N ""
|
||||
cat /home/pfm/.ssh/gitea_deploy.pub
|
||||
# Add to Gitea: repo → Settings → Deploy Keys
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Deploy Update Workflow
|
||||
|
||||
```bash
|
||||
# SSH into server
|
||||
sudo -u pfm bash
|
||||
cd /home/pfm/app
|
||||
source venv/bin/activate
|
||||
cd /home/pfm/web
|
||||
source /home/pfm/venv/bin/activate
|
||||
|
||||
git pull origin main
|
||||
pip install -r requirements.txt # if deps changed
|
||||
flask db upgrade # if migrations added
|
||||
pip install -r requirements.txt # only if requirements changed
|
||||
flask db upgrade # only if new migrations
|
||||
|
||||
sudo systemctl restart pfm
|
||||
sudo systemctl status pfm
|
||||
@@ -638,35 +610,50 @@ sudo systemctl status pfm
|
||||
|
||||
---
|
||||
|
||||
## 16. Quick Status Check
|
||||
## 17. First-Run Checklist
|
||||
|
||||
```bash
|
||||
# All PFMS services
|
||||
sudo systemctl status pfm
|
||||
sudo systemctl status nginx
|
||||
sudo systemctl status mysql
|
||||
# 1. Force fresh FX rate
|
||||
source /home/pfm/venv/bin/activate
|
||||
cd /home/pfm/web
|
||||
python scripts/fetch_fx_rate.py
|
||||
# Should print: 1 USD = ₫26,xxx VND [yfinance]
|
||||
|
||||
# Logs (live)
|
||||
sudo journalctl -u pfm -f
|
||||
sudo tail -f /var/log/pfm/error.log
|
||||
sudo tail -f /var/log/nginx/pfm_error.log
|
||||
# 2. Verify all timers
|
||||
sudo systemctl list-timers --all | grep pfm
|
||||
|
||||
# Groq API check
|
||||
# 3. Test AI
|
||||
curl https://api.groq.com/openai/v1/models \
|
||||
-H "Authorization: Bearer $(grep GROQ_API_KEY /home/pfm/app/.env | cut -d= -f2)"
|
||||
-H "Authorization: Bearer $(grep GROQ_API_KEY /home/pfm/web/.env | cut -d= -f2)"
|
||||
|
||||
# 4. Check app logs
|
||||
sudo journalctl -u pfm -f
|
||||
|
||||
# 5. Full service status
|
||||
sudo systemctl status pfm nginx mysql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. Gitea Integration (your self-hosted)
|
||||
## 18. Quick Status Reference
|
||||
|
||||
```bash
|
||||
# On server — set up deploy key
|
||||
sudo -u pfm ssh-keygen -t ed25519 -f /home/pfm/.ssh/gitea_deploy -N ""
|
||||
cat /home/pfm/.ssh/gitea_deploy.pub
|
||||
# Add this public key to Gitea repo → Settings → Deploy Keys
|
||||
# Services
|
||||
sudo systemctl status pfm
|
||||
sudo systemctl status nginx
|
||||
sudo systemctl status mysql
|
||||
|
||||
# Clone with deploy key
|
||||
sudo -u pfm GIT_SSH_COMMAND='ssh -i /home/pfm/.ssh/gitea_deploy' \
|
||||
git clone git@gitea.yourdomain.com:youruser/pfm.git /home/pfm/app
|
||||
# Live logs
|
||||
sudo journalctl -u pfm -f
|
||||
sudo tail -f /var/log/pfm/error.log
|
||||
sudo tail -f /var/log/nginx/pfm_error.log
|
||||
|
||||
# Timers
|
||||
sudo systemctl list-timers --all | grep pfm
|
||||
|
||||
# Run a cron manually
|
||||
source /home/pfm/venv/bin/activate && cd /home/pfm/web
|
||||
python scripts/fetch_fx_rate.py
|
||||
python scripts/process_recurring.py
|
||||
python scripts/daily_ai_insight.py
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user