3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

watnow Advent Calendar 2024

Day 14

Googleフォトを自宅で運用!? WSL2でimmichを構築してお引越し

Last updated at Posted at 2024-12-19

はじめまして、学生団体watnowに所属しています、立命館大学4回生のるいるいと申します。この記事は watnow Advent Calender 2024 14日目のエントリーです。外がだいぶ冬の空気になってきた。

中3からの8年間使い続けてきたGoogleフォトへの写真のバックアップをやめて、つい先日Immichというオープンソースソフトウェアを使ってセルフホスティングするという夢を果たしたので、今回はその備忘録とか感想をお話ししようかなと思います。初学者(?)でもわかりやすい表現になってるはずなので、興味がある方はぜひ読んでみてください。

完成図と感想だけ見たい人はコチラ

サムネ作ってみました、ウチったらYouTuber1だし。
image.png
お引越し!

Googleフォトとは

撮った写真と動画を管理してくれる、Googleのクラウドストレージサービスです。スマホにアプリを入れると撮った写真や動画を全部吸い出してクラウドにあげてくれて、お得意のAIが顔認識、文字認識、物体認識を行いながらタグ付け、整理を行なってくれます。スマホアプリ、Webからいつでも閲覧、編集、検索、共有できて、思い出したい写真を自然言語で調べたり、アルバムの作成を提案してくれたり、n年前の今日に何をしていたのかのような思い出のまとめとかまでやってくれます。古い写真を持ち続けるメリット=思い出に浸る体験を享受しやすい機能が続々生まれている、進化が止まらないサービスです。カメラで撮った写真もWebから簡単にアップロードができて、撮影機材を指定して検索することもできるのでとても便利です。

「人生で撮影した写真は分散されて死ぬまで安全にバックアップされていたい」、「自分があらゆるデバイスで撮った写真や動画をがいつでも検索できる場所が1箇所だけあるべき」と考える自分にとって、Googleフォトは最高の選択肢でした。

そう、あの時が来るまでは。

image.png

Google フォト、 容量無制限保存が廃止 (2020年11月 デジカメ Watch)

使い始めた当初は、少しの画質の劣化を受け入れれば無制限に写真や動画をアップロードすることができていました(すごい)。そんな2020年の11月のある日、Googleから突然無制限アップロードのサポートの終了の知らせが届きます。さすがの巨大計算、巨大ストレージ資本を持つGoogleでも、耐えきれなくなったのか、それとも学習のためのデータセットはもう十分に集まったのか、とにかく全Googleフォトユーザーは引越しか容量の定期購入のどちらかの選択を迫られました。

自分はとりあえず 15GB → 100GB → 200GB と保存容量をアップグレードすることによって延命措置を続けてきたのですが、今年ついにその200GBをも使い切ってしまいました。次に選べる最低のプランは2TB(¥1,300/月)。SDGsの観点からも、これまで先延ばしにしてきた問題の本質に向き合うなら今だ、愛する地球を守りたい、そう感じたのでセルフホスティングに乗り換える方法を探り始めました。

そこで見つけたのが、オープンソース写真管理ソリューションImmichです。

Immichとは

Googleフォトの代わりとなる、オープンソースのソリューションです。サーバー、Webアプリ、モバイルアプリの3つでできています。サーバーソフトは家のパソコンやVPSなどで動かし、モバイルアプリはアプリストアからダウンロードして、立てたサーバーに繋いで使います。

他にもいくつか、候補があった中から、モバイルアプリがしっかりしてるもの、一番開発が活発そうなものを選びました。バック、フロント共にTypeScript製で、コントリビュートしたくなった時に触りやすいのも嬉しいね。

このサイトがわかりやすく各サービスを比較してくれています:

image.png

We were lying in bed with our newborn, and my wife said, "We are starting to accumulate a lot of photos and videos of our baby, and I don't want to pay for App-Which-Must-Not-Be-Named anymore. You always want to build something for me, so why don't you build me an app which can do that?"
(中略) I want a simple-to-use backup tool with a native mobile app that can view photos and videos efficiently. So I set sail on this journey as a hungry engineer on the hunt.

奥さんに「赤子の写真を貯めていきたいけど、もうアレには課金したくない、いつもみたいにアプリ作ってくれない?」って言われた。既存のものはアレの代わりにならなそうだし、いつかOSSのコミュニティーに貢献してみたかったし、作りはじめた!

👆いい話。アレとはGoogleフォトのことですね。

Immichを導入する

たまにゲームしたりVRChatをするだけで最近使ってないWindowsのデスクトップがあるので、それを使います。たまにゲームをするからホストOSとしてWindowsは起動しながらやりたかったので、WSL2とDockerを使うことにしました。

環境

  • ホストOS
    • Windows 11 Education (10.0.22631.4602)
  • CPU
    • Intel i7-8700k
  • GPU
    • NVIDIA GeForce GTX 1070
  • 仮想化のやつ
    • WSL 2.3.26.0
    • Docker Desktop 4.35.1
    • Docker Engine 27.3.1 (WSL 2ベース)
  • WSL ディストリビューション
    • Ubuntu 20.04

インストール

WSL2を有効にし、Docker Desktopがインストールされ、Ubuntu 20.04がWSL2上で動いている状態から始めます。Immichの公式ドキュメントはDocker Composeでのインストールを推奨しているので、基本的にはそれに従います。

1. Docker ComposeでImmichをインストール

WSLのUbuntuで作業します。ディレクトリも/mnt/data/immich_photo_libraryに作成します。

① 必要なファイルのダウンロード

~/immich-appで作業
$ mkdir ~/immich-app && cd ~/immich-app

公式ドキュメント通りに以下の4ファイルを落とします。

docker-compose.yml
.env
hwaccel.transcoding.yml
hwaccel.ml.yml

② 設定ファイルを書く(.env)

ダウンロードした.envを編集します。

.env
  (略)
  # The location where your uploaded files are stored
  # You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
- UPLOAD_LOCATION=./library
+ UPLOAD_LOCATION=/mnt/data/immich_photo_library
  # The location where your database files are stored
- DB_DATA_LOCATION=./postgres
+ DB_DATA_LOCATION=pgdata
  (略)

2箇所の変更について説明します。

UPLOAD_LOCATION

  • アップロードされた写真や動画が保存される場所を指定します。
  • ここでは、WSL2 Ubuntu上の/mnt/data/immich_photo_libraryに保存するように設定しています。
  • このディレクトリは後の手順で作成します。

DB_DATA_LOCATION

  • データベースのデータが保存される場所を指定します。
  • 高々数GBだし、速いディスクに置くと良いらしい2らしいです。
  • WSLの場合、ホストディレクトリ(/mnt/cとか)みたいな、Linuxの権限システムをサポートしないファイルシステムにおいちゃダメらしく、Dockerのvolumeを使うと確実らしいので、pgdataというvolumeを指定しました。
    • 同時に、docker-compose.ymlでpgdataというvolumeを作るように設定します:
      docker-compose.yml
      volumes:
        model-cache:
      + pgdata:
      

③ 設定ファイルを書く (docker-compose.yml)

ダウンロードしたdocker-compose.ymlを編集します。

公式ドキュメントに従って、GPUを使用した機械学習を有効にする設定を行いました。今回のホストマシンにはNVIDIAのGPUを使用しているので、NVENCとCUDAを使うようにそれぞれ指定します。

docker-compose.yml
  (略)
  services:
    immich-server:
      container_name: immich_server
      image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
-     # extends:
-     #   file: hwaccel.transcoding.yml
-     #   service: cpu
+     extends:
+       file: hwaccel.transcoding.yml
+       service: nvenc
      volumes:
      
  (略)

    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}
-     # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
-     #   file: hwaccel.ml.yml
-     #   service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
+     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:

  (略)

  volumes:
    model-cache:
+   pgdata:

2. UPLOAD_LOCATIONを準備

先述の通り、Immichは写真や動画がアップロードされた時の保存先のパスを指定できます。すでにPCに刺さってるストレージはほぼ余裕がなかったので、Immichのためだけのハードディスクを1台購入して増設しました。

8TBです、でけー。データの信頼性もこの移行計画を行う価値のために大事だと思って高耐久のNAS用HDDを選んだら、プライムデーでも2万8千円しました、たけー!

image.png
新入りだよ。仲良くね。

増設したHDDはWindowsからアクセスすることがほとんどないと思ったので、あえてext4(Linux専用のファイルシステム)でフォーマットして、Windows起動時にWSL2に自動でマウントするようにしました。(迷った形式で実際にフォーマットを行い速度を比較した結果、2倍くらい読み書き速度に差が出た覚えがあります。気が向いたら記事にします)

① Windowsで物理ディスクをext4にフォーマットする

この記事通りにやりました 👇

② マシンに接続されたext4の物理ディスクを直接WSLに接続する & それを自動化する

この記事通りにやりました 👇

もっと具体的に!🔍

1. PowerShellで物理ディスクの一覧を見る

PowerShell
PS C:\Windows\system32> GET-CimInstance -query "SELECT * from Win32_DiskDrive"
>>

DeviceID           Caption                   Partitions Size          Model
--------           -------                   ---------- ----          -----
\\.\PHYSICALDRIVE2 ST3500413AS               1          500105249280  ST3500413AS
\\.\PHYSICALDRIVE0 Samsung SSD 860 EVO 500GB 3          500105249280  Samsung SSD 860 EVO 500GB
\\.\PHYSICALDRIVE1 WDC WD40EZRZ-00GXCB0      1          4000784417280 WDC WD40EZRZ-00GXCB0
\\.\PHYSICALDRIVE3 ST8000VN002-2ZM188        0          8001560609280 ST8000VN002-2ZM188

\\.\PHYSICALDRIVE3 の容量が8TBなので、これと判断しました。

2. デバイスIDを指定して、物理ディスクを直接WSLに接続する

管理者権限 PowerShell
PS C:\Windows\system32> wsl --mount \\.\PHYSICALDRIVE3 --bare
この操作を正しく終了しました。

--bareオプションで未マウント状態で接続できるらしいです。3

3. WSLに接続できてることを確認する

bash
$ lsblk
NAME MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda    8:0    0 388.4M  1 disk
sdb    8:16   0     4G  0 disk [SWAP]
sdc    8:32   0   7.3T  0 disk                    👈 できてる
sdd    8:48   0   256G  0 disk /mnt/wslg/distro

sdcというデバイスとしてディスクが接続できてそうです。マウントはされていないので、マウントポイントの欄は空になっています。

③ マウントポイントを作成し、WSL起動時に自動でマウントするように

以下、WSL上の作業です。これも②の記事の続きとなります。

もっと具体的に!🔍

1. マウントポイントの作成

$ sudo mkdir /mnt/data

/mnt/data にマウントできてそうです。

2. パーティションのUUIDを調べる

$ sudo blkid
/dev/sda: TYPE="ext4"
/dev/sdb: UUID="a5cf91b8-0609-4cc4-93ec-9eb1b16565a0" TYPE="swap"
/dev/sdc: UUID="a589ba1a-ea5f-4254-b2ec-18620e54ce1c" TYPE="ext4"
/dev/sdd: UUID="3255683f-53a2-4fdf-91cf-b4c1041e2a62" TYPE="ext4"

lsblkの結果、/dev/sdc のデバイスが目的のデバイスだったので、今回の場合ディスクのUUIDは a589ba1a-ea5f-4254-b2ec-18620e54ce1c となります。

3. fstabに自動マウントのための設定を追記

$ sudo vi /etc/fstab

2で調べた、マウントしたいディスクのUUIDを指定してマウントする設定を以下のように追記します。

UUID=<自動マウントしたいディスクのUUID> <マウントポイントへのパス> ext4 defaults 0 0

/etc/fstab 例
  LABEL=cloudimg-rootfs	/	ext4	defaults	0 1
+ UUID=a589ba1a-ea5f-4254-b2ec-18620e54ce1c	/mnt/data	ext4	defaults	0	0

これで、WSLが起動した時にext4のハードディスクを /mnt/data にマウントする設定が完了しました。

また、このfstabという機能をWSL上で有効にするには /etc/wsl.conf で設定する必要があります。僕は以下の記事を読みながらやりました 👇

https://qiita.com/boss_ape/items/6ba3a846e45b2ebe24c9#%E3%83%89%E3%83%A9%E3%82%A4%E3%83%96%E3%81%AE%E8%87%AA%E5%8B%95%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E8%A8%AD%E5%AE%9A

WSLを再起動してみます。

管理者権限 PowerShell
PS C:\Windows\system32> wsl --shutdown
PS C:\Windows\system32> wsl --mount \\.\PHYSICALDRIVE3 --bare
この操作を正しく終了しました。

④ 完了!

image.png
無事、8TBのディスクが /mnt/data にマウントできています。

ちなみに新しいハードディスクはext4でフォーマットしちゃったのでWindowsからは直接読めませんが、Windows11だとエクスプローラーからネットワークドライブとしてそのままWSLの中身を見に行けるので、/mnt/data を見にいけば確認できます。便利。

④ 写真を保存するディレクトリを作成

.envに書いたmnt/data/immich_photo_library のディレクトリを作成します。

$ cd /mnt/data
/mnt/data$ mkdir immich_photo_library

3. 起動

Docker Composeなのでコマンドひとつで起動できます。

~/immich-app$ docker compose up -d

コンテナが4つ立ち上がったら成功です。ローカルのimmich_serverコンテナにアクセスするとこんな感じの画面が出るので、管理者メールアドレスとパスワードを入力し、セットアップを完了させましょう。
image.png
ようこそ!

4. リバースプロキシのセットアップ

ローカルに立ち上げたWebサーバーをネットに公開して、家の外からでもアクセスできるようにします。

達成したいこと

  1. 外からアクセスしたい
  2. ドメインを使ってアクセスしたい
  3. ポート番号を書かずにアクセスしたい
  4. HTTPSで通信を暗号化したい

① 外からアクセスする

家のルーターに対して、2283番ポートへのアクセスがホストマシンのIPに届くようにポートフォワーディングするように設定すると、http://グローバルIP:2283 (例: http://142.250.206.206:2283) のようなアドレスで外からアクセスできるようになります。

でも、これだとまだ数字ばっかりでアドレスとしてはだいぶカッコ悪いので、IPアドレスを直に指定しないようにしたいですね。

② ドメインでアクセスする

お名前.comやCloudflare Registrarといったドメインサービスでドメインの登録を行い、Aレコードに家のグローバルIPアドレスを指定します。自分の場合は、すでに持っているドメイン(以下、例としてmy-domain.comとする)にphotos.my-domain.com のようなサブドメインを追加して設定しました。

これができたら http://photos.my-domain.com:2283 のようなアドレスで外からアクセスできます。かなり良くなったけど、まだポート番号が見慣れない感じなので、これを書かずにアクセスできるようにしましょう。

③ ポート番号を書かずにアクセスする

HTTPはポート番号を省略すると80番を利用するため、このポートを受け入れてimmichのポートにリダイレクトする仕組み(リバースプロキシ)が欲しいです。これを実現するために今回はnginxを用います。

まず、ルーターのポートフォワーディング設定を2283番から80番に変更します。

このホストマシンではマイクラのマルチプレイサーバーも別のサブドメインを受け入れるように建てているので、photosサブドメインのアドレスからだけをimmichに通したいと思います。この仕組みはnginxのバーチャルホストという機能によって実現できます。

やりたいことはこの辺の記事が似てます(当時参考にしたものが見つからず) 👇

さて、これがうまく行くと http://photos.my-domain.com にアクセスするだけで外からページを見ることができます!かなりアドレスがスッキリしてきました。

でも実はこの通信は暗号化がされておらず、悪意のあるユーザーが盗聴できてしまいます。

④ HTTPSで通信を暗号化する

このWebアプリはメールアドレスやパスワードをやりとりするだけでなく、写真というスーパー個人情報をやり取りするため、その通信は誰にも盗聴されたくないです。通信を常時SSLにすることによって安全に外部から通信ができるようになります。

これも当時使った記事が見つからなかったので、同じプロセスを行なっている記事を紹介しておきます 👇

また、HTTPの時は80番ポートをルーターでフォワーディングさせていたので、これを今度はHTTPSのポートである443番に変更します。

これでやっとhttps://photos.my-domain.com で自宅のimmichサーバーへどこからでも安全にアクセスできるようになりました。

記事を読んだりChatGPTに聞いたりしながら以下のように書いて、最終的なnginx confは以下のようになりました。が、まだWebSocketがうまくつながってないです。nginxについてだけまだ何もわからない状態で触ってしまっている(危険)ので、少しでもツッコミどころがある方、ぜひコメントをお待ちしております(T ^ T)

/etc/nginx/conf.d/default.conf
/etc/nginx/conf.d/default.conf
map $http_upgrade $connection_upgrade { 
    default upgrade;
    ''      close;
} 

server {
    server_name photo.my-domain.com;

    # allow large file uploads
    client_max_body_size 50000M;

    # Let's Encrypt用のチャレンジ用ディレクトリ
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        proxy_pass http://localhost:2284;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # set timeout
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
    send_timeout       600s;
    
    # enable websockets: http://nginx.org/en/docs/http/websocket.html
    proxy_http_version 1.1;
    proxy_set_header   Host       $host;
    proxy_set_header   Upgrade    $http_upgrade;
    proxy_set_header   Connection $connection_upgrade;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/photo.my-domain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/photo.my-domain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = photo.my-domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name photo.my-domain.com;
    return 404; # managed by Certbot
}

5. Googleフォトからの移行

Immichの構築が完了した(めでたい)ので、これまでのGoogleフォトにアップロードした写真や動画たちを全てダウンロードしてImmichのライブラリにコピーしてしまいましょう。

自分は1.3TBもアホデカGoogleフォトのライブラリを抱えており、その上Googleはかなり厳しい仕様を強いてきた都合でこの作業にもかなりの苦労を伴ったので、同じいばらの道を向かう者がいそうだったら別の記事として書こうかなと思います。

自分がうまくいくまでの流れ

  1. Google Takeoutを使ってGoogleフォトの全てのコンテンツをエクスポートしてもらう
  2. 50GBのzipのリンクが29個送られてくる
  3. ブラウザでのダウンロードを試みるも、保管期限(約1週間)中に完了できず、断念
  4. 1~3 をもう一度繰り返す
  5. Google Driveの2TBプランを1ヶ月契約 (💸 ¥1,300)
  6. Google DriveにTakeout
  7. rcloneを使ってDriveをUbuntuに接続、ダウンロード実行 (30時間くらい)
  8. immich-goを用いてアップロード(8時間くらい)

immich-goはしかもGoogle Takeoutが提供する謎の写真のメタデータが入ったjsonを解釈し、その情報をいい感じにマージしてimmichにアップロードしてくれたり、アルバムをちゃんと移行してくれたり、とにかくGoogleフォトからimmichへの移行をしっかりサポートしてくれるツールです。Zipを解凍せずにそのままアップロードができるので便利でした!

6. 完成!

くぅ~疲れましたw これにて完結です!4

最終的な全体の構成はこんな感じだと思います。
immich (1).jpg

1ヶ月間使ってみて

導入してから紆余曲折がありましたが、安定動作まで持っていくことができたと思います。導入後1ヶ月経った今の感想です。

良かったこと

👍 Google Oneへの課金をやめれた
「お金を払っていないと写真を安全に持ってられない」という小さなストレスから解放されました。月1200円くらい節約しながら同様の体験が得られている気がします。HDD代は2年かけてペイできます。

👍 読み書きが超早い!
はやい!とてつもない量のユーザーのリクエストを処理するGoogleとは異なり、セルフホスティングサービスを使っているのでサーバーリソースを占有することができてとても快適に使えます。特別な回線やPCを用意していないのにも関わらずダウンロードもアップロードも安定したパフォーマンスが出ていてすごい。

動画プレビューの読み込みにGoogleフォト(右)は3秒ほど、Immich(右)はほぼクリックと同時

動画プレビューの読み込みがとにかく早い

👍 思ったよりちゃんと動いてる
最近のアップデートでモバイルアプリがバックグラウンドでバックアップができるようになってから、アプリ開いてなくても勝手に最近撮った写真が上がっていることが多くて感動しました。モバイルアプリのバックグラウンド処理って難しいと聞くので。

👍 使うのがワクワクする

  • 家で動いてるサービス、生々しさがあり、アプリを開く度に結構ロマンを感じます
  • 動かなくなってるのを直すのすら楽しい
  • 共有アルバムを作ってアップロードしてもらうのは当然楽しい

👍 嫌なところがあれば直せばいい
OSSプロジェクトであることの最も尊いメリットですね。

悪かったこと

👎 自然言語での写真検索にだいぶクセがある

  • GoogleフォトのOCRとか物体検知はやっぱりかなり優れていました
  • 目的の写真を探すのが難しいことがある

👎 顔認識がどうしても比較的甘い

  • これもGoogleフォトほど頼りきれません
  • Googleフォトは同じ文脈で撮られた写真に映る人の後ろ姿も人物としてタグ付けするので本当にすごかったです
  • こういう時、やろうと思えば自力でチューニングできるのもセルフホスティングのいいところですね

👎 モバイルアプリがやや不安定

  • 起動直後、差分確認中とかにカクつく

発見

💡 業務用のHDDってうるさい

  • immichのタイムラインをスクロールする度にゴゴ〜ッって鳴って面白いです
  • というか、家庭用は静かに動くように設計されており、えらい

注意したいこと

⚠️ アップデートを慎重にやる
破壊的変更が頻繁にあるっぽいので、確認して必要があればちゃんと言う通りにマイグレーションをしてからアップデートするようにしましょう。GitHubでやばいリリースノートが検索するのが便利そう。

⚠️ Volumeを消さない
適当にいじってて、何も考えずにデータベースのVolumeを消すとデータベースが消えました(当たり前)写真ライブラリのパス、ユーザー情報、写真についての情報の全てが消えて使えなくなるのでVolumeを消さないようにしましょう。
DBのバックアップ機能に完全に助けられました、本当にありがとうImmich...)

⚠️ バックアップをもっと冗長に
お金に余裕ができたら、外部ストレージかクラウドにライブラリ全体のバックアップを取るようにしたいです。安心してスマホの写真を消せるようになります。immichは3-2-1ルールに従ったバックアップを推奨しているので、いずれは別ディスク、オフサイトにバックアップを取るようにしたいなと思います。

おわりに

今回はGoogleフォトのセルフホスティングソリューション、Immichの構築を頑張ったらかなり楽しくなった、という話でした。みなさんの写真管理のファイナルアンサーや、改善点などあればぜひコメントもらえると嬉しいです。

楽しいOSSセルフホスティングライフを!

ではまた。

  1. Requirements | Immich

  2. Linux ディスクを WSL 2 にマウントする

  3. くぅ〜疲れましたw とは【ピクシブ百科事典】

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?