Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
셀프호스팅 웹앱으로 요요 사양·사진·입고·판매 페이지·CSV 입출력·전체 백업을 관리합니다
Self-hosted web app to catalog and showcase your yoyo collection — specs, photos, arrivals, a public For Sale page, CSV import/export, and full backup. Node + SQLite, no build step.
라이프스타일JavaScript · CSSGitHub @stammig온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A self-hosted web app to catalog and showcase your yoyo collection — specs,
purchase info, condition, and multiple photos per throw, with search, filtering,
sorting, stats, a public "For Sale" page, an arrivals calendar for on-order
yoyos, and shareable links for individual throws. No build step, no accounts,
runs from a single folder.
Catalog your throws, track what's on the way, and show off the collection.
Live demo: test.daveronica.com — log in with the
password demo to see the owner/editing view. It's read-only, so you can't break
anything.README 전문 보기 ↗A self-hosted web app to catalog and showcase your yoyo collection — specs, purchase info, condition, and multiple photos per throw, with search, filtering, sorting, stats, a public "For Sale" page, an arrivals calendar for on-order yoyos, and shareable links for individual throws. No build step, no accounts, runs from a single folder.
Catalog your throws, track what's on the way, and show off the collection.
Live demo: test.daveronica.com — log in with the
password demo to see the owner/editing view. It's read-only, so you can't break
anything.
Screenshots from a real collection's public view — owner-only fields like price paid and seller aren't shown to visitors.
node:sqlite — a single file, nothing to compileuploads/Requires Docker. Copy-paste into a terminal (macOS / Linux):
curl -fsSL https://raw.githubusercontent.com/stammig/yoyo-collection/main/install.sh | bash
It creates ~/yoyo-collection, pulls the prebuilt image, optionally asks for an
owner password, and starts the app. Open http://localhost:3000. Re-run the
same command any time to update — your data is untouched.
# Install to /opt/yoyo, serve on port 8080, set the owner password without a prompt:
curl -fsSL https://raw.githubusercontent.com/stammig/yoyo-collection/main/install.sh \
| YOYO_DIR=/opt/yoyo YOYO_PORT=8080 ADMIN_PASSWORD='super-secret' bash
YOYO_DIR, YOYO_PORT, YOYO_IMAGE (pin a version tag), and ADMIN_PASSWORD
are all honored.
Grab the compose file and start the prebuilt image — no cloning, no building:
mkdir yoyo-collection && cd yoyo-collection
curl -fsSL https://raw.githubusercontent.com/stammig/yoyo-collection/main/docker-compose.yml -o docker-compose.yml
docker compose up -d
Open http://localhost:3000. Your data lives in the yoyo-data volume and
survives restarts and updates. Update with docker compose pull && docker compose up -d.
npm install
npm start # or: npm run dev (auto-restarts on changes)
Open http://localhost:3000.
Build the Docker image from source (instead of pulling it):
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build
All settings are environment variables and all are optional — with none set,
the app runs fully open on port 3000. Copy .env.example to
.env and fill in what you want. Highlights:
| Variable | Purpose |
|---|---|
PORT | HTTP port (default 3000) |
DB_PATH / UPLOAD_DIR | Where the database and photos live |
RESTORE_TMP_DIR | Where Restore puts its scratch folder (defaults to alongside DB_PATH) |
RESTORE_MAX_MB | Largest backup Restore accepts (default 8192 MB) |
ADMIN_PASSWORD | Public read-only + owner login to edit |
READ_ONLY | Make the whole app read-only |
AUTH_USER / AUTH_PASS | HTTP Basic auth over the entire app (fully private) |
DEMO_MODE | Public demo: login works but all writes are blocked, and pages are noindex |
RATE_LIMIT_MAX / RATE_LIMIT_WINDOW_MS | Throttle requests per IP (off by default; recommended for public instances) |
FRAME_ANCESTORS | Allow embedding the app in an <iframe> on your own site |
SESSION_SECRET | Override the auto-derived owner-login signing key |
TZ | Your time zone (e.g. America/New_York). Only affects the dates in backup and CSV export filenames, which otherwise follow the server's clock (UTC in Docker) |
UPS_* / USPS_* / FEDEX_* | Enable carrier ETA look-ups |
See .env.example for the complete, commented list.
Everything you enter lives in two places:
data/yoyos.db by default)uploads/ by default)Where to keep it: on the machine running the app — local disk, a Docker
volume, or your host's persistent disk. In production, point DB_PATH /
UPLOAD_DIR at a persistent location; many hosts have ephemeral filesystems
that wipe on redeploy.
⚠️ Don't put the live database in Dropbox / Google Drive / iCloud Drive. File-sync services copy the
.dbfile mid-write and don't understand SQLite's-wal/-shmsidecar files, which can corrupt your collection. Keep the live data on a normal local/host disk.
Backups: use Settings ⚙ → Backup in the app for a one-file (.zip)
snapshot of the database and photos, and Restore to bring it back. That
zip is safe to store anywhere — this is the file to drop in Dropbox/Drive or
keep off-machine. Both data paths are git-ignored so your collection is never
committed.
Restore handles multi-gigabyte backups. The browser uploads the file in small chunks, which keeps it under the request-size limit most shared hosts and reverse proxies enforce, and the server unpacks it one photo at a time rather than loading it into memory. The server needs free disk for roughly twice the backup's size while a restore runs.
Want a populated collection to explore (or for screenshots / a public demo)?
node seed-demo.mjs # writes data/demo.db with 10 example yoyos
DB_PATH=data/demo.db npm start
It refuses to overwrite a database that already has yoyos unless you pass
FORCE=1, so it can't clobber a real collection.
It's a standard Node web server, so it runs on most hosts (Docker, Render, Railway, Fly.io, a VPS, shared cPanel/Passenger hosting, etc.):
npm start (or run the Docker image
ghcr.io/stammig/yoyo-collection:latest, multi-arch amd64 + arm64).PORT is read from the environment (already handled).data/ and uploads/ a persistent volume, or point DB_PATH and
UPLOAD_DIR at one.The natural home for this app: a Docker container with one persistent local disk. One guide, with per-platform sections for OpenMediaVault, Unraid, Synology, TrueNAS SCALE, QNAP, and generic Linux (Proxmox, Raspberry Pi, a VPS).
The rule that matters everywhere: the database must live on a real local
filesystem — never a union/FUSE layer (mergerfs, Unraid's /mnt/user) or a
network share (SMB/NFS). SQLite's locking assumes local disk, and the failure is
silent.
Good for a personal catalog on the machine you use; a desktop OS sleeps and reboots, so it's a weaker fit as the household's always-on collection.
That guide recommends running natively rather than in Docker — the reverse of the NAS advice. Docker Desktop reaches host files through a translation layer (VirtioFS on macOS, 9p/WSL2 on Windows), the same hazard as a union or network filesystem. Native puts the database directly on APFS or NTFS. If you do use Docker there, use a named volume, not a bind mount.
REMOTE-ACCESS.md covers getting to your collection from
anywhere. Short version: a mesh VPN like Tailscale needs no app changes and
exposes nothing to the internet, while genuinely publishing the app means
setting ADMIN_PASSWORD and RATE_LIMIT_MAX first. Don't forward a router port.
A detailed cPanel/Passenger walkthrough is in DEPLOY-NAMECHEAP.md, and general notes in DEPLOY.md.
The view toolbar (below the filters) controls how the collection is displayed, and your choices are remembered in the browser.
Esc, or by clicking outside.Links to other people's videos about a throw — a review, a trick video, an unboxing. Paste one into the Videos section of the add/edit form, with an optional label.
They're deliberately kept out of the photo gallery: there are usually several, and none of them should end up as the cover image for your yoyo.
Accepted link formats:
https://www.youtube.com/watch?v=ID https://youtu.be/ID
https://m.youtube.com/watch?v=ID https://youtu.be/ID?t=90
https://www.youtube.com/shorts/ID https://www.youtube.com/live/ID
https://www.instagram.com/p/SHORTCODE/ https://www.instagram.com/reel/SHORTCODE/
Mobile (m.youtube.com) links, music.youtube.com, and links pasted without
https:// all work, as do the tracking parameters that get appended when you
share from an app. A t= / ?t=90 timestamp is preserved, so you can link
straight to the part of a review that discusses the throw. The same video pasted
in two different formats is recognised as a duplicate. Shorts and Reels get a
portrait frame.
Nothing is requested from YouTube or Instagram until someone presses play. The
card you see first is local markup; the player <iframe> is only created on
click, and YouTube is embedded via youtube-nocookie.com. On a public showcase
page that means visitors aren't handed third-party cookies for videos they never
watched — and a yoyo with several videos still opens instantly.
Instagram embeds only work for public posts and reels. Private or age-gated ones will show an empty frame, so every card also carries an "Open on Instagram" link as a fallback.
Use ⤓ Export CSV / ⤴ Import CSV in the toolbar. Columns:
Brand, Model, Body Material, Composition, In Hand, Color, Retail, Paid, Est. Value,
Purchase Date, Seller, Percent off, Condition, Weight, Diameter, Width, Gap Width,
Bearing Size, Reponse Type, Finish, Shape, Edition, Serial, Signature, Description,
Release Date, Tracking, ETA, Sold Date, Buyer
(export appends Favorite, Photos, and id). Import is lenient: column order
and case don't matter, $85.00→85 and 64.60 g→64.6 are parsed, % off is
recomputed, brand+model-less rows are skipped, and rows with a matching id are
updated (so re-importing an export won't duplicate). Photos aren't imported.
Purchase and sold dates are stored as YYYY-MM-DD: 6/15/2026 is read as
month/day, and 15/6/2026 (first number over 12) as day/month. Anything that
isn't a recognizable date, like Spring 2024, is kept exactly as typed.
Handy if you want to script against it (subject to the access mode above):
GET/POST /api/yoyos, GET/PUT/DELETE /api/yoyos/:idPOST /api/yoyos/:id/photos, DELETE /api/photos/:photoIdPOST /api/yoyos/:id/videos — JSON { url, title? }, attach a YouTube /
Instagram link; DELETE /api/videos/:videoIdPOST /api/track — carrier ETA look-up (needs carrier creds)GET /api/stats, GET /api/configGET /api/backup.zip, POST /api/restoreGET /y/:id — public, shareable page for a single yoyo (with link-preview tags)If you put an instance on the open internet, a sensible setup is:
ADMIN_PASSWORD to a long, random value. This keeps the public view
read-only (prices and other private fields are hidden from visitors) while you
log in to edit. Avoid a short or guessable password.RATE_LIMIT_MAX (e.g. 120) to throttle abusive traffic and slow down
password-guessing.That's it — there are no accounts or third-party services involved.
Issues and PRs welcome — see CONTRIBUTING.md. See CHANGELOG.md for a history of what's changed and why.
MIT © David Barker