Funkwhale 配置参数详解
Funkwhale 2.0.12 三层配置体系拆解:环境变量/设置推导/动态偏好,FUNKWHALE_* 全变量分组详解,NGINX_MAX_BODY_SIZE 与实例名偏好改值实测、consolemail 密码重置邮件全链验证。
Funkwhale 配置参数详解
Funkwhale 2.0.12 的配置不是一份平铺的 conf 文件,而是「环境变量 → Django 设置推导 → 动态偏好」三层体系。本文把 2.0.12 实际读取的全部关键环境变量按职责逐组拆解,每组都给出 2.0.12 发行版源码依据与本站实例的真实改值/验证记录——包括
NGINX_MAX_BODY_SIZE改值生效、实例名偏好改值、密码重置邮件渗透主机名等实测。
部署过程与编排文件见《Funkwhale Docker Compose 部署实战》,定位与选型见《Funkwhale 介绍与选型指南》;日常管理动作见《Funkwhale 常用命令与运维实战(核心篇)》。
一、配置体系三层结构
1.1 三层总览
| 层 | 载体 | 生效方式 | 典型例子 |
|---|---|---|---|
| 环境变量层 | .env 文件(编排注入) | 需重建对应服务 | FUNKWHALE_HOSTNAME、NGINX_MAX_BODY_SIZE |
| 设置推导层 | config/settings/common.py 内部逻辑 | 由变量推导,不可直接改 | FUNKWHALE_URL = FUNKWHALE_PROTOCOL://FUNKWHALE_HOSTNAME |
| 动态偏好层 | 数据库(dynamic_preferences) | 保存即生效,无需重启 | 实例名称/描述、默认上传配额 |
三层最容易混淆的点:「实例名称」「实例描述」「注册开放与否」这类运营参数不在 .env 里,它们存数据库、在管理界面即时可改;.env 里的 FUNKWHALE_HOSTNAME 则是技术身份,改动会撕裂联邦身份。
💡 技术拓展:dynamic_preferences 是什么? 这是 Django 生态的一个「把配置存进数据库」的库,Funkwhale 用它承载全部运营侧配置(偏好注册表按
区块__名称寻址,如instance__name)。它与 .env 的分工是:运维视角的机器配置走环境变量,管理员视角的站点配置走偏好——后文 2.3 节有一个两面对照的实测。
1.2 读取机制与 ENV_FILE
Funkwhale 用 django-environ 读取环境变量,设置文件里有一个容易被忽略的入口:
ENV_FILE = env_file = env("ENV_FILE", default=None)
...
env.read_env(env_file)
即支持通过 ENV_FILE 变量指定一个额外文件再读一层(官方裸机安装脚本正是用 ENV_FILE=/srv/funkwhale/config/.env 传配置)。Docker 编排下则由 env_file: .env 直接把变量注入服务进程,两种途径等价。本文所有变量名均来自 2.0.12 发行版 config/settings/common.py 与 production.py 的实际读取行,不是官方文档的二手转述。
1.3 生产模式的两个硬性前置
config/settings/production.py(默认 DJANGO_SETTINGS_MODULE=config.settings.production)在导入时直接读两个变量,缺失即抛异常拒绝启动:
SECRET_KEY = env("DJANGO_SECRET_KEY")
数据库 URL 同理(见第四节)。这是 2.0.12 生产模式的「钥匙检查」:DJANGO_SECRET_KEY 不设,服务起不来。
二、实例身份组:FUNKWHALE_HOSTNAME 与它的推导链
2.1 变量清单与推导逻辑
| 变量 | 默认值 | 说明 |
|---|---|---|
FUNKWHALE_HOSTNAME | 无默认,必填 | 实例对外主机名,如 pod.example.io。官方警告:上线后不可更改,否则联邦身份断裂 |
FUNKWHALE_PROTOCOL | https | 对外访问协议,http 或 https |
FUNKWHALE_URL | 无 | 可整体替代上面两者(从 URL 解析出主机名与协议),二者取其一 |
FUNKWHALE_HOSTNAME_PREFIX / FUNKWHALE_HOSTNAME_SUFFIX | 无 | 开发态 traefik 场景的主机名拼装,生产不用 |
FEDERATION_HOSTNAME | FUNKWHALE_HOSTNAME | 联邦层对外主机名(可分离,一般不动) |
FEDERATION_SERVICE_ACTOR_USERNAME | service | 实例服务账号的用户名 |
FUNKWHALE_SPA_HTML_ROOT | FUNKWHALE_URL | 前端页面根地址(API 注入页面 meta 用) |
FUNKWHALE_SPA_HTML_CACHE_DURATION | 900 | SPA 页面缓存秒数 |
FUNKWHALE_EMBED_URL | FUNKWHALE_URL + "/embed.html" | 可嵌入播放器地址 |
主机名解析的优先级(2.0.12 源码节选,真实分支顺序):
FUNKWHALE_HOSTNAME = None
if FUNKWHALE_HOSTNAME_PREFIX and FUNKWHALE_HOSTNAME_SUFFIX:
FUNKWHALE_HOSTNAME = "{}.{}".format(FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX)
else:
try:
FUNKWHALE_HOSTNAME = env("FUNKWHALE_HOSTNAME")
FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
except Exception:
FUNKWHALE_URL = env("FUNKWHALE_URL")
_parsed = urlsplit(FUNKWHALE_URL)
FUNKWHALE_HOSTNAME = _parsed.netloc
FUNKWHALE_PROTOCOL = _parsed.scheme
FUNKWHALE_URL = f"{FUNKWHALE_PROTOCOL}://{FUNKWHALE_HOSTNAME}"
2.2 主机名的渗透范围实测
本实例设 FUNKWHALE_HOSTNAME=funkwhale.lab、FUNKWHALE_PROTOCOL=http,即 FUNKWHALE_URL=http://funkwhale.lab。实测这个值出现在至少四个互不相干的位置:
- 联邦对象 ID:曲目的艺术家对象
fid为http://funkwhale.lab/federation/music/artists/99209b21-...(API 实测回显); - 密码重置邮件链接:
http://funkwhale.lab/auth/password/reset/confirm?uid=...(第八节实测全文); - 导入结果回显 URL:
http://funkwhale.lab/library/a2b1e8f7-.../upload?import=ops-import-002(部署篇 5.3 节实测); - front 服务内置 Web 服务器的配置:
/manifest.json请求被 302 到http://funkwhale.lab/api/v2/instance/spa-manifest.json(导出 front 运行配置实测可见)。
第 4 项说明一个重要机制:front 的配置里烘焙了 FUNKWHALE_HOSTNAME,改它必须重建 front 服务(docker compose up -d front),只重启不重建不会重新渲染。
2.3 对照实验:实例名称在偏好层
FUNKWHALE_HOSTNAME 是技术身份,而访问者看到的「站点名称」属于动态偏好。在 api 服务内实测修改:
from dynamic_preferences.registries import global_preferences_registry
m = global_preferences_registry.manager()
print('before:', m['instance__name'])
m['instance__name'] = 'Funkwhale Lab Pod'
print('after :', m['instance__name'])
实测输出:
before:
after : Funkwhale Lab Pod
不重启任何服务,nodeinfo 端点立即反映:
curl -s http://127.0.0.1:8140/api/v1/instance/nodeinfo/2.0/ | python3 -c "import json,sys; print(json.load(sys.stdin)['metadata'].get('nodeName'))"
实测输出:
nodeinfo.nodeName = Funkwhale Lab Pod
同样的改动在管理界面(/api/admin/ → 动态偏好)里点几下也能完成——这正是「偏好层即时生效」的含义。
三、Web 服务组:监听、并发与上传上限
| 变量 | 默认值 | 说明 |
|---|---|---|
FUNKWHALE_API_IP | 127.0.0.1 | API 服务监听地址(Docker 内应为 0.0.0.0) |
FUNKWHALE_API_PORT | 5000 | API 监听端口 |
FUNKWHALE_WEB_WORKERS | 4 | API 并行工作进程数,官方编排传给 gunicorn |
FUNKWHALE_FRONTEND_PATH | 无 | 前端 dist 目录,裸机安装用;Docker 下 front 自带前端文件,本文栈实测未消费该变量(2.0.12 源码 grep 无引用) |
NGINX_MAX_BODY_SIZE | 100M | front 内置 Web 服务器的单请求体积上限(Web 上传音乐的上限) |
REVERSE_PROXY_TYPE | nginx | 反代类型(影响受保护文件的头处理) |
PROTECT_FILES_PATH | /_protected | 受保护文件的内部路径前缀 |
3.1 监听与并发实测
FUNKWHALE_WEB_WORKERS=2 + FUNKWHALE_API_PORT=5000 的生效证据在 api 服务启动日志第一行:
+ exec gunicorn config.asgi:application --workers 2 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:5000
FUNKWHALE_API_IP=0.0.0.0 在 Docker 栈下是必需的:宿主机端口映射要靠它把 5000 暴露到网络命名空间边界。裸机安装的官方默认 127.0.0.1(由系统反代转发)则相反。
3.2 NGINX_MAX_BODY_SIZE 改值实测
把 .env 中 NGINX_MAX_BODY_SIZE=100M 改为 50M 后 docker compose up -d front 重建,导出运行中的 Web 服务器配置:
docker exec funkwhale-2.0.12-front nginx -T 2>/dev/null | grep -m2 'client_max_body_size'
实测输出:
client_max_body_size 50M;
client_max_body_size 50M;
两处命中(全局 server 块 + /api/ 反代块),改值生效。随后恢复 100M 并重建,复测回到基线:
client_max_body_size 100M;
结论:Web 上传大文件报 413 时,先查这个变量;它是 front 启动时渲染进配置的,改完必须重建 front 服务。
3.3 受保护文件链路实测
原位导入的音乐文件的播放链路是:API 校验权限后,把请求转交给 front 的内部路径。front 运行配置实测片段:
location /_protected/music/ {
# This is an internal location that is used to serve
# local music files once correct permission / authentication
# has been checked on API side.
# Set this to the same value as your MUSIC_DIRECTORY_PATH setting.
internal;
alias /music/;
add_header Access-Control-Allow-Origin '*';
}
internal 意味着外部直接访问 /_protected/music/... 一律 404,只有 API 发起内部跳转才能取到文件——这就是 PROTECT_FILES_PATH 与 MUSIC_DIRECTORY_PATH 两个变量必须对齐的原因。
四、数据库组:五件套与 URL 组装
| 变量 | 默认值 | 说明 |
|---|---|---|
DATABASE_HOST | localhost | 数据库主机(Docker 内为服务名 postgres) |
DATABASE_PORT | 5432 | 数据库端口 |
DATABASE_NAME | funkwhale | 库名 |
DATABASE_USER | funkwhale | 用户名 |
DATABASE_PASSWORD | funkwhale | 密码 |
DATABASE_URL | 由五件套自动拼装 | 完整连接串,显式提供时整体覆盖五件套 |
DB_CONN_MAX_AGE | 0 | 连接最大复用秒数,0 为每次请求后关闭 |
五件套到 URL 的组装逻辑(2.0.12 源码节选):
DATABASE_URL = env.db(
"DATABASE_URL",
(
f"postgres:"
f"//{DATABASE_USER}:{DATABASE_PASSWORD}"
f"@{DATABASE_HOST}:{DATABASE_PORT}"
f"/{DATABASE_NAME}"
),
)
两点必须写进记忆:
- 2.0.3 起官方要求 Docker 部署显式补齐
DATABASE_HOST/DATABASE_USER/DATABASE_NAME三个变量——从 1.4 时代沿用下来的 .env 缺这三项,升 2.x 后数据库连不上。这是官方 changelog 里少见的「不填必炸」级要求; DATABASE_URL一旦显式提供,优先级高于五件套。两种写法二选一,混着写会以 URL 为准,排查连接问题时先确认没有残留的DATABASE_URL。
五、缓存与异步队列组:一个 Redis 跑两个角色
| 变量 | 默认值 | 说明 |
|---|---|---|
CACHE_URL | redis://127.0.0.1:6379/0 | Django 缓存(含会话)连接串 |
CELERY_BROKER_URL | 取 CACHE_URL | Celery 消息中间件连接串 |
REDIS_PASSWORD | 无 | 编排文件注入 Redis 服务端 --requirepass 用(API 本体不读它) |
CELERYD_CONCURRENCY | 1 | worker 并行进程数 |
LOGLEVEL | info(受 DJANGO_DEBUG 影响) | 日志级别,worker/beat 的 --loglevel 直取此值 |
本实例的分工设计(.env 实值):
CACHE_URL=redis://:fw_redis_2o12_Passw0rd@redis:6379/0
CELERY_BROKER_URL=redis://:fw_redis_2o12_Passw0rd@redis:6379/1
同一 Redis 用 0/1 两个逻辑库隔离缓存与队列:缓存数据丢失可重建,队列消息丢了就是任务丢失,物理隔离字段但不隔离实例,备份与连接管理都只需要面对一个 Redis。
CELERYD_CONCURRENCY=2 与 LOGLEVEL=info 的生效证据:worker 启动后任务日志里的执行线程名为 ForkPoolWorker-1/ForkPoolWorker-2,beat 启动行为 exec celery '--app=funkwhale_api.taskapp' beat '--loglevel=info'(均为 docker logs 实测)。
六、存储目录组:MEDIA、STATIC 与原位导入双路径
| 变量 | 默认值 | 说明 |
|---|---|---|
MEDIA_ROOT | 发行版内 media/ | 上传文件、封面、头像落点(必须持久化) |
MEDIA_URL | FUNKWHALE_URL + "/media/" | 媒体访问 URL 前缀 |
STATIC_ROOT | 发行版内 staticfiles/ | collectstatic 产物目录(管理后台样式等) |
MUSIC_DIRECTORY_PATH | 无 | 服务进程视角的原位音乐挂载点,Docker 推荐固定 /music,末尾不带斜杠 |
MUSIC_DIRECTORY_SERVE_PATH | 同 MUSIC_DIRECTORY_PATH | 宿主机视角的真实目录 |
MUSIC_USE_DENORMALIZATION | true | 音乐数据反规范化缓存开关 |
原位导入双路径是 Docker 部署最容易迷糊的一组:
MUSIC_DIRECTORY_SERVE_PATH=/opt/.../data/music:文件在宿主机上的真实位置,供编排文件挂载用;MUSIC_DIRECTORY_PATH=/music:挂载后文件在 api/worker/front 服务内的路径, Funkwhale 数据库里登记的source就是它(数据库实测:file:///music/01 - Tone 440Hz.mp3)。
导入命令会先校验路径约定(部署篇 5.3 节实测首行 Checking imported paths against settings.MUSIC_DIRECTORY_PATH),播出的最后一跳由 3.3 节的 /_protected/music/ internal 路径完成。
STATIC_ROOT 的教训复述一遍:迁移完成后必须执行 collectstatic(实测 174 个文件收集后管理后台样式才正常),否则轻则无样式、重则被 SPA 兜底页的假 200 糊弄(部署篇 3.2 节有完整排障链)。
七、安全与管理入口组
| 变量 | 默认值 | 说明 |
|---|---|---|
DJANGO_SECRET_KEY | 无,必填 | 会话/令牌签名密钥,生产必须换成高强度随机值 |
DJANGO_SETTINGS_MODULE | config.settings.production | 设置模块开关(local 为开发态) |
DJANGO_ADMIN_URL | ^api/admin/ | Django 管理后台路径(正则,改掉可减少扫描面) |
SESSION_COOKIE_AGE | 3600 * 25 * 60(540 万秒,约 62.5 天) | 会话 cookie 有效期 |
THROTTLING_ENABLED | true | 请求节流总开关 |
THROTTLING_RATES | {} | 各作用域的自定义限速字典 |
ACCOUNT_LOGIN_METHODS | {"email", "username"} | 登录凭证类型 |
ACCOUNT_EMAIL_VERIFICATION_ENFORCE | false | 强制邮箱验证后才可用(需先配好邮件服务) |
ACCOUNT_USERNAME_BLACKLIST | [] | 保留用户名黑名单 |
DJANGO_ADMIN_URL=^api/admin/ 的生效实测:http://192.168.146.128:8138/api/admin/login/ 返回 200(登录页)。注意这是 Django 的管理后台(数据库级运维入口),与 Web 界面里管理员侧边栏的「管理」不是同一套——后者是 Funkwhale 自带的应用管理页。
八、邮件组:consolemail 实测
| 变量 | 默认值 | 说明 |
|---|---|---|
EMAIL_CONFIG | consolemail:// | 邮件连接串。可选:consolemail://(打印到日志)、smtp://、smtp+ssl://、smtp+tls://,均可带 user:pass@host:port |
DEFAULT_FROM_EMAIL | Funkwhale <noreply@{主机名}> | 系统邮件发件人 |
SERVER_EMAIL | 同 DEFAULT_FROM_EMAIL | 错误报告类邮件发件人 |
EMAIL_SUBJECT_PREFIX | [Funkwhale] | 邮件主题前缀 |
测试实例用 consolemail://(不发真邮件,内容打印进 api 日志)。实测触发一次密码重置:
curl -s -X POST http://127.0.0.1:8140/api/v2/auth/password/reset/ \
-H 'Content-Type: application/json' -d '{"email":"admin@funkwhale.lab"}'
API 回显:
{"detail":"Password reset e-mail has been sent."}
api 日志中捕获到的邮件全文:
Subject: Password reset on funkwhale_api
From: noreply@funkwhale.lab
To: admin@funkwhale.lab
Date: Wed, 28 Oct 2026 16:06:26 +0000
Message-ID: <179320358695.8.14762155340990056427@e4bd20bc3a28>
You're receiving this e-mail because you requested a password reset for your =
user account at funkwhale.lab.
Please go to the following page and choose a new password:
http://funkwhale.lab/auth/password/reset/confirm?uid=3DMQ&token=3Ddh622q-a92c=
ecf66204f8896fd3aeb9f3d20441
Your username, in case you've forgotten: admin
Thanks for using our site!
The funkwhale.lab team
一封邮件同时验证了三个变量:EMAIL_CONFIG(内容落日志)、DEFAULT_FROM_EMAIL(From 行)、FUNKWHALE_HOSTNAME(正文链接)。生产换 smtp+tls://user:pass@smtp.example.com:587 即可,格式不变。
九、可选集成组速览
以下变量默认全关,按需启用(2.0.12 源码均含对应读取行):
- Typesense 全文搜索:
TYPESENSE_API_KEY(设了即启用)、TYPESENSE_HOST(默认typesense)、TYPESENSE_PORT(默认8108)、TYPESENSE_PROTOCOL(默认http)、TYPESENSE_NUM_TYPO(默认5)。未启用时启动日志固定打印Typesense is not activated. You can enable it by setting the TYPESENSE_API_KEY env variable.(本实例实测可见); - S3 兼容对象存储:
AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_STORAGE_BUCKET_NAME、AWS_S3_ENDPOINT_URL、AWS_S3_REGION_NAME、AWS_S3_CUSTOM_DOMAIN、AWS_S3_URL_PROTOCOL(默认https:)、AWS_LOCATION(默认空)、AWS_DEFAULT_ACL——配齐即把媒体与附件从本地磁盘切到对象存储; - LDAP 认证:
LDAP_SERVER_URI、LDAP_BIND_DN、LDAP_BIND_PASSWORD、LDAP_SEARCH_FILTER、LDAP_ROOT_DN、LDAP_GROUP_DN等,启用后登录走目录服务; - 元数据源:
MUSICBRAINZ_HOSTNAME(默认musicbrainz.org,可指向自建同步站规避限流); - Subsonic 兼容层:
SUBSONIC_DEFAULT_TRANSCODING_FORMAT(默认mp3); - 监控:
FUNKWHALE_SENTRY_DSN(设了即上报错误到 Sentry)、FUNKWHALE_SENTRY_SR(默认0.25,追踪采样率); - 插件:
FUNKWHALE_PLUGINS(逗号分隔启用列表)、FUNKWHALE_PLUGINS_PATH。2.0.12 默认启用 scrobbler、listenbrainz、maloja 三个听歌记录插件(启动日志实测); - 附件治理:
ATTACHMENTS_UNATTACHED_PRUNE_DELAY(默认 86400 秒,未挂接的附件一天后由周期任务清理)。
十、改配置的标准动作与禁忌
- 环境变量改动:改
.env→docker compose up -d(受影响服务会按需重建)→ 验证。凡是被 front 烘焙进配置的变量(NGINX_MAX_BODY_SIZE、FUNKWHALE_HOSTNAME)必须重建而非重启; - 偏好改动:管理界面或 API 即时生效,无需重启(2.3 节实测);
- 禁忌一:
FUNKWHALE_HOSTNAME上线后不改。联邦 actor ID、对象 fid 全部以它为根,改了等于给实例换身份,既有联邦关系全部作废; - 禁忌二:
DJANGO_SECRET_KEY上线后不换。换了之后全部会话、OAuth 令牌、密码重置链接作废(必须换时安排在维护窗口); - 禁忌三:数据库五件套与
DATABASE_URL不混写,DATABASE_URL优先级更高,排查连接问题先查残留。
信息来源:变量名、默认值与代码行为均取自 funkwhale/api 2.0.12 发行版 config/settings/common.py、config/settings/production.py 与 front 2.0.12 内置 Web 服务器配置的实测导出(2026-10-28);改值实验(NGINX_MAX_BODY_SIZE、instance__name、密码重置邮件)为本站实例实机操作记录。