Macro Tracker runs on a single Ubuntu server with Apache serving the static frontend and reverse-proxying API requests to a Node.js backend.
Browser
│
├─ Static files (HTML/CSS/JS/icons) ──► Apache ──► /var/www/macros.stephens.page/
│
└─ /api/* requests ──► Apache (ProxyPass) ──► Node.js on port 3457
- Frontend: Vite builds static assets into
dist/. These are copied to/var/www/macros.stephens.page/and served directly by Apache. The app uses hash-based routing (#/login,#/recipes, etc.), so Apache always servesindex.html— no server-side routing needed. - Backend: Express server compiled from TypeScript, managed by systemd, listening on
localhost:3457. Apache proxies any request starting with/apito this backend. - Database: SQLite file at
/home/jacob/MacroTracker/server/data/macros.db. Schema auto-creates on first run and auto-migrates on restart. - SSL: Let's Encrypt certificate via Certbot, auto-renewed.
- PWA: Service worker generated by vite-plugin-pwa precaches the app shell. The
manifest.webmanifestandassetlinks.jsonfor TWA are served as static files.
HTTP (/etc/apache2/sites-available/macros.stephens.page.conf):
Redirects all HTTP traffic to HTTPS.
HTTPS (/etc/apache2/sites-available/macros.stephens.page-le-ssl.conf):
<VirtualHost *:443>
ServerName macros.stephens.page
DocumentRoot /var/www/macros.stephens.page
# Static frontend files
<Directory /var/www/macros.stephens.page>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# Reverse proxy API to Node.js
ProxyPreserveHost On
ProxyPass /api http://127.0.0.1:3457/api
ProxyPassReverse /api http://127.0.0.1:3457/api
# SSL (Let's Encrypt)
SSLCertificateFile /etc/letsencrypt/live/macros.stephens.page/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/macros.stephens.page/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>Required Apache modules: proxy, proxy_http, ssl, rewrite.
Unit file (/etc/systemd/system/macros-api.service):
[Unit]
Description=Macro Tracker API
After=network.target
[Service]
Type=simple
User=jacob
WorkingDirectory=/home/jacob/MacroTracker/server
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=JWT_SECRET=<secret>
Environment=PORT=3457
Environment=USDA_API_KEY=<key>
Environment=FATSECRET_CLIENT_ID=<client_id>
Environment=FATSECRET_CLIENT_SECRET=<client_secret>
Environment=SMTP_HOST=smtp.mandrillapp.com
Environment=SMTP_PORT=587
Environment=SMTP_USER=<user>
Environment=SMTP_PASS=<pass>
Environment=SMTP_FROM=jacob@stephens.page
Environment=APP_URL=https://macros.stephens.page
[Install]
WantedBy=multi-user.target| What | Path |
|---|---|
| Source code | /home/jacob/MacroTracker/ |
| Frontend web root | /var/www/macros.stephens.page/ |
| Server working directory | /home/jacob/MacroTracker/server/ |
| SQLite database | /home/jacob/MacroTracker/server/data/macros.db |
| Systemd service | /etc/systemd/system/macros-api.service |
| Apache HTTP vhost | /etc/apache2/sites-available/macros.stephens.page.conf |
| Apache HTTPS vhost | /etc/apache2/sites-available/macros.stephens.page-le-ssl.conf |
| SSL certificate | /etc/letsencrypt/live/macros.stephens.page/ |
| Apache logs | /var/log/apache2/macros_error.log, macros_access.log |
cd /home/jacob/MacroTracker
npm run build
sudo cp -r dist/* /var/www/macros.stephens.page/cd /home/jacob/MacroTracker/server
npx tsc
sudo systemctl restart macros-api# Check API is running
sudo systemctl status macros-api
curl -s https://macros.stephens.page/api/health
# Check Apache
sudo apache2ctl configtest
sudo systemctl status apache2
# Check logs
sudo journalctl -u macros-api -f
sudo tail -f /var/log/apache2/macros_error.logEdit the systemd service file, then reload:
sudo vim /etc/systemd/system/macros-api.service
sudo systemctl daemon-reload
sudo systemctl restart macros-apiCertbot handles automatic renewal via a systemd timer. To manually renew:
sudo certbot renewThe SQLite database is a single file. To back up:
cp /home/jacob/MacroTracker/server/data/macros.db ~/backups/macros-$(date +%Y%m%d).db