PhotoPrism+Tailscaleでお手軽写真サーバを作ろう(Immichへの移行のおまけつき on Windows)

この記事は[岩手の人とか、岩手県立大学の人とか Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ipu) 24日目の参加投稿です。
予定日から3日遅れての投稿となってしまい申し訳ありません...

## はじめに
3年前にGoogleフォトが有料化してからというものの、私はGoogle Oneに月250円を払い続けてきた。しかも途中で100GBから200GBプランに変更したためより金額は上がり、
大体年に**3800円**程度を払っていることになった。
どうにかしなければならないな～と思いつつここまで何も手を打たないでいたが、先日いよいよ200GBの容量制限に近づいたという通知が来た。
流石にこれ以上金を払うのはまずいと思い、楽天のブラックフライデーセールに乗じて一念発起した。

## 作るにあたって考えたこと
1. **面倒くさくないこと**
一回つけっぱなしにしたらあとは放置で全部済むようにしたい。

2. **金がかからないこと**
CloudFlare Tunnel+独自ドメインという手もあったが、ドメインは手軽に取れるがその後の維持管理や更新の費用も考えるとランニングコストがかかってくる
そのためHDDとサーバの電気代だけで運用できるようなお手軽構成とした

3. **すでにあるもので動くこと**
自宅にあった使ってないPCを動かせばいんじゃね？と思ったため。
スリムPCのため拡張性は一切ないが、仕方ない。

## 考えなかったこと
1. **耐故障性**
故障した場合のことは一切考えていない。そのため今後差分バックアップなどの策は考える予定。

2. **消費電力**
お古のCPUのため、消費電力が高いと思われる。

3. **騒音**
CPUがフル稼働していると添い寝し辛いうるささに。

4. **セキュリティ**
とりあえず動くだけを目標にしているので、ガバがあっても致し方なし。

## 機器構成
マシンスペックは以下の通り。

CPU: Intel Core i7-4790
GPU: NVIDIA GeForce GTX 1650
HDD: Seagate Barracuda 8TB
SSD: Acer 480GB→KIOXIA Exceria SATA 480GB(Clonezillaでクローン)
NW: 1Gb Ethernet
OS: Windows 10 Pro)

## Photoprismのインストール→起動まで
予めDocker DesktopをPCにインストールしておく。
こちらを参考にしてほしい。
[Windows 11にDocker Desktopを入れる手順（令和5年最新版）](https://qiita.com/zembutsu/items/a98f6f25ef47c04893b3)


次に、SSDにphotoprismをインストールする。
[Setup Using Docker Compose](https://docs.photoprism.app/getting-started/docker-compose/#__tabbed_1_5)を参考にcompose.yamlを作成し、
C:/User/ユーザ名/photoprism に配置する。

といっても変更したポイントは2点のみで
①取り込む画像/動画ファイルの配置場所を記述する
```yaml
    volumes:
      - "H:/photoprism/originals"                
      - "H:/photoprism/import"                         
      - "H:/photoprism/storage"                   
```
それぞれのフォルダの用途は以下の通り。
<table>
  <thead>
    <tr>
      <th>フォルダ名</th> <th>用途</th>
    </tr>
  </thead>
  <tr>
    <td> originals </td> <td>オリジナル画像。<br>ここに置かれた画像はそのままphotoprismにインデックスされる</td>
  </tr>
  <tr>
    <td> import </td> <td>取り込み用画像フォルダ。<br>ここに置かれた画像はEXIF情報を保持したままコピーされoriginalsフォルダに移動される。</td>
  </tr>
  <tr>
    <td> storage </td> <td>キャッシュやDBデータなどの置き場。</td>
  </tr>
</table>

また、上で記述した通りのフォルダ構成になるように、HDDの直下にphotoprismフォルダを作成し、
その下に予め*originals*,*import*,*storage*の3つを作成しておくこと。

②デフォルトの管理者名とパスワードを変更する

あくまで以下は例である。この通りには設定しないこと。
```yaml
    environment:
      PHOTOPRISM_ADMIN_USER: "hogehoge"          #管理者のユーザ名        
      PHOTOPRISM_ADMIN_PASSWORD: "fugafuga_1234567"   #8~72文字でパスを入力
```

ここまできたら[Googleフォトから写真を一括ダウンロードし、容量を回復させる方法](https://qiita.com/Aichi_Lover/items/eef3186d4b20c603566d)を参考に、
Google Photoからデータをエクスポートし、zipをimportフォルダ内に解凍する。

そしてcompose.yamlが配置されたフォルダ内に移動し
```
docker compose up -d
```
を実行する。自動的に公式のイメージダウンロードや必要なファイルのインストールが行われ、コンテナが起動する。
http://localhost:2342 をブラウザで開き、上述②で設定した管理者名とパスワードでログインする。

最初は画像も動画もない状態。
公式ガイドの[Importing Files to Originals](https://docs.photoprism.app/user-guide/library/import/)に従い、
importフォルダの取り込みを実施するとだんだん画像が増えていく。
ただ画像が多い場合処理に2~3日かかるので、気長に待つこと。


## あれ、immichのがよくない...?
色々photoprismをいじくった後に気づいた問題点として

・スマホアプリがサードパーティ製のものしかなく、クローズドソースで、広告も出るしバックグラウンドのアップロードにも対応していない。

・デザインがこれまでのGoogleフォトから大きく変更され使い慣れない

・設定欄がごちゃついていてわかりにくい

これらが問題となった。
そのためimmichに移行することにした。

## immichのWindowsへのインストール手順
色々ググってみたがimmichをwindowsで立ててみた人の記事はあまり見つからなかったので手こずった。
といってもそこまで難しくない。パスの欄はWinでもフルパス指定すればいけたのが驚きだった。

①docker-compose.ymlなど4ファイルを記述する。
作成した4ファイルは今回はSSDの`C:/User/ユーザ名/immich-app` に配置する。
サンプルを載せておくので、よければ参考にしてほしい。
.env以外はNVIDIA GPUの設定も記述している。


<details>
<summary>.env</summary>

```yaml
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=H:\immich
# The location where your database files are stored
DB_DATA_LOCATION=pgdata

# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
TZ=Asia/Tokyo

# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release

# Connection secret for postgres. You should change it to a random password
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
DB_PASSWORD=postgres

# The values below this line do not need to be changed
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

```
</details>

<details>
<summary>docker-compose.yml</summary>

```yaml
#
# WARNING: Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
#

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    extends:
        file: hwaccel.transcoding.yml
        service: nvenc
    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - '2283:2283'
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
    # Example tag: ${IMMICH_VERSION:-release}-cuda
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-cuda
    extends:
        file: hwaccel.ml.yml
        service: cuda
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: >-
        pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
        Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
        --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
        echo "checksum failure count is $$Chksum";
        [ "$$Chksum" = '0' ] || exit 1
      interval: 5m
      start_interval: 30s
      start_period: 5m
    command: >-
      postgres
      -c shared_preload_libraries=vectors.so
      -c 'search_path="$$user", public, vectors'
      -c logging_collector=on
      -c max_wal_size=2GB
      -c shared_buffers=512MB
      -c wal_compression=on
    restart: always

volumes:
  model-cache:
  pgdata:

```
</details>

<details>
<summary>hwaccel.ml.yml</summary>

```yaml
# Configurations for hardware-accelerated machine learning

# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# into the immich-machine-learning service in the docker-compose.yml file.

# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.

services:
  armnn:
    devices:
      - /dev/mali0:/dev/mali0
    volumes:
      - /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver)
      - /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required)

  cpu: {}

  cuda:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities:
                - gpu

  openvino:
    device_cgroup_rules:
      - 'c 189:* rmw'
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - /dev/bus/usb:/dev/bus/usb

  openvino-wsl:
    devices:
      - /dev/dri:/dev/dri
      - /dev/dxg:/dev/dxg
    volumes:
      - /dev/bus/usb:/dev/bus/usb
      - /usr/lib/wsl:/usr/lib/wsl

```
</details>

<details>
<summary>hwaccel.transcoding.yml</summary>

```yaml
# Configurations for hardware-accelerated transcoding

# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# into the immich-microservices service in the docker-compose.yml file.

# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.

services:
  cpu: {}

  nvenc:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities:
                - gpu
                - compute
                - video

  quicksync:
    devices:
      - /dev/dri:/dev/dri

  rkmpp:
    security_opt: # enables full access to /sys and /proc, still far better than privileged: true
      - systempaths=unconfined
      - apparmor=unconfined
    group_add:
      - video
    devices:
      - /dev/rga:/dev/rga
      - /dev/dri:/dev/dri
      - /dev/dma_heap:/dev/dma_heap
      - /dev/mpp_service:/dev/mpp_service
      #- /dev/mali0:/dev/mali0 # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
    volumes:
      #- /etc/OpenCL:/etc/OpenCL:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
      #- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping

  vaapi:
    devices:
      - /dev/dri:/dev/dri

  vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - /usr/lib/wsl:/usr/lib/wsl
    environment:
      - LIBVA_DRIVER_NAME=d3d12

```
</details>

②移動して起動確認
`C:/User/ユーザ名/immich-app`に移動し、
```shell
docker-compose up -d
```
と実行する。
しばらく待つと`http://localhost:2283`でimmichにログインできるようになるはず。

## GPUがある人向け設定
GPUとドライバがインストールされ正常に使用できる状態になっている人なら画像分類やトランスコード、重複検出などに用いて処理が高速化される。

Ex. 動画トランスコードの例
管理→設定→動画トランスコード設定→ハードウェアアクセラレーションと移動し、
アクセラレーション APIを**NVEncに変更**
ハードウェアデコードを**有効**に
適応的量子化を**有効**にそれぞれ設定する。
![clipboard_2024-12-27_00-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576615/0ecd446f-aff2-58b8-092e-d06128ca26ac.jpeg)



## Photoprism→Immichの移行
Giulio Librando氏が作成した[Photoprism2Immich](https://github.com/giuliolibrando/photoprism2immich)を使用する。
適当な場所でコマンドプロンプトを開き、

```sh
# リポジトリをクローン
git clone https://github.com/giuliolibrando/photoprism2immich.git
cd photoprism2immich

# requirements.txtからインストール
pip install -r requirements.txt
```

ここまできたらImmichのAPIキーを生成する。
設定からAPIキーを開き、新しいAPIキーを押下してキーを生成する。
![clipboard_2024-12-26_23-47.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576615/8d8538ab-c84d-cb1a-41a3-88d7b0d93086.jpeg)

最後に

```shell
photoprism2immich migrate-library --apikey "APIキー" --baseapiurl "http://localhost:2283/api" --originals "H:\photoprism\originals"
```
これでインポートが開始される。結構時間がかかるので気長に待つこと。



## そもそも...
最初からimmichを使うことが決まっているならば、immich-goを用いて簡単にGoogleフォトから乗り換えられる。
こちらの記事を参照していただきたい。

https://qiita.com/muhi111/items/7a3a5aa2217fed9adb06#google%E3%83%95%E3%82%A9%E3%83%88%E3%81%8B%E3%82%89%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%A7%BB%E8%A1%8C


## TailScaleの設定
こちらの記事を参考にTailScaleをサーバとスマホの双方にインストールする。

https://utakamo.com/article/network/vpn/tailscale/index.html

その後、サーバ側で以下のコマンドを入力する。

```shell
tailscale up
tailscale serve --bg 2283
```
これでスマートフォンからサーバにアクセスできるようになった。

## Immichスマホアプリの設定
ImmichのアプリにはNWの自動選択画面が存在する。
これにより
自宅：WiFiをつなぎ宅内LAN経由で高速にアップロードをする
宅外：TailScale VPN経由でアップロードする
とユースケースに合わせて適切なアップロードが可能になる。

やり方は簡単。
Immichの設定からNetworkingに移動。
**Automatic URL switching**をオンにする。
自宅の場合は上部の*Local Network*で**USE CURRENT CONNECTION**を押下する。

宅外の場合はTailScaleのサーバ側のURLを下の*External Network*の箇所に入力するだけ。
疎通確認が取られ、OKならチェックが付く。
![Screenshot_20241214-235136.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576615/c2f463cc-1755-83d2-ad48-2f4367df25d5.png)




## 今後やること
### 耐障害性
・定期バックアップを何処かに取る(差分or増分)
・PCがスリム型でHDDベイが一つしかない&エアフローが悪いのでPCを新しく自作してRAIDを組む
・UPSとか買って設置してみたい
### 多用途化
・他の用途にもPCを使えるようにしたいため、Proxmox VEにP2Vしてみたい
・常時稼働させた場合の電力と騒音がヤバいのでラズパイにするか悩む
・NextCloudなどのオールインワンパッケージと比べてみる
・音楽サーバーやファイルサーバなどとしても使ってみる
### 可用性向上
・PCが再起動した場合にどうにかできるようVDE周りの設定を考えたりする
・TailScaleを毎回手動で起動させているので、OS起動時にコンテナ起動やTailnet接続まで完結するスクリプトを組む

## おわりに
使う当てがなく放置していたPCでも、低スペでもサーバとしてなら意外と使えるかもしれません。
よかったら休日にお手軽サーバ構築、試してみてください。