Skip to content

Commit ebaa044

Browse files
committed
feat: multi-provider auth wizard, auto NEXTAUTH_URL, custom domain docs in README
1 parent 0e5a338 commit ebaa044

4 files changed

Lines changed: 531 additions & 7 deletions

File tree

‎README.md‎

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# SplitPro — DAppNode Package
2+
3+
[SplitPro](https://splitpro.app) is a free, open-source app for splitting expenses with friends and groups. This package runs SplitPro on your DAppNode with PostgreSQL, pg_cron for recurring transactions, and your choice of authentication provider.
4+
5+
- **Web UI (local network / VPN):** http://split-pro.public.dappnode:3000
6+
- **Web UI (internet):** https://splitpro.\<hash\>.dyndns.dappnode.io (requires [HTTPS package](https://docs.dappnode.io/user/packages/https))
7+
8+
---
9+
10+
## First-time setup
11+
12+
On first launch the package auto-generates `POSTGRES_PASSWORD` and `NEXTAUTH_SECRET` and stores them in the config volume. These are never shown again — back up the config volume if you need to migrate.
13+
14+
The setup wizard runs on install and asks for your authentication provider credentials. At least one provider must be configured for sign-in to work.
15+
16+
---
17+
18+
## Authentication providers
19+
20+
SplitPro uses [NextAuth](https://next-auth.js.org) for authentication. Providers are enabled by setting the relevant environment variables. The setup wizard lets you choose one provider at install time; you can add more later via **Config** in the DAppNode UI.
21+
22+
Username/password login is not supported. All sign-in flows are passwordless (magic link or OAuth).
23+
24+
### Email (SMTP) — magic link
25+
26+
Users receive a one-time sign-in link by email. No password required.
27+
28+
**Required variables:** `FROM_EMAIL`, `EMAIL_SERVER_HOST`, `EMAIL_SERVER_PORT`, `EMAIL_SERVER_USER`, `EMAIL_SERVER_PASSWORD`
29+
30+
**Common SMTP providers:**
31+
32+
| Provider | Host | Port | Notes |
33+
|---|---|---|---|
34+
| Gmail | smtp.gmail.com | 587 | Use an [App Password](https://myaccount.google.com/apppasswords), not your account password. Requires 2-Step Verification. |
35+
| Resend | smtp.resend.com | 465 | Free tier available. API key as password. |
36+
| Mailgun | smtp.mailgun.org | 587 | Requires domain verification. |
37+
| Brevo | smtp-relay.brevo.com | 587 | Free tier available. |
38+
| Postmark | smtp.postmarkapp.com | 587 | Transactional email focused. |
39+
40+
### Google OAuth
41+
42+
Users sign in with their Google account. No email server needed.
43+
44+
**Required variables:** `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`
45+
46+
**How to get credentials:**
47+
48+
1. Go to [Google Cloud Console](https://console.cloud.google.com) → **APIs & Services → Credentials**
49+
2. Create an **OAuth 2.0 Client ID** (type: Web application)
50+
3. Add your SplitPro URL as an **Authorized redirect URI:**
51+
`https://splitpro.<hash>.dyndns.dappnode.io/api/auth/callback/google`
52+
(replace with your custom domain if you have one)
53+
4. Copy the **Client ID** and **Client Secret** into the setup wizard
54+
55+
### OIDC (generic)
56+
57+
Connects to any OpenID Connect provider.
58+
59+
**Required variables:** `OIDC_NAME`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_WELL_KNOWN_URL`
60+
61+
Set `OIDC_WELL_KNOWN_URL` to your provider's discovery endpoint, e.g.:
62+
`https://sso.example.com/.well-known/openid-configuration`
63+
64+
Set redirect URI in your provider to:
65+
`<your-splitpro-url>/api/auth/callback/oidc`
66+
67+
### Keycloak
68+
69+
**Required variables:** `KEYCLOAK_ID`, `KEYCLOAK_SECRET`, `KEYCLOAK_ISSUER`
70+
71+
**How to get credentials:**
72+
73+
1. Keycloak Admin Console → your realm → **Clients → Create**
74+
2. Type: OpenID Connect
75+
3. Set redirect URI: `<your-splitpro-url>/api/auth/callback/keycloak`
76+
4. Under **Credentials** tab, copy the client secret
77+
5. `KEYCLOAK_ISSUER` format: `https://keycloak.example.com/realms/your-realm`
78+
79+
### Authentik
80+
81+
**Required variables:** `AUTHENTIK_ID`, `AUTHENTIK_SECRET`, `AUTHENTIK_ISSUER`
82+
83+
**How to get credentials:**
84+
85+
1. Authentik Admin → **Applications → Providers → Create** (type: OAuth2/OpenID)
86+
2. Set redirect URI: `<your-splitpro-url>/api/auth/callback/authentik`
87+
3. `AUTHENTIK_ISSUER` format: `https://authentik.example.com/application/o/splitpro/`
88+
89+
---
90+
91+
## Accessing SplitPro
92+
93+
### Local network / VPN
94+
95+
Access via the DAppNode VPN or local network:
96+
97+
```
98+
http://split-pro.public.dappnode:3000
99+
```
100+
101+
### DynDNS (default for internet access)
102+
103+
Install the [HTTPS package](https://docs.dappnode.io/user/packages/https). SplitPro automatically detects your DynDNS domain from the `_DAPPNODE_GLOBAL_DOMAIN` environment variable and sets `NEXTAUTH_URL` accordingly. No configuration needed.
104+
105+
Your URL will be:
106+
```
107+
https://splitpro.<hash>.dyndns.dappnode.io
108+
```
109+
110+
### Custom domain
111+
112+
Use this if you want a URL like `https://splitpro.yourdomain.com`.
113+
114+
**Why NEXTAUTH_URL matters:** NextAuth embeds the app URL in magic-link emails and session cookies. Accessing SplitPro from a different URL than `NEXTAUTH_URL` causes login failures and can create separate user accounts per URL. Set `NEXTAUTH_URL` in **SplitPro → Config** to match the URL you browse to.
115+
116+
**Why not a simple A record:** DAppNodes run on residential internet with dynamic IPs. The HTTPS package handles DynDNS certificates, but it uses DAppNode's own certificate signing service which only works for `*.dyndns.dappnode.io` — it cannot issue certificates for external domains you own.
117+
118+
The cleanest solution is **Cloudflare as a proxy**. Cloudflare terminates TLS for your custom domain and resolves your CNAME to the DynDNS name, so the dynamic IP is handled automatically.
119+
120+
#### Option A — Cloudflare Worker (recommended)
121+
122+
Cloudflare's proxied CNAME only forwards traffic on [specific ports](https://developers.cloudflare.com/fundamentals/reference/network-ports/). Port 3000 is not in that list. A Worker bypasses this constraint and is simpler than port forwarding.
123+
124+
**1. Create the Worker**
125+
126+
In Cloudflare dashboard → **Workers & Pages → Create**, deploy:
127+
128+
```js
129+
export default {
130+
async fetch(request) {
131+
const url = new URL(request.url);
132+
url.hostname = "splitpro.<hash>.dyndns.dappnode.io";
133+
url.port = "3000";
134+
url.protocol = "http:";
135+
return fetch(url, request);
136+
}
137+
}
138+
```
139+
140+
Replace `splitpro.<hash>.dyndns.dappnode.io` with your actual DynDNS subdomain.
141+
142+
**2. Add a custom domain to the Worker**
143+
144+
In the Worker's **Settings → Domains & Routes**, add:
145+
```
146+
splitpro.yourdomain.com
147+
```
148+
149+
Cloudflare handles TLS automatically. No router port forwarding needed.
150+
151+
**3. Set NEXTAUTH_URL**
152+
153+
In DAppNode UI → **SplitPro → Config**, set **App URL** to:
154+
```
155+
https://splitpro.yourdomain.com
156+
```
157+
158+
No trailing slash. Save and restart the package.
159+
160+
#### Option B — Cloudflare proxied CNAME with port forwarding
161+
162+
Use this if you prefer not to use Workers, and are willing to expose a Cloudflare-supported port.
163+
164+
**1. Forward a supported port on your router**
165+
166+
Cloudflare proxied connections support ports: 80, 443, 2052, 2053, 2082, 2083, 2086, 2087, 2095, 2096, 8080, 8443, 8880.
167+
168+
Forward one of these (e.g. 8080) from your router to your DAppNode's local IP on port 3000.
169+
170+
**2. Create a CNAME record in Cloudflare**
171+
172+
| Type | Name | Target | Proxy |
173+
|---|---|---|---|
174+
| CNAME | `splitpro` | `splitpro.<hash>.dyndns.dappnode.io` | Proxied (orange cloud) |
175+
176+
**3. Set SSL/TLS mode to Full**
177+
178+
Cloudflare → **SSL/TLS → Overview** → **Full** (not Flexible, not Full Strict).
179+
180+
**4. Set NEXTAUTH_URL** same as Option A.
181+
182+
---
183+
184+
## Backup
185+
186+
The package backs up three volumes:
187+
188+
| Volume | Contents |
189+
|---|---|
190+
| `database` | PostgreSQL data (groups, expenses, users) |
191+
| `uploads` | Receipt images |
192+
| `config` | Auto-generated secrets (POSTGRES_PASSWORD, NEXTAUTH_SECRET) |
193+
194+
Use the DAppNode backup feature or snapshot the volumes manually before any migration.
195+
196+
---
197+
198+
## Troubleshooting
199+
200+
**Sign-in emails are not arriving**
201+
Check SMTP credentials in **Config**. Verify the SMTP host and port are reachable. For Gmail, confirm you are using an App Password, not your account password.
202+
203+
**Magic-link emails link to the wrong URL**
204+
`NEXTAUTH_URL` does not match the URL you are browsing to. Check for a trailing slash or wrong protocol.
205+
206+
**I get a redirect loop on the custom domain**
207+
Cloudflare SSL/TLS mode is set to Flexible. Change it to Full.
208+
209+
**Cloudflare Worker returns 522**
210+
The DynDNS hostname in the Worker is wrong, or the HTTPS package is not running. Verify the DynDNS URL works directly in a browser first.
211+
212+
**I signed up via DynDNS and switched to a custom domain — my account seems missing**
213+
Your account exists. The session cookie is scoped to the old origin. Sign in again with the same email on the new URL — you will land in the same account. No data is lost.
214+
215+
**Recurring transactions are not running**
216+
The package uses pg_cron. Verify the postgres service is healthy and the `pg_cron` extension is loaded (`SHOW shared_preload_libraries` should include `pg_cron`).

‎build/splitpro/entrypoint.sh‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ for file_var in $(env | grep -E '^[^=]+_FILE=' | cut -d'=' -f1); do
1515
echo "Set $base_var from $file_var ($file_path)" >&2
1616
done
1717

18+
# Set NEXTAUTH_URL: use value from setup wizard if provided, otherwise auto-detect
19+
# from DAppNode's DynDNS domain, otherwise fall back to the internal package URL.
20+
if [ -z "$NEXTAUTH_URL" ] && [ -n "$_DAPPNODE_GLOBAL_DOMAIN" ]; then
21+
export NEXTAUTH_URL="https://splitpro.${_DAPPNODE_GLOBAL_DOMAIN}"
22+
echo "Auto-set NEXTAUTH_URL to $NEXTAUTH_URL" >&2
23+
elif [ -z "$NEXTAUTH_URL" ]; then
24+
export NEXTAUTH_URL="http://split-pro.public.dappnode:3000"
25+
echo "Set NEXTAUTH_URL to internal fallback: $NEXTAUTH_URL" >&2
26+
fi
27+
1828
# Construct and EXPORT DATABASE_URL (upstream start.sh sets it but doesn't export it)
1929
if [ -z "$DATABASE_URL" ]; then
2030
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_CONTAINER_NAME}:${POSTGRES_PORT}/${POSTGRES_DB}"

‎docker-compose.yml‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ services:
5252
- POSTGRES_PORT=5432
5353
- POSTGRES_DB=splitpro
5454
- NEXTAUTH_SECRET_FILE=/config/NEXTAUTH_SECRET
55-
- NEXTAUTH_URL=http://split-pro.public.dappnode:3000
55+
- NEXTAUTH_URL
5656
- NEXTAUTH_URL_INTERNAL=http://splitpro:3000
5757
- DEFAULT_HOMEPAGE=/balances
5858
- CURRENCY_RATE_PROVIDER=frankfurter
@@ -66,6 +66,18 @@ services:
6666
- EMAIL_SERVER_PORT
6767
- EMAIL_SERVER_USER
6868
- EMAIL_SERVER_PASSWORD
69+
- GOOGLE_CLIENT_ID
70+
- GOOGLE_CLIENT_SECRET
71+
- OIDC_NAME
72+
- OIDC_CLIENT_ID
73+
- OIDC_CLIENT_SECRET
74+
- OIDC_WELL_KNOWN_URL
75+
- KEYCLOAK_ID
76+
- KEYCLOAK_SECRET
77+
- KEYCLOAK_ISSUER
78+
- AUTHENTIK_ID
79+
- AUTHENTIK_SECRET
80+
- AUTHENTIK_ISSUER
6981
ports:
7082
- "3000:3000/tcp"
7183
volumes:

0 commit comments

Comments
 (0)