GymCoach
근력 트레이닝 기록 및 개인 AI 코치
구역별 칫솔 타이머 PWA, 치아 도표와 음·시각 신호 제공, 오프라인 작동, Docker·Caddy로 자동 HTTPS·업데이트
Zone-by-zone toothbrush timer PWA with a teeth diagram and audio/visual cues. Works offline. Self-hosts on a DigitalOcean droplet with Docker + Caddy, automatic HTTPS and auto-updates.
건강JavaScript · ShellGitHub @norbsmaq온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A small, dependency-free web app that guides you through brushing your teeth zone by zone.
Brushing order: upper right → upper left, then lower left → lower right.
git clone https://github.com/norbsmaq/toothbrush-timer /opt/toothbrush-timer
cd /opt/toothbrush-timer
sudo ./setup-droplet.sh
The script asks for your domain name(s) and whether to enable HSTS (see below). Then it installs Docker if needed, opens ports 80 and 443 if ufw is active, turns on automatic updates, installs GoAccess for visitor stats, and starts the app. It finishes by printing the address to open.The container restarts automatically after reboots (restart: unless-stopped).
Point a DNS A record for each domain at the droplet's IP. For example, add records for both example.com and www.example.com. Then enter the domains when setup-droplet.sh asks, separated by spaces or commas:
Domains [none]: example.com www.example.com
The script warns you if a domain doesn't point at the droplet yet. Caddy gets and renews a Let's Encrypt certificate for each domain automatically, and serves the app on all of them. HTTPS is needed for the browser's keep-screen-awake feature and for installing the app on your phone. Everything else works over plain HTTP.
If you list both a domain and its www. version, the script also offers to redirect www.example.com to example.com (keeping the rest of the address). That way everyone ends up on one address, so an installed app and its saved settings always use the same one.
To change domains later, run sudo ./setup-droplet.sh again. It shows your current settings as defaults, so press Enter to keep any of them. Or edit .env directly (SITE_ADDRESS="example.com, www.example.com", REDIRECT_WWW=true) and run docker compose up -d.
By default the site sends an HSTS header. It tells browsers to always use HTTPS for your domain for the next year. Browsers ignore it over plain HTTP or an IP address, so it does no harm there.
If you're using a domain but might switch it back to plain HTTP later, answer n when the setup script asks about HSTS. Or set ENABLE_HSTS=false in .env and run docker compose up -d. Turning it off doesn't undo HSTS in browsers that have already received it; they keep it until it expires.
setup-droplet.sh turns on three kinds of automatic updates:
| What | How | When |
|---|---|---|
| Ubuntu security updates | unattended-upgrades | Daily. If an update needs a reboot, the droplet reboots at 04:00 and the app starts back up on its own. |
| Docker Engine | unattended-upgrades, from Docker's apt repository | Daily, along with the OS updates |
| The app and its Caddy web server | toothbrush-timer-update.timer runs update.sh | Daily around 03:30 |
update.sh makes the server's copy match the main branch on GitHub exactly, rebuilds on the newest Caddy image, restarts the container and removes old images. Anything pushed to main goes live on the next run. Any edits made directly to the repo's files on the server are overwritten. Your .env settings aren't part of the repo, so they're kept.
To update right away, run sudo ./update.sh. To see when the update last ran and when it runs next:
systemctl list-timers toothbrush-timer-update.timer
journalctl -u toothbrush-timer-update.service -n 50
To turn off the app auto-update: sudo systemctl disable --now toothbrush-timer-update.timer
.env is git-ignored, so your settings are kept across updates.
Caddy writes an access log for every request to logs/access.log in the app folder on the droplet. It starts a new file at 10 MB, compresses the old ones and deletes them after 90 days. setup-droplet.sh installs GoAccess, which turns the logs into a stats dashboard in your terminal. Nothing is published on the website.
To see the stats, SSH into the droplet and run:
cd /opt/toothbrush-timer
sudo ./stats.sh
It shows visitors, pages, browsers, operating systems and referrers per day, across the current and older log files. Use the arrow keys and Tab to move around, and press q to quit. Extra options are passed to GoAccess. For example, sudo ./stats.sh --ignore-crawlers hides search engine bots.
The logs include visitors' IP addresses and browser details. They stay on the droplet, readable only by root.
The app is a Progressive Web App (PWA). Once installed, it opens full-screen from your home screen and works offline.
Installing requires HTTPS, so set up a domain first (see above). Over plain http://IP it still works in the browser, but it won't install.
When you deploy a new version, an installed app picks it up the next time it's opened online.
To try the app or test changes without a server, get the code with git clone https://github.com/norbsmaq/toothbrush-timer (or Code → Download ZIP on GitHub), then either:
public/index.html in your browser. Everything works except offline mode and installing as an app.docker compose up, then open http://localhost.cd public && python3 -m http.server 8000, then open http://localhost:8000.public/ the app (index.html, style.css, app.js, icons)
manifest.webmanifest PWA manifest
sw.js service worker (offline support)
Caddyfile web server config
Dockerfile Caddy image with the app baked in
docker-compose.yml
setup-droplet.sh one-time droplet setup
update.sh pull + rebuild (run daily by a systemd timer)
stats.sh visitor stats from the access logs (GoAccess)
logs/ access logs on the server (git-ignored)