Triggered by: a9a512e fix: clear opened_at on sealed packages row during split
🏠 EverShelf
Self-hosted pantry management system — Track your food inventory, scan barcodes, get AI-powered recipe suggestions, and reduce waste.
🚀 Try the live demo — no installation required!
▶ Open Live Demo · 🌐 Project Website · 📖 Wiki
The demo runs with mock pantry data. AI features are fully enabled. All write operations are safely sandboxed.
🌍 Recent Updates
- Category badges on inventory items — Every product now shows its macro-category badge (icon + label) inline. Items that can't be classified locally are automatically refined via Gemini AI after the page loads, with server-side caching so the AI call only happens once per product.
- Category search — Typing a category name (e.g. "biscotti", "latticini") in the inventory search bar now returns all matching items across brands and exact names.
- AI function guards — All Gemini-powered features (price estimation, category classification, recipe suggestions, etc.) now explicitly check whether a Gemini API key is configured before making any AI call. The UI gracefully skips or shows a friendly message instead of silently failing.
- AI price estimation for shopping list — Each Bring! shopping item now shows an estimated retail price badge (unit price + total). Prices are fetched via Gemini AI, cached server-side for 3 months, and stored client-side in
sessionStorageto survive navigation. The dashboard shopping stat card shows a live greenca. €X.XXbadge that updates in real-time as prices are calculated — even in background when you're on another tab. - Kiosk v1.7.0: OTA update system — "Cerca aggiornamenti" button in Settings triggers a forced GitHub release check; new
installUpdate()JS bridge calls AndroidDownloadManagerdirectly (lockTask mode blocks external browser links); graceful degradation for older APKs with manual instructions. Automatic OTA check every 6 hours with native update banner. - Kiosk: consistent APK signing — Project keystore (
evershelf.jks) committed to the repo; every build — local or CI — now produces an APK with the same signature, eliminating "APK incompatible / signature conflict" errors on OTA update. - GitHub Actions: auto-publish kiosk APK — On every push to
mainthat touchesevershelf-kiosk/, Actions builds the APK and publishes a versioned semver release (kiosk-X.Y.Z) plus updates thekiosk-latestalias. No more manual release uploads. - Fix: false "update available" on launch —
checkForUpdatesnow requires a strictly-greater semver tag to flag an update. Non-semver tags (e.g.kiosk-latest) no longer trigger a false positive immediately after a fresh install. - Kiosk: live scale diagnostic panel — When connected, Settings shows device name, battery %, real-time weight, protocol and reconnection status without leaving the settings page.
- Kiosk: scale dot visible on header — Connected-state dot changed from green-on-green to white fill + green glow, clearly visible on any background.
- Kiosk: reconfigure BLE scale — New "Riconfigura bilancia BLE" button in Settings; shows amber notice with download link if the installed APK predates the
reconfigureScale()bridge method. - Nutrition analysis dashboard — Category distribution pie chart (3D conic-gradient), health/variety/freshness score bars, alternates with the anti-waste section hourly.
- Screensaver nutrition panel — Animated 3D pie + donut ring scores rotate with fact cards every 5 minutes in the screensaver overlay.
- Automatic error reporting — Unhandled JS errors, Android crashes and PHP exceptions are silently posted to
api/?action=report_error; the server deduplicates by fingerprint and creates or comments on a GitHub Issue automatically. Crash details are persisted toSharedPreferencesso even errors that prevent network I/O are sent on the next launch. - Demo mode (JS) — Full frontend demo with mock pantry data, Gemini enabled, Bring! writes silently no-op'd; accessible via
?demo=1or.envDEMO_MODE=true. - Graceful Bring! no-key state — When Bring! credentials are not configured, the shopping tab shows a friendly message with a direct link to Settings instead of a raw error.
- Use-quantity guard — Consuming more than the stocked quantity at a given location is now blocked client-side with a shake animation on the quantity field.
- Kiosk v1.6.0: BLE scale gateway integrated — The standalone Scale Gateway app is no longer needed. BLE scanning, GATT connection and the WebSocket server (
:8765) now run as a built-inGatewayServiceforeground service inside the kiosk app. Setup step 4 shows a live BLE device scan — users select their scale directly, no external APK install required. The external gateway app is deprecated. - Kiosk setup wizard overhaul — Auto-discovery rewritten with
ExecutorCompletionService+NetworkInterface(no deprecatedWifiManager), 60 parallel TCP pre-checks, real-time UI feedback, ports 80/443/8080/8443, correct LAN subnet detection (VPN/cellular interfaces filtered,wlan/ethprioritised). - Kiosk permissions flow — Grant button transforms into a green "✅ Permessi concessi — Continua →" button after permissions are granted instead of just showing a card.
- 3 new AI features (Gemini) — Storage/shelf-life hint shown inline in the add form; AI-enriched shopping suggestions with a short practical tip per item; plain-language anomaly explanation via a "🤖 Spiega" button on anomaly banners.
- Security hardening —
get_settingsno longer exposes API keys in plain text (boolean flags only);save_settingsprotected by optionalSETTINGS_TOKEN(validated withhash_equals); nativeDEMO_MODEin.envblocks all write operations at the PHP router level before any other guard. - Real-time webapp update detection — An inline header pill appears when a newer release is on GitHub (checked on load + every 30 min); no intrusive full-page banners.
- Gemini availability flag — All AI entry points check
_geminiAvailablebefore firing; the header button shows a visual no-AI state (greyed + amber dot) when no key is configured. - Dashboard skeleton loading — Stat cards show an animated shimmer while data loads instead of a jarring
0flash for 3–5 seconds. - APK self-update with conflict recovery — Both Kiosk and Scale Gateway use the
PackageInstallersession API for OTA installs; a signature conflict now shows a dialog to uninstall the old version instead of a cryptic failure. - Smarter low-quantity alerts — The "suspiciously low quantity" banner is no longer raised for a partially-used entry when the same product has stock in another location.
- Non-alarmist expired banner — Adapts icon, colour, and title to the actual safety level: green ✅ for long-life products still safe, amber 👀 for items to check, red 🚫 only for genuinely dangerous items.
✨ Features
📦 Inventory Management
- Barcode scanning — Scan products with your phone camera using QuaggaJS
- AI identification — Take a photo and let Google Gemini identify the product, with suggestions from your existing inventory; gracefully shows a friendly message when AI quota is exhausted instead of a raw API error
- Smart locations — Track items across Pantry, Fridge, Freezer, and custom locations
- Expiry tracking — Automatic shelf-life estimation based on product type and storage
- Opened product tracking — Reduced shelf-life calculation when packages are opened; opened-product expiry is now also checked when building banner alerts (not just the dashboard section)
- Vacuum-sealed support — Extended expiry dates for vacuum-sealed items
- Anomaly detection — Banner alerts for suspicious quantities and consumption predictions with inline correction; dismiss button now shows the current inventory quantity so the action is unambiguous ("La quantità è giusta (2 pz)")
🤖 AI-Powered (Google Gemini)
- Expiry date reading — Photograph a label and extract the expiry date automatically
- Product identification — Point your camera at any product for instant recognition
- Existing product matching — AI scan shows matching products already in your pantry before suggesting new ones
- Storage & shelf-life hint — When adding a new product, Gemini suggests the optimal storage location and shelf-life in the background; shown as an inline AI badge next to the expiry estimate
- Recipe generation — Get personalized recipes based on what's in your pantry; streams live via Server-Sent Events so results appear as they are generated
- Smart chat assistant — Ask questions about your inventory, get cooking tips
- Shopping suggestions with tips — AI-powered purchase recommendations, each enriched with a short practical buying/storing tip
- Anomaly explanation — "🤖 Spiega" button on anomaly banners explains in plain language why a discrepancy likely occurred and what to do
- Model fallback — All AI endpoints try
gemini-2.5-flashfirst and fall back togemini-2.0-flashautomatically - Graceful no-key state — When no Gemini key is configured, AI entry points show a friendly message; the header button is visually greyed with an amber dot
🛒 Shopping List
- Bring! integration — Sync with the Bring! shopping list app
- Generic shopping names — Products are grouped by type ("Latte", "Affettato", "Panna da cucina") rather than brand, keeping the Bring! list clean and consolidated
- Smart predictions — Know what you'll need before you run out
- Auto-add on depletion — When a product reaches zero the app adds it to Bring! automatically, no confirmation needed
- Auto-remove on scan — Products are removed from the shopping list when scanned in - Auto-migration — Items already on the Bring! list are silently renamed to their generic name in the background (throttled, runs on list load)
- Catalog coverage — All product types resolve to a German Bring! catalog key for icon and category display in the Bring! app
🍳 Cooking Mode
- Step-by-step guidance — Follow recipes with a hands-free cooking interface
- Text-to-Speech — Voice readout of recipe steps; supports browser Web Speech API, native Android TTS (kiosk), or a custom REST endpoint (Home Assistant, etc.); retries voice loading for up to 10 seconds with a fallback refresh button; TTS activates automatically without requiring the global TTS setting to be enabled
- Auto-read on navigate — Each step is read aloud automatically when you tap Next or Previous; the first step is read when entering cooking mode
- Timer voice alerts — 10-second countdown warning spoken aloud before each timer expires; expiry announced vocally when time is up
- Recipe completion — "Buon appetito!" spoken when the last step is confirmed
- Built-in timer — Automatic timer suggestions based on recipe instructions
- Ingredient tracking — Mark ingredients as used during cooking; leftover quantities prompt a "move to another location" flow
📊 Dashboard
- Waste tracking — Monitor consumed vs. wasted products over 30 days
- Anti-waste report — Personalised waste rate vs. national average with annual kg estimate; shown above the expiring-items list
- Expiry alerts — Visual warnings for expired and soon-to-expire items
- Opened products panel — Tracks partially-used items; expiry is recalculated from the opening date using AI (Gemini) + per-category rule fallback; whole sealed packages always keep their original manufacturer expiry; conf items with mixed whole + fractional units are shown as two separate entries
- Freezer shelf-life — Granular per-product estimates (USDA/EFSA): fish 120 d, poultry 270 d, whole red-meat cuts 365 d, mince 120 d, vegetables/fruit 270 d, generic 180 d; AI + cache still take priority over rules
- Safety ratings — Smart assessment of expired product safety (by category and location); expired unsafe items shown with a red danger banner and "L'ho buttato" as the primary action
- Expired product banner — Products that have passed their effective shelf-life (including opened-product reduced expiry) appear in the top notification banner; icon, colour and title adapt to the actual safety level (✅ green for safe, 👀 amber to check, 🚫 red for danger); high-risk items get a prominent discard action
- Quick recipe bar — One-tap recipe suggestion using expiring products
- Anomaly banner — Scrollable banner with suspicious quantities and consumption prediction mismatches, with one-tap correction or inline edit
- Expired/expiring alerts — Priority-sorted banner notifications for expired and soon-to-expire products with use, throw, edit, and dismiss actions
- Swipe navigation — Touch swipe or tap arrows/dots to browse banner notifications
- Quick-access buttons — Recently used and most popular products shown on the inventory page for fast access
📱 Progressive Web App
- Mobile-first design — Optimized for phones, works on tablets and desktop
- Installable — Add to home screen for a native app experience
- Multi-device — Settings and data sync across devices on the same server
⚖️ Smart Scale Integration (Add-on)
- Bluetooth gateway — Connects a BLE smart scale to EverShelf via local WebSocket
- SSE relay — Server-side relay avoids mixed-content (HTTPS→WS) issues
- Auto-discovery — Server scans LAN to find the gateway automatically
- Auto weight reading — When adding/using a product with unit g/ml, weight fills automatically
- 10g threshold — Ignores readings that haven't changed enough between products - Duplicate-reading prevention — Server-side 12-second dedup window rejects a second scale-triggered deduction of the same product, guarding against BLE multi-fire- ml conversion hint — Shows "weight in grams → will be converted to ml" when product unit is ml
- Stability + auto-confirm — 10s stable wait + 5s countdown before confirming
- Real-time status — Scale connection indicator always visible in the header
- Multi-protocol — Supports Bluetooth SIG Weight Scale, Body Composition, Xiaomi Mi Scale 2 and 100+ models
- Built into kiosk (v1.6.0+) — BLE gateway runs as an integrated foreground service inside the EverShelf Kiosk app; no separate APK needed. The standalone gateway app in
evershelf-scale-gateway/is deprecated but kept for non-kiosk use cases.
📺 Android Kiosk Mode (Add-on)
- Dedicated tablet app — Full-screen WebView wrapper for wall-mounted kitchen tablets
- True kiosk lock — Screen pinning blocks home/recent buttons
- Setup wizard — 6-step guided configuration (language, welcome, permissions, server URL, BLE scale scan, screensaver, summary)
- Smart auto-discovery — Scans the LAN in parallel (60 threads, TCP pre-check, ports 80/443/8080/8443) with real-time UI feedback; correctly identifies the device's Wi-Fi/Ethernet subnet (VPN and cellular interfaces are filtered out)
- Built-in BLE scale gateway —
GatewayServiceforeground service; BLE scanning + WebSocket server:8765run directly inside the kiosk app. Select your scale in step 5 of the wizard — no external app required - Scale auto-configuration — After selecting the BLE device, the wizard writes
scale_enabledandscale_gateway_url=ws://127.0.0.1:8765to the server automatically - Camera & mic permissions — Full hardware access for barcode scanning and voice; grant button transforms to a green confirmation after granting
- Native TTS bridge — Cooking mode voice readout uses the Android TextToSpeech engine directly, bypassing Web Speech API voice limitations; no offline voice packs required
- Hard refresh — ↻ button clears WebView cache to pick up web app updates
- Update notifications — Checks GitHub releases every 6h, shows banner when updates available
- SSL support — Accepts self-signed certificates
- Android kiosk app —
evershelf-kiosk/— downloadable APK
🚀 Quick Start
Prerequisites
- Web server with PHP 8.0+ (Apache or Nginx)
- PHP extensions:
pdo_sqlite,curl,mbstring,json - HTTPS recommended (required for camera access on mobile)
Installation
Option A: Docker (recommended)
# 1. Clone the repository
git clone https://github.com/dadaloop82/EverShelf.git
cd EverShelf
# 2. Create configuration file
cp .env.example .env
nano .env
# 3. Start with Docker Compose
docker compose up -d
# → Open http://localhost:8080
Option B: Manual
# 1. Clone the repository
git clone https://github.com/dadaloop82/EverShelf.git
cd EverShelf
# 2. Create configuration file
cp .env.example .env
# 3. Set permissions
chmod 755 data/
chmod 664 data/.gitkeep
chown -R www-data:www-data data/
# 4. Edit your configuration
nano .env
Configuration (.env)
# Required for AI features (get a key at https://aistudio.google.com/app/apikey)
GEMINI_API_KEY=your_api_key_here
# Optional: Bring! shopping list integration
BRING_EMAIL=your_email@example.com
BRING_PASSWORD=your_password
# Optional: Text-to-Speech for cooking mode
TTS_URL=http://your-home-assistant:8123/api/events/tts_speak
TTS_TOKEN=your_long_lived_token
TTS_ENABLED=true
# Optional: Security — protect the save_settings endpoint
# Set a strong random string; the Settings UI will ask for it before saving
SETTINGS_TOKEN=
# Optional: Demo mode — block all write operations at the router level
DEMO_MODE=false
Web Server Configuration
Apache (.htaccess)
The app works out of the box with Apache if placed in the web root or a subdirectory. Make sure mod_rewrite is enabled and AllowOverride All is set.
<Directory /var/www/html/evershelf>
AllowOverride All
Require all granted
</Directory>
Nginx
server {
listen 80;
server_name your-server.local;
root /var/www/html/evershelf;
index index.html;
location /api/ {
try_files $uri $uri/ =404;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
# Deny access to sensitive files
location ~ /\.env { deny all; }
location ~ /data/ { deny all; }
location ~ /backup\.sh { deny all; }
}
HTTPS Setup (Recommended)
Camera access requires HTTPS on most mobile browsers. Options:
- Let's Encrypt with Certbot (for public-facing servers)
- Self-signed certificate (for local network only)
- Reverse proxy (e.g., Caddy, Traefik) with automatic TLS
Cron Job (Optional)
Set up a cron job for smart shopping predictions:
# Run every 5 minutes
*/5 * * * * php /path/to/evershelf/api/cron_smart_shopping.php >> /path/to/evershelf/data/cron.log 2>&1
Backup (Optional)
The included backup.sh creates local daily backups of your database:
# Run daily at 3 AM
0 3 * * * /path/to/evershelf/backup.sh
🏗️ Architecture
evershelf/
├── index.html # Single-page application (SPA)
├── manifest.json # PWA manifest
├── .env.example # Configuration template
├── backup.sh # Local database backup script
├── LICENSE # MIT License
│
├── api/
│ ├── index.php # Main API router (all endpoints)
│ ├── database.php # SQLite schema, migrations, helpers
│ └── cron_smart_shopping.php # Background job for predictions
│
├── assets/
│ ├── css/style.css # All application styles
│ ├── js/app.js # All application logic
│ └── img/ # Static images
│
└── data/ # Runtime data (gitignored)
├── evershelf.db # SQLite database (auto-created)
├── backups/ # Local DB backups
└── *.json # Token/cache files
evershelf-scale-gateway/ # ⚖️ Android BLE gateway [DEPRECATED — integrated into kiosk v1.6.0+]
├── README.md # Deprecation notice + legacy docs
└── app/src/ # Kotlin Android source (WebSocket + BLE)
evershelf-kiosk/ # 📺 Android kiosk app (add-on)
├── README.md # Setup & feature docs
└── app/src/ # Kotlin Android source (WebView wrapper)
API Endpoints
| Category | Action | Method | Description |
|---|---|---|---|
| Products | search_barcode |
GET | Find product by barcode |
lookup_barcode |
GET | Look up barcode on Open Food Facts | |
product_save |
POST | Create or update a product | |
products_list |
GET | List all products | |
| Inventory | inventory_list |
GET | List inventory items |
inventory_add |
POST | Add product to inventory | |
inventory_use |
POST | Use/consume from inventory | |
inventory_summary |
GET | Count by location | |
| AI | gemini_identify |
POST | Identify product from photo |
gemini_expiry |
POST | Read expiry date from photo | |
gemini_chat |
POST | Chat with AI assistant | |
generate_recipe |
POST | Generate recipe from inventory | |
gemini_product_hint |
POST | Storage location + shelf-life hint | |
gemini_shopping_enrich |
POST | Enrich shopping suggestions with tips | |
gemini_anomaly_explain |
POST | Plain-language anomaly explanation | |
| Shopping | bring_list |
GET | Get Bring! shopping list |
bring_add |
POST | Add items to Bring! | |
smart_shopping |
GET | Smart shopping predictions | |
| Settings | get_settings |
GET | Get server configuration |
save_settings |
POST | Update server configuration |
🔒 Security Notes
- Credentials are stored in
.env(server-side, never committed to Git) - Database stays local — never pushed to remote repositories
- API keys are never exposed to the browser —
get_settingsreturns only boolean flags (gemini_key_set,settings_token_set), never raw key values - Settings write protection — set
SETTINGS_TOKENin.envto require a secret token (X-Settings-Tokenheader) for allsave_settingscalls; validated withhash_equalsto prevent timing attacks - Demo / public mode — set
DEMO_MODE=trueto block all write operations at the PHP router level before any business logic runs - The API uses parameterized SQL queries (PDO prepared statements) against injection
- Input validation on all inventory operations (quantity bounds, location whitelist)
- Consider adding reverse-proxy authentication (e.g. Authelia, Nginx
auth_basic) if the server is accessible from the internet
🛠️ Development
# Run PHP's built-in server for local development
php -S localhost:8080 -t /path/to/evershelf
# Check PHP syntax
php -l api/index.php
php -l api/database.php
The application uses no build tools — edit files directly and refresh.
📋 Roadmap
- Multi-language support (i18n) — 3 languages (it/en/de), 347 keys
- User authentication / multi-user support
- Docker container for easy deployment — see Dockerfile + docker-compose.yml
- REST API documentation (OpenAPI/Swagger) — see docs/openapi.yaml
- First-run setup wizard — 4-step guided configuration
- API rate limiting — file-based, 3 tiers (120/15/5 req/min)
- CI/CD pipeline — GitHub Actions (lint, Docker build, translation validation)
- Android kiosk mode — dedicated tablet app with screen pinning
- Anomaly detection banner — suspicious quantities + consumption predictions
- AI scan local matching — suggest existing pantry products before OFF lookup
- Scale auto-fill improvements — 10g threshold, ml conversion hints
- Update notification system — inline header pill (webapp) + kiosk checks GitHub releases
- Kiosk OTA update — forced check button,
installUpdate()bridge, graceful old-APK fallback - Kiosk consistent APK signing — project keystore eliminates signature conflicts on OTA
- GitHub Actions kiosk CI — auto-builds and publishes versioned semver APK on every push to main
- Kiosk live scale diagnostics — device, battery, real-time weight in Settings when connected
- Nutrition analysis dashboard — category pie + health/variety/freshness scores, alternates with waste section
- Screensaver nutrition panel — animated pie + donut ring scores rotate with facts
- Automatic error reporting — JS/Android/PHP errors → GitHub Issues with deduplication
- Generic shopping name grouping — compound-phrase + keyword map (100+ entries) + Gemini AI fallback
- Auto-add to Bring! on product depletion — no confirmation step when stock reaches zero
- Native Android TTS in kiosk — bypasses Web Speech API voice detection issues
- AI product storage hint — background Gemini call suggests location + shelf-life in the add form
- AI shopping tips enrichment — each suggestion enriched with a short practical tip
- AI anomaly explanation — "🤖 Spiega" button explains discrepancies in plain language
- Security hardening — no raw key exposure, SETTINGS_TOKEN auth, DEMO_MODE native blocking
- Offline mode with service worker
- Export/import inventory data
- Notification system (Telegram, email) for expiring products
🌐 Translations
The app supports multiple languages via JSON translation files in the translations/ folder.
| Language | Status |
|---|---|
| 🇮🇹 Italian (it) | ✅ Complete (base) |
| 🇬🇧 English (en) | ✅ Complete |
| 🇩🇪 German (de) | ✅ Complete |
Want to add your language? See the Translation Guide — just copy translations/it.json, translate the values, and submit a PR!
🤝 Contributing
Contributions are welcome! See CONTRIBUTING.md for detailed guidelines.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -m 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License — see the LICENSE file for details.
👨💻 Author
Stimpfl Daniel — evershelfproject@gmail.com
- Website: evershelfproject.dadaloop.it
- GitHub: @dadaloop82








