èªå® Lifebook ã§åããããŒã çš Wiki ã Cloudflare Tunnel + Access ã§å ¬éãã話
å®¶ã«ç ã£ãŠãã LifebookïŒUbuntu Server åæžã¿ïŒã®äžã«ãDocker ã§ Wiki.js ãç«ãŠãCloudflare Tunnel + Cloudflare Access çµç±ã§ããŒã ã«å ¬éããä»çµã¿ãçµã¿ãŸãããããŒãéæŸã»åºå® IPã»æåãµãŒãã¹ã®ãããã䜿ãããæã³ã¹ã $0 + ãã¡ã€ã³å¹Ž $10 ã ãã§å®çµããŠããŸãã
ã¿ã¹ã¯ã¯ GitHub Issues ã§ç®¡çããªãã 1 é±éã»ã©ãããŠæ§ç¯ããŸããïŒå®äœæ¥æéã¯ããŒã¿ã«ã§ 6ã8 æéçšåºŠïŒããã®èšäºã§ã¯ãã®å šå·¥çšããèšèšå€æã®èæ¯èŸŒã¿ã§æžãæ®ããŸãã
æ³å®èªè : ãèªå® ãµãŒããŒã«äœãç«ãŠãŠå€éšå ¬éãããããCloudflare Tunnel ãš Access ãå®éçšã§çµã¿åãããäºäŸã欲ããããWiki.js ã Docker Compose ã§éçšãããããšãã£ãæ¹ãæ³å®ããŠããŸãã
ãŽãŒã«
- ããŒã å ïŒéçºè ãšééçºè ãæ··åšïŒã§ ããã¥ã¡ã³ããå ±æã§ããå Žæ ãæ¬²ãã
- ç¡æéçš ãããïŒç¶ç¶ã³ã¹ãã¯ãã¡ã€ã³ã®å¹Ž $10 ãŸã§ïŒ
- èªèšŒããã誰ã§ãé²èЧå¯èœã§ã¯ãªãããš
- ããŒãžåäœã§æš©é ãåããããš
- ã¡ã³ããŒã«äœèšãªã¯ã©ã€ã¢ã³ãïŒTailscale çïŒãå ¥ããããããªã
- ã¬ãã¬ãã®ã»ãã¥ãªãã£ã¯äžèŠã ãããããªãã«å ãããš
çµè«å åã: æ¡çšæ§æ
flowchart TB
Browser["ã¡ã³ããŒã®ãã©ãŠã¶"]
subgraph CFEdge["Cloudflare Edge"]
direction TB
Access["Cloudflare Access<br/>Email OTP / Session 1 week"]
Tunnel["Cloudflare Tunnel"]
Access --> Tunnel
end
subgraph LF["Lifebook (Ubuntu Server, Wi-Fi)"]
subgraph Compose["docker compose"]
direction TB
cloudflared["cloudflared"]
wiki["wiki<br/>(Wiki.js v2)"]
db[("db<br/>(PostgreSQL 16)")]
backup["backup<br/>cron ã§ pg_dump â rclone sync"]
cloudflared --> wiki
wiki --> db
backup --> db
end
end
R2[("Cloudflare R2<br/>wiki-backup bucket<br/>off-site / AES-256 at-rest")]
Browser -- "HTTPS (wiki.example.com)" --> Access
Tunnel -- "outbound æç¶æ¥ç¶" --> cloudflared
backup -- "rclone sync" --> R2
ãã€ã³ããæŽçãããšä»¥äžã«ãªããŸãã
- Cloudflare Tunnel ã§å€åãæ¥ç¶ã®ã¿ã§å ¬éããŸããããŒãéæŸã»åºå® IP äžèŠã»HTTPS èªåã»å®¶åº Wi-Fi ã§åããŸã
- Cloudflare AccessïŒZero Trust FreeïŒã§ wiki ã«å°éããåã« Email OTP ã®èªèšŒã²ãŒããæã¿ãŸãã50 ãŠãŒã¶ãŒãŸã§ç¡æã»ç¡æéã§ã
- Wiki.js v2 ã¯ããŒãžåäœ ACL ãæšæºæ©èœãMarkdown ãã€ãã£ããUI ãã¢ãã³ã§ã
- Cloudflare R2 ã§ off-site ããã¯ã¢ãããFree tier 10GBïŒwiki dump èŠæš¡ã§ã¯æ°å幎äœè£ã®å®¹éïŒ
- Lifebook 管ççšã® SSH / Cockpit 㯠Tailscale çµç± ã®ãŸãŸæ®ããwiki ã ãã Cloudflare çµç±ã§å ¬éããŸã
1. åæ©ãšèŠä»¶
ãªãããŒã çš Wiki ãªã®ã
å人ãã¡ãšããã¥ã¡ã³ãããã¬ããžãå ±æããåããåºãŠããããªé°å²æ°ãæããŠããŠããã®åšã欲ããã£ãã®ãåºçºç¹ã§ããå°èŠæš¡ïŒ10 åæªæºãæ³å®ïŒã§ãéçºè ãšééçºè ãæ··ããã°ã«ãŒãã«ãªããŸããNotion / Confluence / Google Workspace ã®ãã㪠SaaS ãå¥çŽããã»ã©ã§ã¯ãªããGitHub Wiki ã§ã¯ã¡ã³ããŒå šå¡ã« GitHub ã¢ã«ãŠã³ãã匷å¶ããããããŸããããã©ã€ããŒããªè©±é¡ãæ±ããããç¡æã®å ¬é Wiki ãã¹ãã£ã³ã°ã¯éžæè¢ã«å ¥ããŸããã§ããã
ãªãèªå® ãµãŒããŒãªã®ã
é«å°æä»£ã«äœ¿ã£ãŠãã Windows ããŒã PCïŒLifebookïŒã«ãããæ¥ãäœãéã³ãããªãããšæãç«ã£ãŠ Ubuntu Server ãå ¥ãããã®æµãã§ Docker / Tailscale ãªã©è«žã ã»ããã¢ããããŠãããã®ãæå ã«ãããŸãããæåãã homelab ãç«ãŠãæç¢ºãªç®çããã£ãããã§ã¯ãªããå¯ãããŠããã®ããã£ãããªãã®ã§è§Šã£ãŠããããšãããããã®åæ©ã§ãããããªãšãã«å人ãã¡ãšã®ãã¬ããžå ±æã®å Žã欲ãããªãããã¡ããã©ãã® Lifebook ã䜿ãã°ãããããããšæãè³ã£ãã®ããã£ããã§ããã
ææ°çŸåã®ã¯ã©ãŠã VPS ãšæ¯ã¹ãŠãã24 æé皌åãããŠããŠé»æ°ä»£ä»¥å€ã®ã©ã³ãã³ã°ã³ã¹ããããããªãèªå® æ©ã®åªäœæ§ã¯å€§ããã§ãã
ããŒã PC ãåžžæèµ·åã®ãµãŒããŒã«ããããã§ã®æ³šæç¹ã¯ãç±å¯ŸçïŒèãéããç¶æ ã§ãããã¬ã¹éçšããã®ã§åºé¢éæ°ã確ä¿ããïŒãšãWi-Fi çµç±ã§æ¥ç¶ããŠããå Žåã®æé¢ãã³ããªã³ã°ãããã§ãããããä»å㯠cloudflared ãèªå忥ç¶ããŠãããåæã§ãWi-Fi æ§æã®ãŸãŸé²ããŸããã
èŠä»¶ã®æŽç
| # | èŠä»¶ | åè |
|---|---|---|
| 1 | æã³ã¹ã ⊠0 å | ãã¡ã€ã³ä»£ïŒå¹Ž $10ïŒã¯åºå®è²»ãšããŠèš±å®¹ããŸã |
| 2 | èªèšŒãã | ã¡ã³ããŒããšã«èå¥ã§ããããšãæ¡ä»¶ã§ã |
| 3 | ããŒãžåäœã®æš©é | ãå šå¡ã«èŠããããŒãžãã圹è·éå®ããå人ãã®åãåããå¿ èŠã§ã |
| 4 | ã¯ã©ã€ã¢ã³ã远å ãªã | ã¡ã³ããŒåŽã«ã¢ã㪠/ VPN ãå ¥ããããªã |
| 5 | ããŒãéæŸãªã | å®¶åºçšã«ãŒã¿ã®èšå®å€æŽãªããISP ã®èŠçŽãæ°ã«ããªã |
| 6 | èªåãæ®æ®µç®¡çãããã | Tailscale çµç±ã® SSH + VS Code Remote SSH ã§éçš |
æ€èšããå ¬éæ¹åŒ
| æ¡ | Pros | Cons | æ¡åŠ |
|---|---|---|---|
| Tailscale æåŸ ã®ã¿ | æãå ç¢ãèšå®ãç°¡å | Personal ãã©ã³ç¡ææ 3 ãŠãŒã¶ãŒãŸã§ãã¡ã³ããŒå šå¡ã« Tailscale å°å ¥ãå¿ é | à |
| Tailscale Funnel | tailnet å€ãã HTTPS å ¬éå¯èœãTLS çµç«¯ã¯ Tailscale ä»»ã | èªèšŒã¯ wiki åŽã®ã¿ãURL ããããªãã¯ã«é²åºãã | à |
| Cloudflare Tunnel + Access | 50 ãŠãŒã¶ãŒãŸã§ç¡æã»ç¡æéãwiki åæ®µã« SSO/OTP ãæãããç¬èªãã¡ã€ã³éçšãå¯èœ | ãã¡ã€ã³ä»£ïŒå¹Ž $10ïŒãå¿ èŠ | â |
| ngrok / Localtunnel | éã«ãããªãæé | èªèšŒã匱ããURL ãã©ã³ãã ã§å€ãããç¡ææ ã®å¶éãå³ãã | à |
Cloudflare Tunnel + Access ã®çµã¿åããã¯ãèŠä»¶ 1ã5 ã®ãã¹ãŠãåç¬ã§æºãããæ§æã§ããã
æ€èšãã Wiki ãšã³ãžã³
| åè£ | æ¡åŠ | å¯žè© |
|---|---|---|
| Wiki.js v2 | â æ¡çš | ããŒãžåäœ ACLãMarkdown ãã€ãã£ããUI ã¢ãã³ãNode.js + PostgreSQL |
| BookStack | Ⳡ第äºåè£ | Shelf > Book > Chapter > Page æ§é ãUX å¹³æãæš©éç²åºŠã¯ããç²ã |
| DokuWiki | à | 軜éã»DB äžèŠã ã UI ãå€ããMarkdown ãã€ãã£ãã§ã¯ãªã |
| Outline | à | ãªãœãŒã¹èŠä»¶ãé«ããOAuth ãããã€ãå¿ é |
| Confluence Cloud | Ã | ææ |
第äºåè£ã® BookStack ãè§Šãäºå®ã§ããããWiki.js ãæåã«ç«ãŠãŠã¿ãŠã»ããã¢ããããååããŒã å°éãŸã§è©°ãŸããªãé²ãã ã®ã§ããã®ãŸãŸæ¡çšã«åãæ¿ããŸãããBookStack ã詊ããªãã£ãã®ã¯å°ãå¿æ®ãã§ãããWiki.js v2 ã®éçšã«äžæºã¯åºãŠããŸããïŒv3 ã¯é·æããŒã¿ã®ããäžæ¡çšãšããŸããïŒã
2. 圹å²åæ ãšéçšãããŒ
MacBook ãéçºæ©ãLifebook ãéçšæ© ãšããŠæç¢ºã«åããæ§æã«ããŸãããLifebook ã¯ãã£ã¹ãã¬ã€ã»ããŒããŒããç¹ãããããã¬ã¹ã§åãããæäœã¯ãã¹ãŠ MacBook ãã Tailscale çµç±ã® SSH ã§è¡ããŸãã
| æ©æ | æ åœ |
|---|---|
| MacBookïŒéçºæ©ïŒ | docker-compose.ymlã»cloudflared èšå®ã»ããã¯ã¢ããã¹ã¯ãªããã®ç·šéãGit 管ç |
| LifebookïŒéçšæ©ïŒ | ã³ã³ããå®è¡ãããŒã¿ä¿ç®¡ïŒNVMe SSDïŒãããã¯ã¢ããå®è¡ |
æŽæ°ãããŒã¯ä»¥äžã®ãšããã§ãïŒä»¥éã®ã³ãã³ãäŸã§ã¯ãMacBook ã® ~/.ssh/config ã« Host lifebook ã®ãšã€ãªã¢ã¹ãæžããŠããåæã§ ssh lifebook ãšè¡šèšããŸãïŒã
MacBook ã§ç·šé
â git push
GitHub (private repo: <you>/wiki)
â MacBook ãã Tailscale çµç±ã§ SSH:
â ssh lifebook
â Lifebook äžã§:
â git pull
â docker compose --profile tunnel --profile backup up -d
ã³ãŒãã®ç·šéèªäœã¯ MacBook ã®ããŒã«ã«ã§å®çµãããŠãªããžããªã« push ããæµãã§ãããLifebook äžã® .env ãçŽæ¥ãããããå Žåãã峿ããã¯ã¢ãããèµ°ããããå Žåãªã©ã®ããµãŒããŒåŽã ãã§å®çµããäœæ¥ãã¯ãVS Code Remote SSH ãããã¯çŽ ã® SSH ã§æ¥ç¶ããŠæžãŸããŸãã
åœããåã§ãããã·ãŒã¯ã¬ããé¡ïŒCloudflare Tunnel ããŒã¯ã³ãDB ãã¹ã¯ãŒããR2 API ããŒïŒã¯ Git ã«å«ãããLifebook äžã® .env ã§ç®¡çããŸãããªããžããªã«ã¯ .env.example ã ããå«ããŠããŸãã
3. æ§ç¯ã®æµã
GitHub Issues ãç«ãŠãŠãäŸåé ã«æœ°ããŠãããŸãããããããã¯åãã§ãŒãºã§äœããã£ãããäœã«ããã£ãããé ã«æžããŸãã
ãã§ãŒãº 0: ãã¡ã€ã³ãš Cloudflare ã¢ã«ãŠã³ã
- Cloudflare Registrar ã§
example.comãååŸããŸããïŒå¹Ž $10 ã¡ãã£ãšïŒ - ã¬ãžã¹ãã©ïŒDNS ãããã€ããã©ã¡ãã Cloudflare ãªã®ã§ NS å§è²ã»äŒæ¬ç¢ºèªã¯äžèŠã§ãã
- wiki çšã«ã¯
wiki.example.comããµããã¡ã€ã³ãšããŠäœ¿ããŸã - ã«ãŒã
example.comã¯å°æ¥ã®å人ãµã€ãçšã«æž©åããŸã
ããã§åææ¡ä»¶ã¯ã¯ãªã¢ãå®ã³ã¹ãã®çºçã¯ããã ãã§ãã
ãã§ãŒãº 1: Wiki ãšã³ãžã³ãããŒã«ã«ã§åãã
æåã«äœã£ã docker-compose.yml 㯠wiki + DB + cloudflared ã® 3 ãµãŒãã¹æ§æã§ããããã ããããŒã«ã«çé確èªã段éã§ã¯ cloudflared ãèµ·åããããããŸããïŒTunnel ããŒã¯ã³ããŸã ç¡ãããïŒã
æåã¯çŽ æŽã«ããæžããŠããŸããã
cloudflared:
image: cloudflare/cloudflared:latest
environment:
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:?CLOUDFLARE_TUNNEL_TOKEN is required}
:? ã¬ãŒãã眮ããŠãæªèšå®ãªãèµ·å倱æãã«ããã€ããã ã£ãã®ã§ãããããã第äžã®èœãšã穎ã«ãªããŸããïŒè©³çްã¯åŸè¿°ãåŠã³ãã»ã¯ã·ã§ã³ã§è§ŠããŸãïŒãçµå± Compose profile ã䜿ã£ãŠ cloudflared ãããã©ã«ãèµ·åããå€ãæ§é ã«å€æŽããŠããŸãã
cloudflared:
image: cloudflare/cloudflared:latest
environment:
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:-}
profiles:
- tunnel
ããã«ããïŒ
docker compose up -dâdb+wikiã®ã¿èµ·åïŒããŒã«ã«çéçšïŒdocker compose --profile tunnel up -dâ cloudflared ãå«ããŠå šéšèµ·åïŒæ¬çªïŒ
:? ã :-ïŒempty 蚱容ïŒã«å€ããŠãprofile ã§èµ·åå¶åŸ¡ãããšããäºæ®µæ§ãã«ãªããŸããã
ããŒã«ã«ã§ã®åæã»ããã¢ããæé ã¯ä»¥äžã®ãšããã§ãã
- MacBook ãã Lifebook ã« SSH æ¥ç¶ããLifebook äžã§
git clone - SSH ã»ãã·ã§ã³å
ã§
.envã«POSTGRES_PASSWORDã ããèšå® wikiãµãŒãã¹ã®ports: ["127.0.0.1:3000:3000"]ãäžæçã«æå¹å- SSH ã»ãã·ã§ã³å
ã§
docker compose up -d - å¥ã® MacBook ã¿ãŒããã«ãã
ssh -L 3000:127.0.0.1:3000 lifebookã§ããŒããã©ã¯ãŒã - MacBook ã®ãã©ãŠã¶ã§
http://localhost:3000ãéããŠåæã»ããã¢ãããŠã£ã¶ãŒããé²ãã
SSH ããŒããã©ã¯ãŒãã§
localhostã䜿ããšãIPv6 ã®::1åŽã«è§£æ±ºãã㊠Docker ã®127.0.0.1ãã€ã³ãã«ç¹ãããªãããšããããŸãã127.0.0.1ãæç€ºããã®ã確å®ã§ãã
ã»ããã¢ããå®äºåŸãports: ãå
ã«æ»ããŸãã
ãã§ãŒãº 2: Cloudflare Tunnel ã§å€éšå ¬é
Cloudflare Zero Trust ããã·ã¥ããŒãïŒone.dash.cloudflare.comïŒã§ä»¥äžãè¡ããŸãã
- Networks â Tunnels â Add a tunnel â Cloudflared
- Tunnel å:
wiki-tunnel - Save â ããŒã¯ã³ååŸïŒ
eyJ...圢åŒã® JWTïŒ - Public hostname:
wiki.example.comâhttp://wiki:3000- Service URL ã«
localhost:3000ã§ã¯ãªãwiki:3000ã䜿ãããšãéèŠã§ããcloudflared ã³ã³ããããèŠãlocalhost㯠cloudflared èªèº«ãæããŠããŸããŸããDocker Compose ã®ãµãŒãã¹åã§å éš DNS 解決ãããŸããã
- Service URL ã«
- Save
Lifebook åŽã§ã¯ïŒ
# .env ã« CLOUDFLARE_TUNNEL_TOKEN ã远å
nano .env
# tunnel profile ãå«ããŠèµ·å
docker compose --profile tunnel up -d
docker compose logs cloudflared
# â "Registered tunnel connection ..." ãè€æ°è¡ïŒCF Edge ã®è€æ° PoP ã«æ¥ç¶ïŒ
Cloudflare ããã·ã¥ããŒãåŽã§ Tunnel ã HEALTHY ã«å€ãããhttps://wiki.example.com ã§ã¢ã¯ã»ã¹ã§ããããã«ãªããŸããããã®æç¹ã§ã¯ãŸã èªèšŒãªãã§äžçäžããã¢ã¯ã»ã¹ã§ããç¶æ
ãªã®ã§ãWiki.js ã®ãã°ã€ã³ç»é¢ãåæ®µã«ç«ã£ãŠããã ããé²åŸ¡ã«ãªã£ãŠããŸããæ¬¡ã®ãã§ãŒãºã§èªèšŒã²ãŒããä¹ããŸãã
ãã§ãŒãº 3: Cloudflare Access ã§èªèšŒã²ãŒã
Tunnel å ¬éãšåãã¿ã€ãã³ã°ã§ Access ãæå¹åãããã£ãã®ã§ãèšèšé ãšããŠã¯ Access ãå ã«èšå®ããŠãã Tunnel ã® public hostname ã远å ããã®ãå®å šã§ãã
IdP ã®éžå®
| åè£ | æ¡åŠ | å¯žè© |
|---|---|---|
| Google å人 / Workspace | à | å šå¡ã Gmail ãæã£ãŠãã確蚌ããªããWorkspace å¥çŽããªã |
| GitHub | à | ééçºè ã®ã¡ã³ããŒãã¢ã«ãŠã³ããæããªã |
| Email OTPïŒOne-time PINïŒ | â | Cloudflare 忢±ã§ IdP 远å èšå®äžèŠãä»»æã®ã¡ãŒã«ã§äœ¿ãããææ°åããŒã¹ãªãæéã蚱容ç¯å² |
ã¡ã³ããŒããéçºè + ééçºè ã®æ··åšãã ã£ãæç¹ã§ãç¹å®ã® OAuth ãããã€ãã«çžããªãæ¡ä»¶ã«ãªããŸãããæçµçã« Email OTP ãæ¡çšããŠããŸããCloudflare Access ã¯è€æ° IdP ã䜵çšã§ããã®ã§ãå¿ èŠã«ãªã£ããåŸãã Google ãè¶³ãããšãã§ããŸãã
Application ã®èšå®
Zero Trust â Access controls â Applications â Add an application â Self-hostedïŒæ°ãã UI ã§ã¯ Public DNS ã¿ãïŒã§ä»¥äžã®ããã«èšå®ããŸããã
| é ç® | å€ |
|---|---|
| Application name | Team Wiki |
| Application domain | wiki.example.com |
| Session Duration | 1 week |
| Identity providers | One-time PIN ã®ã¿ |
| Apply instant authentication | ONïŒIdP éžæç»é¢ãã¹ãããïŒ |
ãã㊠Allow policy ãšã㊠Approved members ãäœæããSelector ã«èš±å¯ããã¡ãŒã«ã¢ãã¬ã¹ãåæããŠããŸãã
èªèšŒç»é¢ã®è¡šç€ºãã¡ã€ã³åé¡
æåã«ãã©ãŠã¶ã§ https://wiki.example.com ãéããŠç¢ºèªãããšããCloudflare Access ã®ãã°ã€ã³ç»é¢ã«è¡šç€ºããããã¡ã€ã³ãæå³ããªããã®ã«ãªã£ãŠããŸããã
.cloudflareaccess.comïŒauto-generatedïŒ
URL ããŒåŽã¯ <your-team>.cloudflareaccess.com/cdn-cgi/access/login/... ã§æ£ããã®ã«ãã«ãŒãäžã®ã©ãã«ã ãã auto-generated ã®ãŸãŸã ã£ãã®ã§ãããã㯠Settings â Custom pages â Access login page â âYour organizationâs nameâ ã§å¥ç®¡çã«ãªã£ãŠããŠãTeam name èšå®ãšã¯åæããªã仿§ã§ãããæåã§ <your-team>.cloudflareaccess.com ã«æžãæããŠè§£æ±ºããŸããã
ç¥ããªããšæåŸ æã«ã¡ã³ããŒããäœã ãã®è¬ãã¡ã€ã³ã¯ããšãªãã®ã§ãèŠæ³šæãã€ã³ãã§ãã
ãã§ãŒãº 4: ããŒãžæš©éã®èšèš
Wiki.js 㯠Groups ãš Page Rules ã§æè»ã«æš©éãåããŸãããšã¯ããã¡ã³ããŒã 1 人ïŒèªåã ãïŒã®æ®µéã§è€éãªéå±€èšèšãããŠãæºäžè«ã«ãªããŸããYAGNI åå ã§æå°æ§æã«ããŸããã
| ã°ã«ãŒã | åœ¹å² |
|---|---|
AdministratorsïŒããã©ã«ãïŒ | ãã«æš©é |
MembersïŒæ°èŠäœæïŒ | èªã¿æžãã»æ°èŠäœæã»å±¥æŽã»ç»åã¢ããããŒãã¯å¯ãåé€ãš admin ç³»ã¯äžå¯ |
GuestsïŒããã©ã«ãïŒ | äœãèªããªãïŒCloudflare Access ã§å段ã²ãŒãããŠããã®ã§å®è³ªå°éããªãïŒ |
Members ã°ã«ãŒãã® permissions ã¯ä»¥äžã®ãšããã§ãã
- â
read:pages/read:source/read:history - â
write:pages/manage:pages - â
read:assets/write:assets - â
read:comments/write:comments - Ã
delete:pagesâ åé€ã¯ admin ã«éçŽããŸã - Ã
manage:assetsâ ã¢ã»ããåé€ã admin ã«éçŽ - Ã
write:styles/write:scriptsâ CSS/JS æ³šå ¥ã¯ XSS ãªã¹ã¯ãããããé€å€ - à Users / Administration ã»ã¯ã·ã§ã³å šéš
Page Rules ã§ã¯ path Starts with /ïŒå
šãã¹å¯Ÿè±¡ïŒã«äžèšæš©éã ALLOW ã§èšå®ããŸããããç¹å®ãã¹ã ãé²èЧå¶éãã®ãããªèŠæãåºãŠããæç¹ã§å¥ã°ã«ãŒãã远å ããæŠç¥ã«ããŠããŸãã
ãã§ãŒãº 5: ããã¯ã¢ãããšçœå®³åŸ©æ§ã®èªåå
èšèšã¡ã¢ã«ã¯ãDB dump ã cron / systemd timer ã§å®æååŸ â å¥ãã£ã¹ã¯ or å€éšã«åæããšãããŸãããhomelab ãšã¯ãããããŒã«ã«åäžã³ããŒã ãã ãšãã£ã¹ã¯æ éã«èããããªãã®ã§ãoff-site åæ ãŸã§ã¹ã³ãŒãã«å«ããŸããã
ããã¯ã¢ããå ã®éžå®
| åè£ | æ¡åŠ | å¯žè© |
|---|---|---|
| Cloudflare R2 | â | æ¢ã« Cloudflare ã¢ã«ãŠã³ãææãFree tier 10GBïŒwiki èŠæš¡ã§æ°å幎äœè£ïŒãS3 äºæ |
| Backblaze B2 | â³ | ç¡ææ 10GBãã¯ã¬ãžããã«ãŒãäžèŠã ã Cloudflare ãšã® 2 ãã³ããŒåã«ãªã |
| å¥ãã·ã³ãž rsync | à | ç©ççã«å¥ãã·ã³ãæããªã |
| åäžãã·ã³ã®å¥ãã£ã¹ã¯ | à | ãã£ã¹ã¯æ éèæ§ãªã |
R2 äžæã§ãããWiki.js v2 ã¯ã¢ããããŒããå«ãå
šã³ã³ãã³ãã DB ã«ä¿åãããããpg_dump 1 æ¬ã§å®å
šããã¯ã¢ãã ã«ãªããŸãïŒfilesystem åŽã®å¥éåæã¯äžèŠïŒããããã¡ã³ããã³ã¹ã®åçŽãã§å€§ããå¹ããŠããŸãã
backup ãµã€ãã«ãŒã®å®è£
services/wiki/backup/ é
äžã« Alpine ããŒã¹ã® custom image ã眮ããŸããã
FROM alpine:3.20
RUN apk add --no-cache postgresql16-client rclone dcron tini bash coreutils
COPY backup.sh /usr/local/bin/backup.sh
COPY restore.sh /usr/local/bin/restore.sh
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /usr/local/bin/backup.sh /usr/local/bin/restore.sh /entrypoint.sh
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]
ENTRYPOINT ãš CMD ãåããçç±ã¯åŸã»ã©è§ŠããŸãã3 ã€ã®ã¹ã¯ãªããã®åœ¹å²ã¯æ¬¡ã®ãšããã§ãã
entrypoint.shâ ç°å¢å€æ°ã/etc/backup.envã« single-quote escape ã§æžãåºããcrontab ãã»ããããBusyBox crond ã§åžžé§ãããbackup.shâpg_dump -Fc | gzipâ ããŒã«ã«/backupsâ ããŒããŒã·ã§ã³ ârclone syncã§ R2 ã«åærestore.shâ æå®ïŒãŸãã¯ææ°ã®ïŒdump ãpg_restoreã§åŸ©å ãã
compose ã« profiles: [backup] ã§ã²ãŒãããŸããã
backup:
build: ./backup
image: wiki-backup:local
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
POSTGRES_HOST: db
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?...}
BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 3 * * *} # UTC 03:00 = JST 12:00
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-14}
R2_BUCKET: ${R2_BUCKET:-}
R2_ENDPOINT: ${R2_ENDPOINT:-}
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
volumes:
- backups:/backups
profiles:
- backup
rclone ã® S3 èšå®
R2 㯠S3 äºæãªã®ã§ rclone ã§ãã®ãŸãŸåããŸããç§å¯éµã«ç¹æ®æåãæ··ãã£ãŠãããããã«ãconnection string ã§ã¯ãªã ç°å¢å€æ°çµç± ã§èšå®ããŠããŸãã
RCLONE_CONFIG_R2_TYPE=s3 \
RCLONE_CONFIG_R2_PROVIDER=Cloudflare \
RCLONE_CONFIG_R2_ENDPOINT="${R2_ENDPOINT}" \
RCLONE_CONFIG_R2_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}" \
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}" \
rclone sync /backups "r2:${R2_BUCKET}/wiki/" --s3-no-check-bucket --quiet
äžçªå€§äºãªã®ã¯åŸ©å 詊éš
ãããã¯ã¢ãããåããŠãããã®ãšãå®éã«ãããã埩æ§ã§ãããã¯å¥ç©ã§ããIssue #10 ã®å®äºæ¡ä»¶ã«ã¯ ãå®å°ã§åŸ©å ã§ããããšã確èªã ãæç€ºããŸãããæé ã¯æ¬¡ã®ãšããã§ãã
- Wiki.js ã« âRestore Test Markerâ ãšãããã¹ãããŒãžãäœæãã
- 峿ããã¯ã¢ãããåã£ãŠ dump ã«ãã®ããŒã«ãŒãå«ãããã
docker compose downdocker volume rm wiki_db-dataïŒæå³çã« DB ããŒã¿ãç Žå£ïŒdbã ããŸã£ãããªç¶æ ã§èµ·årestore.shã§ææ° dump ãæµã蟌ã- wiki ãèµ·åãããã©ãŠã¶ã§
Restore Test MarkerããŒãžã埩å ãããŠããããšã確èª
dump ã¯ããŒã«ã« /backups ãš R2 ã®äž¡æ¹ã«ããããã埩å
å
ã倱ãå¿é
ã¯ãããŸãããå®éã«ãã£ãŠã¿ããšã5 åãããã§å®å
šåŸ©å
ã§ããŸããããã®è©ŠéšãèžãŸãªããšãæ¬çªé害ã®ãšãã«åããŠãããåããªãããšãªãã®ã§ãå¿
ãããå·¥çšã§ãã
4. ããã£ããšããã»åŠã³
4.1 Compose profile ãš :? ã¬ãŒãã®çžäºäœçš
æåãcloudflared ã® TUNNEL_TOKEN ã ${CLOUDFLARE_TUNNEL_TOKEN:?...} ã§ãæªèšå®ãªãèµ·å倱æãã«ããŠããŸãããprofiles: [tunnel] ã§ cloudflared ãããã©ã«ãèµ·åããå€ããã®ã§ããã§ãŒãº 1ïŒããŒã«ã«çéïŒã§ docker compose up -d ããããšãããã¡ã€ã«å€ã® cloudflared ã¯èµ·åããªãã¯ãããšæã蟌ãã§ããã®ã§ãã
ãšããã宿©ã§è©ŠããšïŒ
error while interpolating services.cloudflared.environment.TUNNEL_TOKEN:
required variable CLOUDFLARE_TUNNEL_TOKEN is missing a value
Compose ã¯ å€æ°å±éãå
šãµãŒãã¹ã«å¯ŸããŠè¡ããŸããprofile ã§é€å€ããããµãŒãã¹ã§ã :? ã¬ãŒãã¯è©äŸ¡ãããŠããŸãã®ã§ããcompose ã®è©äŸ¡ã¢ãã«çã«ã¯åœç¶ãªã®ã§ãããã±ã£ãšèŠã§ããããŸããã
解決çã¯ã:? ã :-ïŒempty 蚱容ïŒã«å€ããŠãprofile ã§èµ·åå¶åŸ¡ã«äžæ¬åããããšã§ãããcloudflared 㯠profile ãæå®ããªããšãèµ·åããªãããprofile ããã§ TOKEN ã空ãªããèµ·åããã runtime ã§å€±æããŠãã°ã«åºãããšããæåã«ãªããŸããloud ãã¯æ®ãã€ã€ãparse ã¯ãã¹ãã圢ã«èœã¡çããŸããã
4.2 Docker ENTRYPOINT ãš CMD ã®äœ¿ãåã
backup ãµã€ãã«ãŒã® Dockerfile ãæåããæžããŠããŸããã
ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]
docker compose run --rm backup /usr/local/bin/backup.sh ã§ on-demand ããã¯ã¢ãããèµ°ããããã£ãã®ã§ãããããã ãš /usr/local/bin/backup.sh ã /entrypoint.sh ã®åŒæ°ãšããŠæ±ãããentrypoint ã¯ãããç¡èŠã㊠crond ãèµ·åããŠåŸ
æ©ããŠããŸããŸããããstuck ãããã©åŸ
ã¡ã®ç¶æ
ïŒããšãªããã¿ãŒã³ã§ãã
解決çã¯ãENTRYPOINT ã tini ã ãã«ããŠãããã©ã«ãæåã CMD ã«åé¢ããããšã§ããã
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]
ããã§ docker compose run --rm backup /usr/local/bin/backup.sh 㯠CMD ã眮ãæã㊠tini -- /usr/local/bin/backup.sh ãå®è¡ â å®èµ°ã㊠exit ããããã«ãªããŸãããDocker ã®ãäœæ³çã«ãæ£ãããã¿ãŒã³ã§ãã
4.3 Cloudflare ã® auth domain ã®èŠãç®ãš URL ã®ãºã¬
ãã§ãŒãº 3 ã§æžãããšãããTeam name ã <your-team> ã«èšå®ããŠããAccess ãã°ã€ã³ç»é¢ã®ã«ãŒãã«è¡šç€ºãããã©ãã«ã¯ auto-generated ã® <auto-generated>.cloudflareaccess.com ã®ãŸãŸã§ãããããã¯å¥ç®¡çã ã£ãã®ã§ãã
- Settings â Team name and domain ã«è¡šç€ºããã
<your-team>.cloudflareaccess.comã¯å®éã® auth URL - Reusable components â Custom pages â Access login page â âYour organizationâs nameâ ãç»é¢ã« 衚瀺ãããã©ãã«
äž¡æ¹ãæåã§æããå¿ èŠããããŸããCloudflare ã®ããã¥ã¡ã³ãã«ã¯æžããŠããã®ã§ãããèšå®å€æŽãããŒããã¯æ°ã¥ãã«ããéšåã§ããã
4.4 Wiki.js ãš Cloudflare Access ã®äºæ®µèªèšŒ
Cloudflare Access ã§ OTP ãééããŠããWiki.js ã¯å¥éç¬èªã«ãã°ã€ã³ãèŠæ±ããŠããŸããã€ãŸãã¡ã³ããŒã¯ä»¥äžãèžãããšã«ãªããŸãã
- ã¡ãŒã«ã¢ãã¬ã¹ãå ¥å â OTP ã³ãŒãåä¿¡ â å ¥åïŒCloudflare AccessïŒ
- ã¡ãŒã« + ãã¹ã¯ãŒããå ¥åïŒWiki.jsïŒ
ã®äºæ®µã§ããã·ã³ã°ã«ãµã€ã³ãªã³åããã«ã¯ Cloudflare Access OIDC â Wiki.js OIDC strategy ã®èšå®ãå¿ èŠã«ãªããŸããä»åã¯æåŸ äººæ°ãå°ãªãããã°ã€ã³é »åºŠãäœãæ³å®ãªã®ã§ãäºæ®µãã°ã€ã³ã蚱容ããŠé²ããŸãããSSO åã¯å°æ¥ã® Issue ã«ããŸããã
4.5 Wiki.js v2 ã PostgreSQL ã«å šéšå ¥ãã仿§
ããã¯çœ ãšãããããå¬ããèšèšå€æã§ãããWiki.js v2 ã¯ããŒãžæ¬æã ãã§ãªãã¢ããããŒãç»åãªã©ã DB ã«æ ŒçŽããŸãïŒããã©ã«ã storage target ã DBïŒãããã«ããããã¯ã¢ããæŠç¥ãã·ã³ãã«ã«ãªããŸãã
ããã¯èšèšãçµãåã« Wiki.js docs ãäžèªããŠããã¹ããã€ã³ãã§ãããäºåã«ç¥ããªãã£ãã®ã§ãæåã¯ãDB ãš uploads äž¡æ¹ããã¯ã¢ããããªããããšæã蟌ãã§ããã®ã§ãã
5. ã³ã¹ãã®å®æ
| é ç® | æã³ã¹ã | åè |
|---|---|---|
| Lifebook 黿°ä»£ | â 100ã200 å | 30W çšåºŠã24h 皌å |
| Cloudflare Tunnel | 0 å | Free tier |
| Cloudflare Access | 0 å | Free tierïŒ50 ãŠãŒã¶ãŒãŸã§ïŒ |
| Cloudflare R2 | 0 å | Free tierïŒ10GB / 1M Class A / 10M Class BïŒ |
| Cloudflare Registrar | 0 å | At-costã幎 $10 çšåºŠïŒæå²ãªã $1/æåŒ±ïŒ |
| Tailscale | 0 å | Personal Free tierïŒç®¡ççšã3 ãŠãŒã¶ãŒæ ã§èªåã®ããã€ã¹ãè³ãïŒ |
ç¶ç¶ã³ã¹ãåèšã¯é»æ°ä»£ + 幎 $10 ã®ã¿ãåœåã®ãç¡æéçšããããç®æšã¯éæã§ããŸããã
R2 ã®å®¹éã¯ãwiki dump ãçŸç¶ 32KB ãªã®ã§ 14 æ¥åã§ã 0.5MB æªæºãææ° PUT çšåºŠã§ããFree tier ã® 0.005% ã䜿ã£ãŠããŸãããã³ã³ãã³ãã GB åäœã«ãªããŸã§å®è³ªç¡æåå ãç¶ããŸãã
6. ãããã
詊é転äžïŒçŸåšïŒ
1 é±éã»ã©èªåã®ã¿ã§è§Šãã以äžã芳å¯ããŠããŸãã
- å®å®çšŒåã®èгå¯ïŒcloudflared 忥ç¶ãcron ã«ããæ¥æ¬¡ããã¯ã¢ããïŒ
- ã»ãã·ã§ã³ 1 week ã®å®äœæïŒOTP å ¥åé »åºŠïŒ
- Wiki.js ã®ç·šéäœéšã§æ°ã«ãªãç¹ã Issue ã§èµ·ãããŠãã
ã³ã¢ã¡ã³ããŒæåŸ ãå šå¡å ¬é
Issue åæžã®æé ã«æ²¿ã£ãŠãCloudflare Access policy ãžã®è¿œå + Wiki.js ããŒã«ã«ãŠãŒã¶ãŒäœæã® 2 ã¹ãããã§æåŸ ããŠãããŸãã
远å ã§æ€èšäžã®æ¹å
- SSO å: 2 段ãã°ã€ã³ãæ©æŠã«ãªã£ãããCloudflare Access SaaS applicationïŒOIDCïŒãš Wiki.js OIDC strategy ã§ 1 段ã«çµ±åã§ããŸã
- ããã¯ã¢ãã倱æç£èŠ: çŸç¶ cron 倱ææã«æ°ã¥ããŸãããR2 ã®æçµæŽæ°æå»ãç£èŠããã¢ã©ãŒãïŒUptime Kuma / Healthchecks.io 飿ºïŒãå ¥ããããšããã§ã
- multi-host å: Lifebook ãå£ãããšãçšã«ãå¥ãã·ã³ãžã® fail-overãR2 dump ããæ°åã§å¥ãã¹ãã«åŸ©å ã§ããåæãªã®ã§ãä»ã¯å¿ èŠæ§ã¯äœãã§ã
7. æ¯ãè¿ã£ãŠ
ãã®ãããžã§ã¯ãã®è¯ãã£ãç¹ãæŽçããŠãããŸãã
- èšèšå€æã®ãã³ã«ããã®åŸã«æ¥ã Issue ã§å®éã«å°ããïŒããèããŸãããå åãã®æœè±¡åã¯ãããå¿ èŠã«ãªã£ãŠãããªãã¡ã¯ã¿ããæ¹éã貫ããŸããïŒcloudflared ã® profile åé¢ã¯ãããŒã«ã«çéã§å°ã£ããããçºçãããªãã¡ã¯ã¿ã§ãïŒ
- ãªã¹ãã¢è©Šéšãå®äºæ¡ä»¶ã«æç€ºããŸãããããã¯ã¢ãããåãã ãã§æºè¶³ããã埩å ã§ããããšã宿©ã§ç¢ºèªãããã§ãŒãºãèšè𿮵éã§çµã¿èŸŒãã§ããŸã
- æ¢åã®ãµãŒãã¹ãçµã¿åãããã ãã§æžã¿ãŸãããæ°ããããšãçºæããŠããŸããïŒCloudflare TunnelãWiki.jsãPostgreSQLãrcloneãããããæ¯ããæè¡ïŒãåçŸæ§ãé«ããšæããŸã
- AI ã³ãŒãã£ã³ã°ãšãŒãžã§ã³ãïŒClaude CodeïŒãšçµã¿åãããŠæ§ç¯ããŸãããèšèšå€æã¯èªåãã³ãã³ãã»ã¹ã¯ãªããã®æŽåœ¢ã»ããã¥ã¡ã³ãå㯠AI ã«ä»»ããããšããã¯ãŒã¯ãããŒãå¹ããŸãã
8. åèè³æ
- Cloudflare Tunnel docs
- Cloudflare Access docs
- Cloudflare R2 docs
- Wiki.js docs
- rclone S3 backend (Cloudflare R2)
ä»é²: äž»èŠãªãã¡ã€ã«ã®æçµåœ¢
services/wiki/docker-compose.ymlïŒæç²ïŒ
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: wiki
POSTGRES_USER: wikijs
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wikijs -d wiki"]
interval: 10s
timeout: 5s
retries: 5
wiki:
image: ghcr.io/requarks/wiki:2
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_TYPE: postgres
DB_HOST: db
DB_USER: wikijs
DB_PASS: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
DB_NAME: wiki
expose: ["3000"]
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:-}
depends_on: [wiki]
profiles: [tunnel]
backup:
build: ./backup
image: wiki-backup:local
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
POSTGRES_HOST: db
POSTGRES_DB: wiki
POSTGRES_USER: wikijs
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?...}
BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 3 * * *}
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-14}
R2_BUCKET: ${R2_BUCKET:-}
R2_ENDPOINT: ${R2_ENDPOINT:-}
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
volumes:
- backups:/backups
profiles: [backup]
volumes:
db-data:
backups:
services/wiki/backup/backup.sh
#!/bin/sh
set -eu
TS=$(date -u +%Y%m%dT%H%M%SZ)
DUMP_FILE="/backups/wiki-${TS}.sql.gz"
RETAIN="${BACKUP_RETENTION_DAYS:-14}"
mkdir -p /backups
echo "==> [${TS}] backup started"
PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \
-h "${POSTGRES_HOST:-db}" \
-U "${POSTGRES_USER:-wikijs}" \
-d "${POSTGRES_DB:-wiki}" \
-Fc \
| gzip > "${DUMP_FILE}"
echo "==> dump complete ($(du -h "${DUMP_FILE}" | cut -f1)): ${DUMP_FILE}"
# Rotate
ls -1t /backups/wiki-*.sql.gz 2>/dev/null \
| tail -n +"$((RETAIN+1))" \
| xargs -r rm -f
# Off-site
if [ -n "${R2_BUCKET:-}" ]; then
RCLONE_CONFIG_R2_TYPE=s3 \
RCLONE_CONFIG_R2_PROVIDER=Cloudflare \
RCLONE_CONFIG_R2_ENDPOINT="${R2_ENDPOINT}" \
RCLONE_CONFIG_R2_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}" \
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}" \
rclone sync /backups "r2:${R2_BUCKET}/wiki/" --s3-no-check-bucket --quiet
echo "==> R2 sync complete"
fi
echo "==> [${TS}] backup finished"
services/wiki/backup/restore.sh
#!/bin/sh
set -eu
DUMP="${1:-}"
if [ -z "$DUMP" ]; then
DUMP=$(ls -1t /backups/wiki-*.sql.gz 2>/dev/null | head -1)
fi
gunzip -c "${DUMP}" | PGPASSWORD="${POSTGRES_PASSWORD}" pg_restore \
--clean --if-exists --no-owner --no-privileges \
-h "${POSTGRES_HOST:-db}" \
-U "${POSTGRES_USER:-wikijs}" \
-d "${POSTGRES_DB:-wiki}"
以äžãé·ãèšäºã«ãªã£ãŠããŸããŸãããã誰ãã䌌ããããªèªå® Wiki æ§ç¯ããããšãã®åèã«ãªãã°å¹žãã§ãã