jf-barcode-search: Scanning Barcodes to Query Jellyfin — and a Checklist for Open-Sourcing a Homelab Repo
- 5 minutes read - 1065 wordsHere’s a problem specific to people who rip their own discs: you’re standing in a thrift store holding a $3 Blu-ray, and you genuinely cannot remember whether it’s already in your library. Pull out your phone, squint at a media app, type the title, hope you spelled it like the metadata does… by which point you’ve put it back.
So I built jf-barcode-search: point your phone camera at the barcode on the case, and it tells you instantly whether that title is already in your Jellyfin library. It’s a small Flask app, and this post covers both what it does and — because I decided to open-source it — the pre-flight review that makes a homelab repo safe to publish, which is a checklist worth having regardless of what you’re releasing.
What it does #
The flow is dead simple by design:
- Open the web app on your phone (it’s served from my homelab, reachable over the tailnet when I’m out).
- Point the camera at the UPC/EAN barcode on the disc case.
- The barcode decodes in-browser, the app looks up the UPC to get a title, then queries the Jellyfin API to see if that title already exists in the library.
- Green “you own this” / red “you don’t.” Decision made before the clerk looks up.
The pieces:
- Frontend: a small static page that does in-browser barcode scanning with ZXing (vendored, so it works without a CDN). Point, decode, done.
- UPC → title: a barcode-lookup API (upcitemdb and friends) turns the number into a product name.
- Title → library: the Jellyfin API (
JELLYFIN_URL+JELLYFIN_API_KEY, searchingMovie,Series) checks for a match. - Backend: a compact Flask app, everything configured through environment variables, containerized with Docker.
It’s the kind of tool that’s trivial in concept and genuinely changes behavior in practice — I’ve avoided several accidental double-buys with it.
Making it public without leaking anything #
The app lived in my homelab for a while before I decided to open-source it. A private homelab repo accumulates things you really don’t want on a public GitHub, so before flipping the switch I ran a proper review. This is the checklist — useful for any repo you’re about to publish.
1. Are secrets tracked, or only referenced? #
The first and most important question. The repo reads everything sensitive from env vars, and the real .env — which held a live Jellyfin API key, OMDB and TMDB keys, and a live Tailscale auth key — was gitignored. Good. But “gitignored now” isn’t enough; you have to check history:
# is .env (or any secret-ish file) tracked right now?
git ls-files | grep -E '\.env|\.csv|\.pdf' || echo " none (good)"
# was it EVER committed, on any branch?
git log --all --oneline -- .env
# has any sensitive file type ever entered history?
git log --all --name-only --pretty=format: | sort -u \
| grep -iE '\.(csv|pdf|sqlite|db)$|\.env'
# scan tracked files for hardcoded secrets
git ls-files -z | xargs -0 grep -nEi \
'(api[_-]?key|secret|password|token|bearer|BEGIN .*PRIVATE KEY)'
The point of the history check: making a repo public exposes every commit ever, not just the current tree. A key that was committed once and later moved to .env is still sitting in the git history for anyone to git log -p. In my case it came back clean — nothing sensitive had ever been committed — so publishing was safe. (I still flagged the live Tailscale key for rotation as hygiene: it’s a reusable key sitting in plaintext in my local .env, and “never committed” is not the same as “never worth rotating.”)
2. Is there a LICENSE? #
A public repo with no license is “all rights reserved” by default — legally, nobody can reuse it, which defeats the point of publishing. The repo had none, so I added one (GPL-3.0, canonical text). This is the single most-forgotten step in open-sourcing something.
3. Is there a config template? #
Secrets are gitignored, which means a new user clones the repo and has no idea what to configure. The fix is a committed .env.example with placeholder/empty values and comments — the standard “copy this to .env and fill it in” pattern. Verify it isn’t itself caught by .gitignore:
git check-ignore .env.example && echo "IGNORED (bad)" || echo "not ignored (good)"
4. Does the README match the code? #
Small, but it’s the first thing anyone reads. Mine claimed the frontend used html5-qrcode; the code actually vendors and uses ZXing. A doc bug like that erodes trust immediately — fixed it.
5. Is the default branch sane? #
The repo’s default branch was refactor-modular — a fine working-branch name, an odd face to show the public. Renamed to main (and noted the follow-ups that can’t be done from the CLI: switching the default on GitHub, then deleting the old remote branch).
6. Any personal breadcrumbs? #
A spec doc had Author: (generated for v3@techlouvet.com). My email is already in every commit’s author metadata so it’s no new exposure, but it’s the kind of thing worth a conscious “yes, I’m fine with this being public” rather than a surprise later.
The result #
After the review: LICENSE added, .env.example created (verified not ignored), README corrected, branch renamed — committed and pushed. The remaining steps were the deliberately-manual, outward-facing ones I left for a human hand on the wheel: switching the GitHub default branch, deleting the old branch, and finally flipping the repo to public. Publishing is irreversible in the sense that anything exposed is exposed forever (caches, forks, clones), so that last click should always be yours.
Takeaways #
- A barcode scanner over the Jellyfin API is a tiny app with an outsized payoff — “do I already own this?” answered in two seconds in a store aisle.
- Before open-sourcing, audit git history, not just the working tree. Public means every commit ever; a secret committed once and later moved is still there.
- No LICENSE = nobody can reuse it. Add one deliberately; it’s the most-forgotten step.
- Ship a
.env.example, make the README match the code, and give the repo a sane default branch. - Rotate any credential that sat in plaintext, even one that was never committed — cheap insurance.
- Keep the final “make public” click human. Exposure is permanent; that decision shouldn’t be automated.
The tool saves me money at thrift stores. The checklist saves me from a much more expensive kind of mistake.