尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
Pay Up is a fine jar for yourself. Make a jar for the habit you want to catch: negativity, swearing, doom-forecasting. Set a fine per slip, tap the big pink button every time you catch yourself, add a note if it deserves one. The jar keeps an honest tally of what you owe; settle it however you like. Share a jar and let friends watch it grow.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Make a jar for the thing you keep doing. Tap it when you do it. Settle up when it hurts.
nvm use (Node 22.22 or newer), then npm install.cp .env.example .env and fill in SESSION_SECRET (openssl rand -base64 32) and at least one provider.npm run dev and open http://localhost:5173. In development APP_URL must match that origin, so set APP_URL=http://localhost:5173 in .env and register callbacks against it.${APP_URL}/auth/github/callback. Copy client id and secret into .env.${APP_URL}/auth/google/callback. Configure the consent screen with the openid, email, profile scopes.A provider whose pair is empty in .env simply has no button on the landing page.
npm testnpm run typechecknpm run build then npm start serves the production build on PORT (default 3000) through server.js, a small Express server that trusts the reverse proxy in front of it (see TRUST_PROXY). A /healthz endpoint answers ok.cp .env.example .env # fill it in; APP_URL is the public https origin
docker compose up --build -d
The SQLite file lives in ./data on the host. Back it up by copying that folder. Cookies are marked secure in production, so put the container behind HTTPS.
| Variable | Required | Default |
|---|---|---|
SESSION_SECRET | yes | |
APP_URL | yes | |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | one provider pair | |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | one provider pair | |
DATABASE_PATH | no | ./data/payup.db (/app/data/payup.db in Docker) |
APP_LOCALE | no | es-ES, used when the browser sends no usable Accept-Language |
OPERATOR_NAME / CONTACT_EMAIL | for a public deployment | shown on the Terms and Privacy pages; the pages say they are not configured until you set them |
TRUST_PROXY | behind a reverse proxy | loopback, linklocal, uniquelocal: trusts X-Forwarded-* from private-network proxies such as a Docker network or a proxy on the same host. Set to true to trust any, or an Express trust-proxy value. The server also treats every request as addressed to APP_URL, so form posts work behind any proxy as long as APP_URL is the public origin. |
PORT | no | 3000 |
Legal pages live at /terms and /privacy (plain-language drafts, not legal advice; set OPERATOR_NAME and CONTACT_EMAIL). There is no cookie banner because the only cookies are the session, the ten-minute sign-in cookie and the timezone cookie. Users can delete their account from their profile page (/profile, behind the avatar in the top-right corner), which also shows their stats.
/admin/stats shows combined figures for the whole deployment: people, jars, fines and settle-ups, sign-ups and fines per week, sign-in providers, public and private jars, and money per currency. It never names a person, jar or note. Admins reach it from the avatar menu; everyone else gets a 404.
Nobody is an admin until you set the flag by hand. Sign in once, then:
docker compose exec payup node -e "console.log(require('better-sqlite3')('/app/data/payup.db').prepare('UPDATE users SET is_admin = 1 WHERE email = ?').run(process.argv[1]).changes)" [email protected]
It prints 1 when it found the account and 0 when no account has that email. It takes effect on the next page load. Use is_admin = 0 to take it away again, and ./data/payup.db instead of the /app/data path to run it outside Docker.
Public links are /j/<slug>: the slug is made from the title (or typed by you), checked live for availability, and changing it on the edit page stops the old link working.
Dates render in the viewer's browser timezone (a tz cookie set on first load); money renders in the viewer's locale.