余ってたN100ミニPCでサイトを自宅公開した

余っていたN100のミニPCをサーバーにして、このサイトを自宅から公開しました。ルーターのポートは1つも開けていません。やったことを順番にまとめておきます。

構成

ブラウザ
  → Cloudflare(TLS / DNS)
  → Cloudflare Tunnel(サーバー側から外向きに接続)
  → N100 ミニPC(Rocky Linux)
  → Podman(rootless)
  → nginx:alpine(127.0.0.1:8080)
  • マシン: Intel N100 / メモリ12GB / SSD 256GB
  • OS: Rocky Linux(SELinux は有効のまま)
  • Web: nginx:alpine を podman-compose で起動
  • 公開: cloudflared を systemd サービスとして常駐

使ったマシン

ACEMAGIC の N100 ミニPC。前面に USB 3.2 Type-A ×3、背面に DC 12V・有線LAN ×2・オーディオ・HDMI ×3

ACEMAGIC の N100 ミニPC です。2万円くらいでした。最近は N100 のミニPCも値上がりしていて、今だと同じ値段では買えなさそうです。

N100 は省電力の CPU(TDP 6W)で、ミニPC全体でもアイドル時は 10W 前後と言われています。仮に 10W で24時間つけっぱなしにしても、1か月で約 7kWh、電気代は 200円ちょっとです(1kWh = 31円で計算)。静かで場所も取らないので、自宅サーバーに向いています。

ただ、このメーカーは過去に、出荷時の Windows にマルウェアが入っていたことがあります。手元の個体も、Windows がボリュームライセンスで認証されていて少し気になりました。どのみち Windows は消して Linux を入れるので、問題ないと判断しました。

OS選び

Windows ではなく Linux にした理由

  • サーバーとして動かすだけなので、余計な機能はいらない。Linux は軽く、そのぶんメモリや CPU をサイトに回せる
  • 勝手に再起動する更新や、おすすめ・広告のような機能がない。最近の Windows は機能を増やしすぎていて、使っていて不快だった
  • nginx、Podman、SSH など、サーバーで使うものが揃っていて無料

Rocky Linux にした理由

  • 今まで Debian 系しか触ってこなかったので、Red Hat 系も触ってみたかった
  • RHEL 互換で、サポート期間が長く堅牢。SELinux も最初から有効になっている

手順

1. Rocky Linux を入れる

公式サイトから ISO をダウンロードして USB メモリに書き込み、ミニPCにインストールします。終わったら更新して、SSH を有効にします。

sudo dnf update -y
sudo reboot
sudo systemctl enable --now sshd
ip a   # サーバーの IP アドレスを確認

ここから先は、メインのPCから ssh ユーザー名@IPアドレス で入って作業しました。

2. ドメインを取る

masu.uk は Cloudflare Registrar で取りました。Cloudflare は原価で売っているので、取得も更新も年5ドルくらいです。DNS もそのまま Cloudflare で管理できるので、あとで Cloudflare Tunnel を使うときに楽です。

3. nginx をコンテナで動かす

Podman と podman-compose を入れます。

sudo dnf install -y podman python3-pip
pip install --user podman-compose
mkdir ~/nginx && cd ~/nginx

~/nginx/docker-compose.yml を作ります。

services:
  web:
    image: docker.io/library/nginx:alpine
    container_name: nginx-masu
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - /home/usr01/blog/public:/usr/share/nginx/html:ro,Z
      - ./default.conf:/etc/nginx/conf.d/default.conf:ro,Z
    restart: always
  • 127.0.0.1:8080 にだけ公開して、LAN にも外にも出さない。外との通信はトンネルだけにする
  • Hugo のビルド結果(public/)をそのままマウントする。ビルドすれば、コンテナを触らずにサイトが更新される
  • ボリュームに :Z を付ける。SELinux が有効だと、これがないとコンテナからファイルが読めない

nginx の設定 ~/nginx/default.conf では、キャッシュの扱いだけ変えています。

server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;
    error_page 404 /404.html;

    # HTML・RSS など: 毎回サーバーに更新を確認させる
    location / {
        try_files $uri $uri/ =404;
        add_header Cache-Control "no-cache";
    }
    # CSS: ファイル名にハッシュが入るので長期キャッシュでOK
    location /css/ {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

起動して、200 OK が返ってくれば成功です。

~/.local/bin/podman-compose up -d
curl -I http://127.0.0.1:8080

4. Cloudflare Tunnel でつなぐ

cloudflared を入れて、サービスとして登録します。

curl -fsSL https://pkg.cloudflare.com/cloudflared.repo | sudo tee /etc/yum.repos.d/cloudflared.repo
sudo dnf install -y cloudflared
sudo cloudflared service install <トークン>

トークンは、Cloudflare の Zero Trust の画面でトンネルを作ると表示されます。画面に出るコマンドをそのまま貼ればOKです。トークンを知られるとトンネルを乗っ取られるので、人に見せないようにします。

最後に、トンネルの公開ホスト名で masu.uk を http://localhost:8080 に向ければ、外から見られるようになります。

5. ログアウトしても止まらないようにする

公開できたと思ったら、しばらくして 502 Bad Gateway になりました。

$ podman ps -a
STATUS
Exited (0) 11 minutes ago

rootless の Podman だと、SSH からログアウトしたタイミングでユーザーのプロセスごとコンテナが止まります。次の2つで解決しました。

sudo loginctl enable-linger $USER
systemctl --user enable --now podman-restart.service

enable-linger でログアウト後もユーザーのサービスが動き続けるようになり、podman-restart.service で再起動後に restart: always のコンテナが立ち上がります。sudo reboot して、何もしなくてもサイトが戻ることを確認しました。

6. ブログを書いて公開する

ブログは Hugo で作っています。テーマは使わず、テンプレートと CSS は自分で書きました。

pip install --user hugo

記事は Markdown で書いて、./deploy.sh を実行すると公開されます。中でやっていることは次の2つです。

hugo --minify --cleanDestinationDir   # public/ にサイトを書き出す
chcon -R -t container_file_t public   # nginx コンテナから読めるように SELinux のラベルを付ける

nginx は public/ をそのまま配信しているので、ビルドすればすぐ反映されます。データベースもない、ただの HTML ファイルなので、N100 でも余裕です。