我的Beancount Fava部署方案

之前使用的是 docker (fava) + webhook (git 仓库) + openresty (auth0) 方案, 后面迁移到 kubernetes 上了,重新整理下方案

整体方案

  • oauth2-proxy 作为应用入口(通过 oidc 配置 auth0)
  • fava 运行 beancount 的 fava webui
  • git-sync 定时同步 beancount 的 git 仓库,跟 fava 使用同一个 volume

beancount 仓库

使用 volume 共享

1
2
3
volumes:
- name: ledger-volume
emptyDir: {}

webhook -> git-sync

之前为了解决国内访问 GitHub 速度问题,把 beancount 的仓库也同步提交国内仓库了,但是中途切换过多个供应商,配置 webhook 不方便,换成定义拉去的方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- name: git-sync
image: ...
env:
- name: TZ
value: Asia/Shanghai
- name: GITSYNC_REPO
value: https://xxxx/accounting.git
- name: GITSYNC_REF
value: master
- name: GITSYNC_ROOT
value: /tmp/git
- name: GITSYNC_USERNAME
value: ...
- name: GITSYNC_PASSWORD
value: ...
- name: GITSYNC_PERIOD
value: 60s
securityContext:
runAsUser: 1000 // 注意跟 fava 容器使用相同用户
volumeMounts:
- name: ledger-volume
mountPath: /tmp/git

fava 镜像

可以使用社区的镜像或者自行构建,这是我使用的 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name: fava
image: docker.cnb.cool/dongfg/packages/fava:1.30.12-260507
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
env:
- name: TZ
value: Asia/Shanghai
- name: FAVA_PORT
value: "5000"
- name: BEANCOUNT_FILE
value: /data/accounting.git/main.bean // accounting.git 是默认的仓库名, 可以通过 GITSYNC_LINK 指定
volumeMounts:
- name: ledger-volume
mountPath: /data

oauth2-proxy

解决 fava 没有 auth 模块的问题,我是用的是 auth0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- name: oauth2-proxy
image: docker.cnb.cool/dongfg/packages/oauth2-proxy:v7.15.2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 3000
protocol: TCP
args:
- "--http-address=0.0.0.0:3000"
- "--upstream=http://localhost:5000"
- "--provider=oidc"
- "--oidc-issuer-url=https://dongfg.auth0.com/"
- "--client-id=xxxxxx"
- "--client-secret=xxxx"
- "--cookie-secret=xxxx"
- "--email-domain=*"
- "--redirect-url=https://实际访问域名/oauth2/callback"
- "--whitelist-domain=.auth0.com" // 退出登录后重定向的域名,不配置时重定向不生效

退出登录

为了方便在 fava 界面上退出,可以利用 fava 的 extension 功能添加一个退出登录的链接,下面是一个实现参考

1
2
3
4
├─exts
│ └─logout
│ └─__init__.py
│ └─Logout.js
1
2
3
4
5
# __init__.py
from fava.ext import FavaExtensionBase

class Logout(FavaExtensionBase):
has_js_module = True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Logout.js
export default {
onPageLoad() {
console.log("!!! Logout Extension Loaded !!!");
if (document.getElementById('fava-logout-btn')) return;

const aside = document.querySelector('aside .navigation:last-child');
if (aside) {
const className = aside.querySelector('li:last-child').className;
const logoutBtn = document.createElement('li');
logoutBtn.className = className;
logoutBtn.id = 'fava-logout-btn';
logoutBtn.innerHTML = `<a href="/logout" class="${className}">退出登录</a>`;
aside.appendChild(logoutBtn);
}
},
};

启用扩展

1
2026-05-07 custom "fava-extension" "exts.logout"

最终效果