🔨

Caddy to WebDAVで個人サイトをはじめよう

2023/07/02に公開

Intro

インターネット = Twitterの皆さんはインターネットが壊れまくっている昨今いかがお過ごしでしょうか。
インターネットを自分で管理したい人が増えつつあるので、Caddyを使って誰でも簡単に個人サイトをはじめる方法を紹介したいと思います。

https://caddyserver.com/

CaddyはGo言語で作られたオープンソースのWebサーバーです。
ApacheやNginxと同じように使えますが何と言ってもバイナリ1つあるだけでどんな環境でも動く素晴らしい特徴を持っています。
そしてApacheやNginxに負けず劣らずのモジュールプラグインがたくさんあります。

https://github.com/mholt/caddy-webdav

今回はCaddyの作者でもあるmholt-sanが公開しているWebDAV for Caddyを紹介します。
CaddyのWebDAV用モジュールです。
皆さんWebDAVはご存知でしょうか?
WebDAVはRFC4918で標準化されたWebサーバーを共有フォルダのように扱うことができるHTTP拡張機能です。
かなり雑に紹介しましたが、もう少し詳しく知りたい人は以下のページをご参照ください。

https://www.kagoya.jp/howto/it-glossary/server/webdav/
https://datatracker.ietf.org/doc/html/rfc4918

廃止されたRFC2518は24年前からあり、非常に長い歴史があります。
WindowsではネットワークドライブとしてSMB/CIFSが使われることが多いですが、WebDAVは現在も標準でサポートされており、HTTPを拡張しているためWebサーバーの機能をそのまま使うことができ、Basic認証やcurlのようなHTTPリクエストクライアントとも相性が良いためものすごく簡単に使用することができます。

webdav

こんな感じの分報サイトも簡単に作ることができます。
index.mdというMarkdownファイル1つだけを編集するだけのシンプルさと、JavaScriptを使わないことで様々な環境で表示できる便利さを備えています。
ではやってみましょう。

Caddy

今回はLinux上でやってみたいと思います。Windowsでも動かせますがWebDAVサーバーは24時間365日動かせたほうがいいのでDebianやUbuntuのようなサーバー上で動かすことをオススメします。
Custom Caddy Builderのxcaddyを使用してWebDAV ModuleをCaddyに追加してみます。

https://github.com/caddyserver/xcaddy

$ curl -sLO https://github.com/caddyserver/xcaddy/releases/download/v0.3.4/xcaddy_0.3.4_linux_amd64.tar.gz
$ tar -xzf xcaddy_0.3.4_linux_amd64.tar.gz
$ sudo mv xcaddy /usr/local/bin/xcaddy
$ xcaddy build --with github.com/mholt/caddy-webdav
$ sudo mv caddy /usr/local/bin/caddy

公式ウェブサイトからダウンロードページへ行き、Platformでlinux_amd64を選択、Moduleのmholt/caddy-webdavを選択した後のダウンロードURLをcurlやwgetでGETしたり、Downloadボタンを押してダウンロードしたファイルを何らかの方法でLinuxへ移動しても構いません。

https://caddyserver.com/download

$ caddy version
v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=

$ caddy list-modules
...

  Standard modules: 100

http.handlers.webdav

  Non-standard modules: 1

  Unknown modules: 0

caddy list-modulesコマンドを使い追加されていることを確認できました。

Caddyfile

Caddyで必要なコンフィグファイル、Caddyfileをこのように書き、カレントディレクトリに置くだけで簡単に設定を読み込むことができます。

:8080
root * public
@notget not method GET
route @notget {
	basicauth {
		{$BASIC_AUTH_USERNAME} {$BASIC_AUTH_PASSWORD}
	}
	webdav
}
file_server {
	browse browse.html
}

ウェブページを表示するGETメソッドの時以外でBasic認証を用いたアクセス制限をもたせることで、ログインしていない人の不正なアップロードを防ぐことができます。
他、別途カスタマイズしたbrowse.htmlを呼び出せるようにします。

$ caddy hash-password --plaintext password

Basic認証ログインに必要なパスワードをハッシュ化します。
デフォルトでbcryptを使用します。
ハッシュ化しますがパスワードは強力なものを使用してください。
上記の例のようなパスワードはだめです。

$ export BASIC_AUTH_USERNAME=username
$ export BASIC_AUTH_PASSWORD='$2a$14$...'

環境変数に自分で決めたユーザー名とパスワードのハッシュを入力します。
パスワードのハッシュはシングルクォーテーションで囲みましょう。
この時自ら設定したユーザー名とパスワードはコピペ等行い覚えておきましょう。

browse.html

<!DOCTYPE html>
<html>
	<head>
		<title>{{html .Host}}</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
		<style>
			:root{--links:#fff}
		</style>
	</head>
	<body>
		<header>
			<a href="/">{{html .Host}}</a>
		</header>
		<main>
{{include (printf "%sindex.md" .Req.URL.Path) | markdown}}
		</main>
		<footer>
			Styled with <a rel="noopener noreferrer" href="https://watercss.kognise.dev">Water.css</a> | Served with <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
		</footer>
	</body>
</html>

Markdown用のTemplateを書きました。
CSSフレームワークはクラスレスなWater.cssを使用しています。
HTMLタグにclassを追加しなくてもスタイルを適用することができるため、Markdownを使ったページと非常に相性が良くオススメです。

https://watercss.kognise.dev/

上記browse.htmlファイルでは簡潔にするためjsDelivrから読み込んでいますが、私はHTMLにminifyしたCSSソースコードをstyleタグで埋め込んでいます。

$ mkdir public
$ touch public/index.md
$ ls -laR
.:
total 16
drwxr-xr-x 3 webdav webdav    56 Jul  2 13:40 .
drwxr-xr-x 3 webdav webdav    15 Jul  2 13:40 ..
-rw-r--r-- 1 webdav webdav 10578 Jul  2 13:40 browse.html
-rw-r--r-- 1 webdav webdav   171 Jul  2 13:40 Caddyfile
drwxr-xr-x 2 webdav webdav    22 Jul  2 13:40 public

./public:
total 0
drwxr-xr-x 2 webdav webdav 22 Jul  2 13:40 .
drwxr-xr-x 3 webdav webdav 56 Jul  2 13:40 ..
-rw-r--r-- 1 webdav webdav  0 Jul  2 13:40 index.md
$ caddy run

準備ができたのでWebDAVサーバーを動かしてみます。

WebDAV

それではWebDAVで実際にCRUDしてみましょう。

$ curl -i -T index.md -u username:password https://webdav.test/
HTTP/1.1 201 Created
Content-Length: 7
Content-Type: text/plain; charset=utf-8
Date: Sun, 02 Jul 2023 13:50:00 GMT
Etag: "..."
Server: Caddy

Created

ユーザー名とパスワードを-uオプションで渡し、-Tオプションでindex.mdを作成します。

https://webdav.test/

FirefoxやDilloで確認できます。
もちろん普段使用しているブラウザであれば何でもいいです。

$ vi index.md
$ curl -i -T index.md https://username:password@webdav.test/
HTTP/1.1 201 Created
Content-Length: 7
Content-Type: text/plain; charset=utf-8
Date: Sun, 02 Jul 2023 13:50:10 GMT
Etag: "..."
Server: Caddy

Created

index.mdを編集し、先程と同様のコマンドを入力すればindex.mdを更新できます。
-uを使わなくてもURLのユーザー名とパスワードに入力すれば問題なくアップロードできます。

$ curl -i -X DELETE -u username:password https://webdav.test/1688240745.zip
HTTP/1.1 204 No Content
Date: Sun, 02 Jul 2023 13:50:15 GMT
Server: Caddy

アップロードしたファイルを削除するときは-XオプションでDELETEメソッドを投げます。

今回はcurlを使いましたがwgetでもaxiosでもfetchでもなんでもいいです。
ただcurlの-T(--upload-file)オプションを使うのが一番簡単にファイルをアップロードできます。

https://github.com/Waboodoo/HTTP-Shortcuts

他にもAndroid端末で使えるHTTPリクエストクライアントを使いスマートフォンからもアップロードすることができます。
私はRaspberry Pi Zero WHでCaddyのWebDAVサーバーを動かし、Wi-Fi経由でアクセスする時使用しています。

他にはWindowsの共有フォルダとして接続する方法もあります。

https://support.kagoya.jp/kir/manual/webdav/win10/index.html

Outro

VPSで動かすもよし。Raspberry Pi Zeroで動かすもよし。NASのDocker機能で動かすもよし。
VPNでアクセスするもよし。Tailscaleでスマホからアクセスするもよし。DDNSで全世界に公開するもよし。
カスタマイズして便利なWebDAVサーバーのある生活を楽しみましょう。

Extra

includeやmarkdownなど、今回使用したTemplateは以下のソースコードを参考にしました。

https://github.com/caddyserver/website/blob/master/src/docs/index.html

今回の解説で省略したいくつかの部分は過去記事をご参照ください。

https://zenn.dev/tkithrta/articles/74085b0d91b5c6

以前紹介した画像生成AIで生成した画像を管理する時のテンプレートは以下のものに落ち着きました。

<!DOCTYPE html>
<html>
	<head>
		<title>{{html .Host}}</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
		<style>
			:root{--links:#fff}
			body{max-width:1600px}
		</style>
	</head>
	<body>
		<header>
			<a href="/">{{html .Host}}</a>
		</header>
		<main>
			{{- if eq "/" html .Name}}
			<ul>
				{{- range .Items}}
				<li><a href="{{html .URL}}">/{{html .Name}}</a></li>
				{{- end}}
			</ul>
			{{- else}}
			{{- range .Items}}
			<img src="{{html .URL}}">
			{{- end}}
			{{- end}}
		</main>
		<footer>
			Styled with <a rel="noopener noreferrer" href="https://watercss.kognise.dev">Water.css</a> | Served with <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
		</footer>
	</body>
</html>

Souinもよろしくお願いします。

https://zenn.dev/tkithrta/articles/f8c995cff13ac8

Discussion