Compare commits
6 Commits
1493fab6af
...
64773f0a16
| Author | SHA1 | Date | |
|---|---|---|---|
| 64773f0a16 | |||
| d603e966e6 | |||
| 7622a0deb4 | |||
| 3a9be57bc5 | |||
| ebf1024836 | |||
| a282a8a04a |
@@ -25,3 +25,4 @@
|
|||||||
*.pem
|
*.pem
|
||||||
*.crt
|
*.crt
|
||||||
secrets/
|
secrets/
|
||||||
|
TODO
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ handled separately — see [Exposing services](#exposing-services).
|
|||||||
| [codeserver](codeserver/) | 8443 | VS Code in a browser. Effectively a remote shell — read its README before exposing |
|
| [codeserver](codeserver/) | 8443 | VS Code in a browser. Effectively a remote shell — read its README before exposing |
|
||||||
| [copyparty](copyparty/) | 3923 | File server built for big/resumable transfers, WebDAV, media index |
|
| [copyparty](copyparty/) | 3923 | File server built for big/resumable transfers, WebDAV, media index |
|
||||||
| [filebrowser](filebrowser/) | 3672 | Web file manager for one directory tree (FileBrowser Quantum fork) |
|
| [filebrowser](filebrowser/) | 3672 | Web file manager for one directory tree (FileBrowser Quantum fork) |
|
||||||
|
| [firefly](firefly/) | 8564 | Personal finance / double-entry ledger. Also 8566 for the data importer |
|
||||||
| [jellyfin](jellyfin/) | 8096 | Media streaming. Also 7359/udp for auto-discovery |
|
| [jellyfin](jellyfin/) | 8096 | Media streaming. Also 7359/udp for auto-discovery |
|
||||||
| [openwebui](openwebui/) | 3033 | LLM chat UI. One `OWUI_TAG` switches between CPU, CUDA and bundled-Ollama |
|
| [openwebui](openwebui/) | 3033 | LLM chat UI. One `OWUI_TAG` switches between CPU, CUDA and bundled-Ollama |
|
||||||
| [overleaf](overleaf/) | 8020 | LaTeX editor. Four containers: app, Mongo (replica set), Redis, init |
|
| [overleaf](overleaf/) | 8020 | LaTeX editor. Four containers: app, Mongo (replica set), Redis, init |
|
||||||
|
|||||||
@@ -81,8 +81,79 @@ Publishes a plain HTTP port; nothing here is proxy-aware.
|
|||||||
- [Cloudflare Tunnel](../cloudflare-tunnel/) — audio is far lighter than video,
|
- [Cloudflare Tunnel](../cloudflare-tunnel/) — audio is far lighter than video,
|
||||||
but it's still media served through the proxy; read
|
but it's still media served through the proxy; read
|
||||||
[the limits](../docs/cloudflare-tunnel.md) before pointing a tunnel at it.
|
[the limits](../docs/cloudflare-tunnel.md) before pointing a tunnel at it.
|
||||||
- [Single sign-on with Authentik](../docs/authentik-sso.md) — as with Jellyfin,
|
- [Single sign-on with Authentik](../docs/authentik-sso.md) — Audiobookshelf
|
||||||
the mobile apps won't understand a forward-auth login page.
|
speaks OIDC natively, so use that rather than forward auth: the mobile apps
|
||||||
|
can't render a forward-auth login page. See the section below.
|
||||||
|
|
||||||
|
## Single sign-on with Authentik
|
||||||
|
|
||||||
|
Worth doing here, because Audiobookshelf is one of the apps that speaks OIDC
|
||||||
|
itself — real accounts, real logout, and the mobile apps keep working. Forward
|
||||||
|
auth would break them.
|
||||||
|
|
||||||
|
**It needs a reverse proxy first.** The whole flow is built from absolute HTTPS
|
||||||
|
URLs, so there has to be a real hostname in front:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
audiobookshelf.example.com {
|
||||||
|
reverse_proxy <ip>:13378
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then follow
|
||||||
|
[Authentik's Audiobookshelf guide](https://integrations.goauthentik.io/media/audiobookshelf/):
|
||||||
|
create an OAuth2/OpenID Connect provider, note the application **slug**, and
|
||||||
|
put the issuer URL, client ID and client secret into
|
||||||
|
**Settings → Authentication** on the Audiobookshelf side. **Auto-populate**
|
||||||
|
fills in the OIDC endpoints for you.
|
||||||
|
|
||||||
|
### The redirect URIs are where this goes wrong
|
||||||
|
|
||||||
|
[Step 2 of the guide](https://integrations.goauthentik.io/media/audiobookshelf/#create-an-application-and-provider)
|
||||||
|
gives these three:
|
||||||
|
|
||||||
|
```
|
||||||
|
Strict Authorization https://audiobookshelf.company/auth/openid/callback
|
||||||
|
Strict Authorization https://audiobookshelf.company/auth/openid/mobile-redirect
|
||||||
|
Strict Post Logout https://audiobookshelf.company/login
|
||||||
|
```
|
||||||
|
|
||||||
|
Those did not work for me. Mine needed an extra `/audiobookshelf` path segment:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://audiobookshelf.company.com/audiobookshelf/auth/openid/callback
|
||||||
|
https://audiobookshelf.company.com/audiobookshelf/auth/openid/mobile-redirect
|
||||||
|
```
|
||||||
|
|
||||||
|
**Don't guess at this — Audiobookshelf tells you the answer.** Its
|
||||||
|
**Settings → Authentication** page has a subfolder section that prints the exact
|
||||||
|
URIs to authorize:
|
||||||
|
|
||||||
|
> Authorize these URLs in your OAuth provider to allow redirection back to the
|
||||||
|
> web app after login:
|
||||||
|
>
|
||||||
|
> `https://<your.server.com>/audiobookshelf/auth/openid/callback`
|
||||||
|
> `https://<your.server.com>/audiobookshelf/auth/openid/mobile-redirect`
|
||||||
|
|
||||||
|
Copy those into Authentik verbatim. A mismatch surfaces as a generic error that
|
||||||
|
doesn't name the URI, which is why this is worth ten seconds of checking rather
|
||||||
|
than an evening of guessing.
|
||||||
|
|
||||||
|
### Mobile apps need their own redirect URIs
|
||||||
|
|
||||||
|
The phone clients don't come back over HTTPS — they hand off to a custom URL
|
||||||
|
scheme. These go in **Allowed Mobile Redirect URIs**:
|
||||||
|
|
||||||
|
```
|
||||||
|
audiobookshelf://oauth
|
||||||
|
https://authentik.company.com/auth/openid/mobile-redirect
|
||||||
|
|
||||||
|
plappa://oauth # Plappa, iOS
|
||||||
|
prologue://oauth # Prologue, iOS
|
||||||
|
```
|
||||||
|
|
||||||
|
One per app: a client you haven't listed fails at the last step of a login that
|
||||||
|
otherwise looked fine. Add the scheme for each third-party app you actually use.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ what the config's `e2ts` needs.
|
|||||||
but a leaked link is public access to that file.
|
but a leaked link is public access to that file.
|
||||||
- **`e2dsa` indexes on first start.** For a large share, expect the first boot
|
- **`e2dsa` indexes on first start.** For a large share, expect the first boot
|
||||||
to churn; uploads are blocked while it runs and it says so in the log.
|
to churn; uploads are blocked while it runs and it says so in the log.
|
||||||
|
- **It is CPU-hungry, and on a small box you will feel it.** On my Intel N100
|
||||||
|
this is the service that shows up in `top`, not the media server. I still run
|
||||||
|
it and still want it, but I stop the container when I'm not actively moving
|
||||||
|
files rather than leave it idling — on a four-core low-TDP chip that headroom
|
||||||
|
is worth more than instant availability. The expensive parts are the ones
|
||||||
|
this config asks for: `e2dsa` walking the share and the media tag/thumbnail
|
||||||
|
work that the `ac` image exists to provide. Nothing is misconfigured — it's
|
||||||
|
what the feature set costs. If you want it always-on there, drop to the `min`
|
||||||
|
image and turn off `e2ts`, and accept losing thumbnails.
|
||||||
- **Back up `DATA_PATH`.** It holds the index and the filekeys that make share
|
- **Back up `DATA_PATH`.** It holds the index and the filekeys that make share
|
||||||
links stable.
|
links stable.
|
||||||
- **Version-check is opt-in.** Uncomment `vc-url` and `vc-exit` in the config
|
- **Version-check is opt-in.** Uncomment `vc-url` and `vc-exit` in the config
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Copy to .env and adjust. .env is git-ignored; this file is not.
|
||||||
|
#
|
||||||
|
# ─── READ THIS FIRST ────────────────────────────────────────────────
|
||||||
|
# This stack is different from the others in this repo. Elsewhere, .env
|
||||||
|
# holds a handful of variables that Compose interpolates into
|
||||||
|
# compose.yaml. Here, `env_file: .env` hands the whole file to the
|
||||||
|
# application, so .env *is* Firefly III's configuration file.
|
||||||
|
#
|
||||||
|
# Because of that, upstream's instruction is to start from their full
|
||||||
|
# file rather than write one by hand:
|
||||||
|
#
|
||||||
|
# wget https://raw.githubusercontent.com/firefly-iii/firefly-iii/main/.env.example -O .env
|
||||||
|
#
|
||||||
|
# Then change the values below. This file is the list of what to change
|
||||||
|
# and why -- not a drop-in replacement for theirs.
|
||||||
|
|
||||||
|
# ─── Required: the two 32-character secrets ─────────────────────────
|
||||||
|
# Both must be EXACTLY 32 characters. Not "at least" -- exactly. Firefly
|
||||||
|
# rejects anything else, and the cron token silently 404s.
|
||||||
|
#
|
||||||
|
# openssl rand -base64 24 | cut -c1-32
|
||||||
|
#
|
||||||
|
APP_KEY=
|
||||||
|
|
||||||
|
# The cron container authenticates with this and nothing else -- it is a
|
||||||
|
# bearer token in a URL path. Treat it as a password.
|
||||||
|
STATIC_CRON_TOKEN=
|
||||||
|
|
||||||
|
# ─── Required: who you are ──────────────────────────────────────────
|
||||||
|
# Used as the "from" address and shown in the admin pages.
|
||||||
|
SITE_OWNER=you@example.com
|
||||||
|
|
||||||
|
# The external URL people actually type. Firefly builds links from it,
|
||||||
|
# so a wrong value breaks password resets the same way it does anywhere.
|
||||||
|
APP_URL=http://localhost:8564
|
||||||
|
|
||||||
|
# Change this value to your preferred time zone. It also decides when the
|
||||||
|
# cron job runs -- see the README.
|
||||||
|
TZ=Europe/Paris
|
||||||
|
|
||||||
|
# ─── Database ───────────────────────────────────────────────────────
|
||||||
|
# DB_HOST is the compose SERVICE name, not the container name.
|
||||||
|
# DB_PASSWORD must match MYSQL_PASSWORD in .db.env, or the app boots and
|
||||||
|
# then fails every request with a connection error.
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=firefly
|
||||||
|
DB_USERNAME=firefly
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# ─── Behind a reverse proxy ─────────────────────────────────────────
|
||||||
|
# Upstream's own comment: "Set it to ** and reverse proxies work just
|
||||||
|
# fine." Without it Firefly sees the proxy's IP as the client and builds
|
||||||
|
# http:// links behind your TLS.
|
||||||
|
TRUSTED_PROXIES=**
|
||||||
|
|
||||||
|
# ─── Optional ───────────────────────────────────────────────────────
|
||||||
|
#DEFAULT_LANGUAGE=en_US
|
||||||
|
# Defaults to `log`, which writes mail to the log instead of sending it.
|
||||||
|
# Fine for a single-user install -- you never need a password reset email
|
||||||
|
# if you are also the admin.
|
||||||
|
#MAIL_MAILER=log
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
# Firefly III
|
||||||
|
|
||||||
|
Personal finance manager: accounts, budgets, bills and recurring transactions,
|
||||||
|
with a double-entry ledger underneath. Self-hosted, so your bank data stays on
|
||||||
|
your disk instead of in someone's analytics pipeline.
|
||||||
|
|
||||||
|
Two web UIs come with this stack — Firefly itself, and a separate **Data
|
||||||
|
Importer** that pulls transactions in from CSV files, Nordigen/GoCardless or
|
||||||
|
Spectre.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
Firefly needs **three** env files, not one. Only `.env` has an example in this
|
||||||
|
repo — the other two are three lines each and are printed below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Firefly's own configuration. Start from upstream's full file:
|
||||||
|
wget https://raw.githubusercontent.com/firefly-iii/firefly-iii/main/.env.example -O .env
|
||||||
|
$EDITOR .env # see .env.example here for what to change
|
||||||
|
|
||||||
|
# 2. Generate the two secrets. Both must be EXACTLY 32 characters:
|
||||||
|
openssl rand -base64 24 | cut -c1-32 # -> APP_KEY
|
||||||
|
openssl rand -base64 24 | cut -c1-32 # -> STATIC_CRON_TOKEN
|
||||||
|
|
||||||
|
# 3. The database credentials:
|
||||||
|
cat > .db.env <<'EOF'
|
||||||
|
MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||||
|
MYSQL_USER=firefly
|
||||||
|
MYSQL_PASSWORD=change_me
|
||||||
|
MYSQL_DATABASE=firefly
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 4. The importer (delete the service from compose.yaml if you don't want it):
|
||||||
|
cat > .importer.env <<'EOF'
|
||||||
|
FIREFLY_III_URL=http://app:8080
|
||||||
|
VANITY_URL=http://localhost:8564
|
||||||
|
TRUSTED_PROXIES=**
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 5. Wire up the cron token — see "The cron job" below. This step is manual.
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
`MYSQL_PASSWORD` in `.db.env` and `DB_PASSWORD` in `.env` are the same password
|
||||||
|
written twice. They must match.
|
||||||
|
|
||||||
|
Open `http://<host>:8564`, register, and the first account created becomes the
|
||||||
|
admin. There's no separate admin bootstrap step.
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Port | Proto | Purpose |
|
||||||
|
|------|-------|---------|
|
||||||
|
| 8564 | http | Firefly III web UI and API (container listens on 8080) |
|
||||||
|
| 8566 | http | Data Importer web UI (container listens on 8080) |
|
||||||
|
|
||||||
|
MariaDB is reachable only on the internal `firefly_iii` network.
|
||||||
|
|
||||||
|
## The cron job
|
||||||
|
|
||||||
|
**This is the part everyone skips, and the failure is silent.** Firefly does not
|
||||||
|
schedule anything itself — the `app` container serves HTTP and nothing else.
|
||||||
|
Recurring transactions, auto-budgets and periodic housekeeping only happen when
|
||||||
|
something calls the cron endpoint:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET http://app:8080/api/v1/cron/<STATIC_CRON_TOKEN>
|
||||||
|
```
|
||||||
|
|
||||||
|
That is what the fourth container is for. It's a bare `alpine` running `crond`
|
||||||
|
with one entry, hitting that URL at 03:00 daily.
|
||||||
|
|
||||||
|
Skip this and nothing announces itself. The app works, the UI is responsive, and
|
||||||
|
your recurring transactions simply never appear. You notice weeks later when a
|
||||||
|
month is missing its rent entry.
|
||||||
|
|
||||||
|
### Activating it
|
||||||
|
|
||||||
|
The token is a bearer credential in a URL path, and it must be **exactly 32
|
||||||
|
characters**. Anything else is rejected.
|
||||||
|
|
||||||
|
In the compose file here, the cron service has **no `env_file:`**, so the token
|
||||||
|
is not an environment variable it can read — it's baked into the crontab line as
|
||||||
|
the literal string `REPLACEME`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
command: sh -c "echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/REPLACEME\" | crontab - && crond -f -L /dev/stdout"
|
||||||
|
```
|
||||||
|
|
||||||
|
So activation is two edits that must agree:
|
||||||
|
|
||||||
|
1. Set `STATIC_CRON_TOKEN=<your 32 chars>` in `.env` (the **app** validates
|
||||||
|
against this).
|
||||||
|
2. Replace `REPLACEME` in `compose.yaml` with **the same 32 characters** (the
|
||||||
|
**cron container** sends this).
|
||||||
|
|
||||||
|
Get them out of sync and the request 404s every night, forever, with no sign in
|
||||||
|
the Firefly UI.
|
||||||
|
|
||||||
|
### Upstream's newer version is better — consider switching
|
||||||
|
|
||||||
|
Current upstream avoids the hardcoded token entirely by giving the cron
|
||||||
|
container the same `.env` and letting the shell expand it (`$$` escapes the `$`
|
||||||
|
so Compose passes it through rather than interpolating it):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cron:
|
||||||
|
image: alpine
|
||||||
|
restart: always
|
||||||
|
container_name: firefly_iii_cron
|
||||||
|
env_file: .env
|
||||||
|
command: ["sh", "-c", "apk add tzdata && \
|
||||||
|
(ln -s /usr/share/zoneinfo/$$TZ /etc/localtime || true) && \
|
||||||
|
echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/$$STATIC_CRON_TOKEN;echo\" \
|
||||||
|
| crontab - && \
|
||||||
|
crond -f -L /dev/stdout"]
|
||||||
|
networks:
|
||||||
|
- firefly_iii
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
```
|
||||||
|
|
||||||
|
Two real improvements: the token lives in exactly one place, and `apk add
|
||||||
|
tzdata` plus the `localtime` symlink make "03:00" mean 03:00 **your** time. The
|
||||||
|
version in this repo has no tzdata, so its 03:00 is 03:00 UTC — which for me is
|
||||||
|
an hour that shifts twice a year.
|
||||||
|
|
||||||
|
### Checking it actually works
|
||||||
|
|
||||||
|
Don't wait until 03:00 to find out. Call the endpoint by hand:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from the host, through the published port
|
||||||
|
curl "http://localhost:8564/api/v1/cron/<your-token>"
|
||||||
|
|
||||||
|
# or from inside the cron container, which proves DNS and the network too
|
||||||
|
docker exec firefly_iii_cron wget -qO- "http://app:8080/api/v1/cron/<your-token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
A correct token returns JSON describing what ran. A wrong one returns a 404 —
|
||||||
|
that 404 is the whole failure mode, so seeing it here is the point of the test.
|
||||||
|
|
||||||
|
The cron container logs to stdout (`crond -f -L /dev/stdout`), so after a
|
||||||
|
scheduled run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs firefly_iii_cron
|
||||||
|
```
|
||||||
|
|
||||||
|
There's also `docker exec firefly_iii_core php artisan firefly-iii:cron` if you'd
|
||||||
|
rather drive it from the app side and skip the token entirely.
|
||||||
|
|
||||||
|
## Why it looks like this
|
||||||
|
|
||||||
|
**Four containers, and each one earns its place.** `app` is PHP serving HTTP.
|
||||||
|
`db` is MariaDB — Firefly is a double-entry ledger and wants a real relational
|
||||||
|
database, not SQLite. `cron` exists because `app` has no scheduler (see above).
|
||||||
|
`importer` is genuinely optional: it's a separate application with its own
|
||||||
|
release cycle that talks to Firefly over the API like any other client. Delete
|
||||||
|
the service if you only ever enter transactions by hand.
|
||||||
|
|
||||||
|
**The importer must be pointed at `http://app:8080`.** Not `localhost`, not the
|
||||||
|
public URL. The compose file carries upstream's warning at the top: other URLs
|
||||||
|
give `500 | Server Error`. It's container-to-container traffic on the compose
|
||||||
|
network, so it uses the service name and the *internal* port.
|
||||||
|
|
||||||
|
**`VANITY_URL` is the other half of that.** The importer talks to `app:8080`,
|
||||||
|
but the links it renders in your browser have to be clickable from outside — so
|
||||||
|
that variable holds the public URL. Two URLs for one service, one for machines
|
||||||
|
and one for humans.
|
||||||
|
|
||||||
|
**This stack still follows upstream's layout, not this repo's conventions.**
|
||||||
|
Worth stating plainly rather than pretending otherwise, because the differences
|
||||||
|
are visible:
|
||||||
|
|
||||||
|
- Data goes to `./firefly_iii_upload` and `./firefly_iii_db` beside the compose
|
||||||
|
file, instead of `${DATA_PATH}`.
|
||||||
|
- `restart: always` rather than `unless-stopped` — so a deliberate stop does not
|
||||||
|
survive a reboot.
|
||||||
|
- `.env` is the *application's* config file, so the usual "`.env` holds a few
|
||||||
|
Compose variables" pattern doesn't apply here at all.
|
||||||
|
|
||||||
|
Adapting it is a fine idea; just do it deliberately.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **`APP_KEY` and `STATIC_CRON_TOKEN` must be exactly 32 characters.** Not a
|
||||||
|
minimum — exactly. `openssl rand -base64 24 | cut -c1-32` gives you one.
|
||||||
|
A wrong-length `APP_KEY` fails at boot; a wrong-length cron token fails
|
||||||
|
silently at 03:00.
|
||||||
|
- **The top-level `volumes:` block is dead code.** It declares
|
||||||
|
`firefly_iii_upload` and `firefly_iii_db`, but both services use *relative
|
||||||
|
bind mounts* (`./firefly_iii_upload`) instead. The named volumes are created
|
||||||
|
and never written to. Harmless, but if you go looking for your database in
|
||||||
|
`docker volume ls` you'll find an empty volume and conclude the wrong thing —
|
||||||
|
the data is in the directory next to `compose.yaml`.
|
||||||
|
- **MariaDB creates its bind directory as root.** Expect `./firefly_iii_db` to
|
||||||
|
be root-owned; that's normal and it needs to stay that way.
|
||||||
|
- **`DB_PASSWORD` (`.env`) and `MYSQL_PASSWORD` (`.db.env`) are the same secret
|
||||||
|
in two files.** Changing one and not the other gives you an app that starts
|
||||||
|
cleanly and then errors on every page.
|
||||||
|
- **Changing `MYSQL_PASSWORD` after the first start does nothing.** The database
|
||||||
|
is initialized once, from the empty data directory. After that the credentials
|
||||||
|
live in MariaDB, and the env var is ignored — change it in the database, or
|
||||||
|
wipe `./firefly_iii_db` and start over.
|
||||||
|
- **`:latest` on both images.** Firefly does schema migrations between releases;
|
||||||
|
an unattended pull across a major is not how you want to meet them. Pin a tag
|
||||||
|
once you're settled.
|
||||||
|
- **Back up `./firefly_iii_db`.** That's the ledger. `firefly_iii_upload` holds
|
||||||
|
attachments and matters less, but isn't regenerable either.
|
||||||
|
- **The importer holds a Personal Access Token, not a password.** Upstream is
|
||||||
|
emphatic that the "command line token" is the wrong one. You can use a client
|
||||||
|
ID instead and leave the token empty.
|
||||||
|
|
||||||
|
## Exposing it
|
||||||
|
|
||||||
|
Publishes plain HTTP; set `APP_URL` and `TRUSTED_PROXIES=**` and it behaves
|
||||||
|
behind a proxy.
|
||||||
|
|
||||||
|
- [Reverse proxy with Caddy](../docs/reverse-proxy.md) — the default choice.
|
||||||
|
- [Cloudflare Tunnel](../cloudflare-tunnel/) — fine for this one. It's a text
|
||||||
|
app; nothing here bumps the ~100 MB body cap unless you attach large files.
|
||||||
|
- [Single sign-on with Authentik](../docs/authentik-sso.md) — **think first.**
|
||||||
|
This is your bank data, and Firefly's own login is the only thing in front of
|
||||||
|
it today. Keep a working local admin until any SSO flow is proven.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Upstream docs: <https://docs.firefly-iii.org/>
|
||||||
|
- Docker install guide: <https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/>
|
||||||
|
- Their sample compose: <https://github.com/firefly-iii/docker/blob/main/docker-compose.yml>
|
||||||
|
- Full `.env.example`: <https://github.com/firefly-iii/firefly-iii/blob/main/.env.example>
|
||||||
|
- Data Importer: <https://docs.firefly-iii.org/how-to/data-importer/>
|
||||||
|
- Images: <https://hub.docker.com/r/fireflyiii/core> · <https://hub.docker.com/r/fireflyiii/data-importer>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# The Firefly III Data Importer will ask you for the Firefly III URL and a "Client ID".
|
||||||
|
# You can generate the Client ID at http://localhost/profile (after registering)
|
||||||
|
# The Firefly III URL is: http://app:8080
|
||||||
|
#
|
||||||
|
# Other URL's will give 500 | Server Error
|
||||||
|
#
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: fireflyiii/core:latest
|
||||||
|
hostname: app
|
||||||
|
container_name: firefly_iii_core
|
||||||
|
networks:
|
||||||
|
- firefly_iii
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./firefly_iii_upload:/var/www/html/storage/upload
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- '8564:8080'
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
db:
|
||||||
|
image: mariadb
|
||||||
|
hostname: db
|
||||||
|
container_name: firefly_iii_db
|
||||||
|
networks:
|
||||||
|
- firefly_iii
|
||||||
|
restart: always
|
||||||
|
env_file: .db.env
|
||||||
|
volumes:
|
||||||
|
- ./firefly_iii_db:/var/lib/mysql
|
||||||
|
|
||||||
|
importer:
|
||||||
|
image: fireflyiii/data-importer:latest
|
||||||
|
hostname: importer
|
||||||
|
restart: always
|
||||||
|
container_name: firefly_iii_importer
|
||||||
|
networks:
|
||||||
|
- firefly_iii
|
||||||
|
ports:
|
||||||
|
- '8566:8080'
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
env_file: .importer.env
|
||||||
|
|
||||||
|
cron:
|
||||||
|
#
|
||||||
|
# To make this work, set STATIC_CRON_TOKEN in your .env file or as an environment variable and replace REPLACEME below
|
||||||
|
# The STATIC_CRON_TOKEN must be *exactly* 32 characters long
|
||||||
|
#
|
||||||
|
image: alpine
|
||||||
|
container_name: firefly_iii_cron
|
||||||
|
restart: always
|
||||||
|
command: sh -c "echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/REPLACEME\" | crontab - && crond -f -L /dev/stdout"
|
||||||
|
networks:
|
||||||
|
- firefly_iii
|
||||||
|
volumes:
|
||||||
|
firefly_iii_upload:
|
||||||
|
firefly_iii_db:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
firefly_iii:
|
||||||
|
driver: bridge
|
||||||
+34
-2
@@ -100,8 +100,40 @@ Publishes plain HTTP; nothing here is proxy-aware.
|
|||||||
- [Caddy reverse proxy](../docs/reverse-proxy.md) — what I do.
|
- [Caddy reverse proxy](../docs/reverse-proxy.md) — what I do.
|
||||||
- [Cloudflare Tunnel](../docs/cloudflare-tunnel.md) — **not for this one.**
|
- [Cloudflare Tunnel](../docs/cloudflare-tunnel.md) — **not for this one.**
|
||||||
Streaming video through a tunnel breaks Cloudflare's terms and performs badly.
|
Streaming video through a tunnel breaks Cloudflare's terms and performs badly.
|
||||||
- [Authentik SSO](../docs/authentik-sso.md) — works for the web UI, but native
|
- [Authentik SSO](../docs/authentik-sso.md) — forward auth works for the web UI
|
||||||
clients can't handle a forward-auth login page.
|
but breaks every native client, so use a plugin instead. See below.
|
||||||
|
|
||||||
|
## Single sign-on with Authentik
|
||||||
|
|
||||||
|
Needs a reverse proxy in front, same as anything else doing OIDC — see
|
||||||
|
[Caddy](../docs/reverse-proxy.md).
|
||||||
|
|
||||||
|
**Authentik's own guide takes a different route than I did.**
|
||||||
|
[Their Jellyfin page](https://integrations.goauthentik.io/media/jellyfin/)
|
||||||
|
documents the **LDAP** approach: an LDAP provider plus an outpost, talking to
|
||||||
|
Jellyfin's *LDAP Authentication* plugin. Their reasoning is that Jellyfin has no
|
||||||
|
native external authentication. That works, but it means running an outpost and
|
||||||
|
managing a bind user.
|
||||||
|
|
||||||
|
I used the **SSO-Auth** plugin instead, which does OIDC against Authentik
|
||||||
|
directly — no outpost, no LDAP bind. If you follow their page and wonder why
|
||||||
|
none of the screens match, that's why.
|
||||||
|
|
||||||
|
### The setting that cost me the time
|
||||||
|
|
||||||
|
**Scheme Override → `https`.**
|
||||||
|
|
||||||
|
If the plugin redirects you to an *insecure* URL — you land on `http://` mid-login
|
||||||
|
and it fails — this is the fix. Behind a TLS-terminating proxy, Jellyfin sees a
|
||||||
|
plain HTTP request and builds the callback from that, so the redirect goes out as
|
||||||
|
`http://` even though everything the browser touches is HTTPS.
|
||||||
|
|
||||||
|
Tracked upstream at
|
||||||
|
[goauthentik/authentik#15936](https://github.com/goauthentik/authentik/issues/15936).
|
||||||
|
|
||||||
|
Note this is the same class of problem as `JELLYFIN_PublishedServerUrl` in
|
||||||
|
`compose.yaml`: Jellyfin builds absolute URLs from what it observes, and behind a
|
||||||
|
proxy what it observes is wrong until you tell it otherwise.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user