Setup guide

Everything you need to run Instrumentify for one school. If you would rather not run a server, email instead and it gets set up for you.

Install

Two ways. The container is the one to use on a school server, a TrueNAS box or a VPS. The pip install is for trying it on a laptop or developing.

With Docker

docker run -d --name instrumentify -p 5000:5000 \
  -v /srv/instrumentify/data:/app/data \
  -v /srv/instrumentify/logs:/app/logs \
  -v /srv/instrumentify/uploads:/app/static/uploads \
  -v /srv/instrumentify/config:/app/config \
  -v /srv/instrumentify/google_ServiceAccount:/app/google_ServiceAccount \
  ghcr.io/itsrecharge/instrumentify:latest

The five folders hold everything that must survive an image update: the CSV data, logs, instrument photos, your configuration, and the Google service-account file if you use one. On TrueNAS SCALE, use repository ghcr.io/itsrecharge/instrumentify, container port 5000, and mount the same five paths.

With Python

git clone https://github.com/ItsRecharge/Instrumentify
cd Instrumentify
python3.11 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp -r config_example config
python app.py                   # serves on port 5001

For production, run it under gunicorn with one worker and put HTTPS in front of it (Caddy, nginx or the reverse proxy your school already has):

gunicorn -w 1 -b 0.0.0.0:5000 wsgi:app
Keep one worker. Settings changed in the admin screen live in the running process; extra workers would show stale toggles until they restart.

Setup wizard

Open the app, sign in with the example admin from config/config.py, and Instrumentify walks you through five steps. It writes the configuration for you; there is no file editing.

  1. School and admin account

    The name shown everywhere, a fresh secret key, and the real director account. The example address admin@example.com is refused on purpose.

  2. School year and loan terms

    When your year starts and ends. Due dates for school-year, summer and both-term loans are computed from this, and the renewal window opens before the summer.

  3. Google Sheets and email

    Optional. Upload the service-account file, paste the spreadsheet ID and worksheet names, add a Gmail app password. Both can be skipped and turned on later.

  4. Public pages

    Whether families see a landing page, a catalog and a features page, and what the hero says. Each is a switch you can flip later.

  5. Review and finish

    A summary of every choice. Finishing signs you in as the new admin and unlocks the rest of the app.

Day-to-day use

Google Forms, Sheets and Drive

All optional, all separate switches. The core inventory and manual checkouts work with no external services at all.

Requests through a Google Form

Students fill in a form with the instrument UUID, their name, email, student ID, graduation year, the loan term, and a few photos of the instrument's condition. Uploads land in the form owner's Drive and the links flow into the linked spreadsheet. Instrumentify reads that sheet every 30 seconds, so the queue stays current, and writes the outcome back when a request is approved or denied.

Backups to Google Sheets

Every edit mirrors the CSV files to a spreadsheet the school owns. If the server dies, the data is readable in Sheets and can be restored from it with one button.

Photo storage

Photos uploaded in the app stay on the server. Photos that arrive through the form live in Drive. The storage manager watches the Drive quota: recent images stay full size in Drive, older ones are downloaded and compressed locally, and past 90% usage it clears old cloud copies while keeping anything younger than six months. iPhone HEIC photos are handled.

Email

Notifications use a Gmail app password over SMTP. Students get approval, denial and reminder emails; the director gets either one email per request or a daily summary. Many school Google accounts block SMTP, in which case a standalone Gmail works.

How it is put together

app.py                    Flask entrypoint
app/
  models/                 user, instrument, checkout (CSV-backed)
  routes/                 auth, admin, api, checkout, instruments, setup
  services/               email, google_sheets, audit, storage_manager
  utils/                  security, helpers, file locking
config/                   your settings (never committed)
config_example/           template the wizard fills in
data/                     CSV files, the source of truth
static/uploads/           instrument photos
templates/                Jinja2 pages
Config moduleWhat it holds
config.pySecret key, admin credentials, school name, debug flag
config_server.pyUpload paths, CSV locations, invite-code expiry, school year
config_security.pyRate limiting, password rules, CSRF, upload checks, cookies
config_integrations.pyGoogle Sheets, Drive storage, email, refresh intervals, renewals
config_landing_page.pyPublic page switches, hero text, feature list, logo, favicon

Data is plain CSV in data/: instruments.csv, users.csv, checkout_history.csv (the audit trail, including where photos live) and signup_codes.csv. A school's inventory is hundreds of rows, not millions; files you can open in a spreadsheet beat a database server on school hardware.

Scheduled jobs (Sheets backup, request refresh, return reminders, the daily summary) start once per deployment behind a file lock. Turn them all off with BACKGROUND_JOBS_ENABLED = False.

Security

ProtectionDetail
Rate limiting5 sign-in attempts per 5 minutes per address
Passwordsbcrypt hashes; 8+ characters with upper, lower and a number
CSRFToken required on every state-changing request
InjectionTemplates escape input; CSV exports are neutralised against formula injection
UploadsMIME sniffing and size limits; filenames are replaced
SessionsHttpOnly, Secure and SameSite cookies
AuditSign-ins, edits and uploads are logged with who and when

Each protection is a switch in config_security.py, which is useful while debugging and unwise in production. Never commit config/, google_ServiceAccount/, data/ or logs/; the repository ignores them already.

Questions

Do students need accounts?

No. Students browse the public catalog and request instruments through the Google Form. Only staff sign in. Public pages can be switched off entirely for an admin-only deployment.

How do other staff get accounts?

An admin generates an invite code with an expiry from the account menu. The new user redeems it on the sign-up page.

Do I need any of the Google integrations?

No. Sheets backup, Forms intake, Drive storage and email are separate switches. Inventory and manual checkouts work with nothing else configured.

Why does the wizard refuse my admin email?

It refuses admin@example.com because that is the published example. Shipping it would put a known login on your live system.

What happens when Drive fills up?

The storage manager reads real usage from the API, compresses older photos to local disk as it approaches the thresholds, warns on the dashboard, and past 90% can remove old cloud copies, never touching anything under six months old.

Can I move to a new server?

Copy the five folders. Everything is in them. Or restore the instruments from the backup sheet after a fresh install.

When something breaks

ModuleNotFoundError: No module named 'fcntl' on Windows

The background-jobs lock uses fcntl, which only exists on Unix. Set BACKGROUND_JOBS_ENABLED = False in config/config.py, or run the container or WSL. Linux deployments are unaffected.

"charmap codec can't encode" spam on Windows

Startup log lines contain characters cp1252 consoles cannot print. Harmless; silence it with set PYTHONIOENCODING=utf-8 (PowerShell: $env:PYTHONIOENCODING='utf-8').

Port already in use

lsof -ti:5000 | xargs kill -9. On Windows, Get-NetTCPConnection -LocalPort 5000 then Stop-Process -Id <pid>. The dev server uses port 5001; check the "Running on" line.

Cannot sign in as admin

After the wizard, the credentials are the ones from step 1, not the example ones. Check ADMIN_EMAIL in config/config.py. Five bad attempts lock you out for five minutes.

Google Sheets backup does nothing

Three checks: share the spreadsheet with the service-account email as Editor, make sure the spreadsheet ID and worksheet name match the settings, and confirm the credentials path exists. Details are in logs/app.log.

Emails are not sent

Use a Gmail app password (needs 2-Step Verification), never the account password. If the school account blocks SMTP, use a standalone Gmail. The SMTP conversation is in logs/email.log.

Form photos do not appear in requests

The form needs a file-upload question, the worksheet name must match GOOGLE_FORMS_CHECKOUT_WORKSHEET_NAME, and the response column must contain drive.google.com links. For permission errors, share the form's response folder with the service-account email as Viewer.

Storage dashboard shows 0% Drive usage

The service account needs the drive.readonly scope and the Google API client must be installed. Check the credentials path in config_integrations.py.

The full reference, including every configuration key, is in DOCUMENTATION.md in the repository.