転んでもユーザーが泣かないMisskeyのダウンページの作り方

個人で雑に運用してるとアプデしくじったりしてMisskeyは落ちます
これをそのまま放置しているとユーザーは泣きます。

ここでは典型的なステータスページへの誘導方法と、
付け加えて簡易チャットページを作成してユーザーが泣かないで済むようにしましょう。


## 基礎編 落ちたらリダイレクトするようにする

まず落ちるとはどういうことか、どのような対処ができるかから考えていきましょう。

まず、通常ユーザーが見ているMisskeyのサーバー構成はおそらくこうなっています

```
# Misskey requires a reverse proxy to support HTTPS connections.
#
#                 +----- https://example.tld/ ------------+
#   +------+      |+-------------+      +----------------+|
#   | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
#   +------+      |+-------------+      +----------------+|
#                 +---------------------------------------+
```

このうちMisskey(3000)が落ちることがあるとして、前段のProxy(443)が落ちていない可能性があります。
このケースは結構あるので、これをケアする方針でいきましょう。


前段のProxyにはnginxを使っています。

```nginx
    # Proxy to Node(index)
    location = / {
        proxy_pass http://*****;

        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_redirect off;


        # For WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Cache settings
        proxy_cache cache_systems;
        proxy_cache_lock on;
        proxy_cache_use_stale updating;
        proxy_force_ranges on;
        add_header X-Cache $upstream_cache_status;

        #handle error
        error_page 500 502 503 504 https://down.misskey.systems/;
    }

    # Proxy to App
    location / {
        proxy_pass http://**********;

        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_redirect off;


        # For WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Cache settings
        proxy_cache cache_systems;
        proxy_cache_lock on;
        proxy_cache_use_stale updating;
        proxy_force_ranges on;
        add_header X-Cache $upstream_cache_status;

    }

```

重要な点を解説していきます。

まずルーティングですが `location = / ` と  `location / ` という極めて似た記述が登場しますが別物です。
`location = / ` はドメイン直下、 indexにあたるhtmlを返すロケーション（完全一致）です。
`location /` はそのままです。

これに ドメイン直下の方にのみステータスコードが500(Internal Server Error), 502 (Bad GateWay), 503(Server Unavailable), 504(Gateway Timeout)のときだけ好きなページにリダイレクトするようにしています。
```nginx
        #handle error
        error_page 500 502 503 504 https://down.misskey.systems/;
```

ステータスページ自体は似たようなサービスが山ほどあるので何を使ってもよいです

筆者はBetterStackを使っていますがよくわかりません。
https://betterstack.com/

### 転んでも泣かないようにする
さて、ステータスページを表示できるようになりました！やったね！
でもみんなサービスが使えない間は悲しいです。お話ができない。

ということで、ダウンページに簡易チャットを設けるようにしました。

今回の要件は以下のようなものです。
- ログイン不要で書き込めるようにする
- リアルタイムで見れなくてもいいんじゃね
- 機能は最低限でいいや


そして出来上がったのがこちらです

![スクリーンショット 2024-07-15 23.44.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/d2fca00a-47ea-2cb2-dc2c-1f822ece441a.png)


```vue
<template>
  <div
    class="app"
    style="display: flex; flex-direction: column; overflow: hidden"
  >
    <div style="display: flex">
      <div>
        <h1>Mistems is down</h1>

        <a href="https://misskey.systems/"
          >みすてむずは直ってるに違いないときに押すリンク</a
        >
      </div>
      <div style="overflow-y: scroll; width: 100%; height: 200px">
        <form @submit.prevent="submit">
          <input type="text" v-model="text" /><button>おくる</button>
        </form>
        <ul>
          <li v-for="chat in chats" :key="chat.id">
            {{
              formatDistanceToNow(chat.created_at, {
                includeSeconds: true,
                locale: ja(),
              })
            }}
            {{ chat.message }}
          </li>
        </ul>
      </div>
    </div>
    <iframe
      src="https://status.misskey.systems/"
      width="100%"
      height="100%"
    ></iframe>
  </div>
</template>

<style>
html,
body,
#__nuxt,
.app {
  height: 100%;
}
</style>
<script lang="ts">
import { createClient } from "@supabase/supabase-js";
import { formatDistanceToNow } from "date-fns";
import { ja } from "date-fns/locale/ja";

const supabaseUrl = "https://*************************.supabase.co";
const supabase = createClient(
  supabaseUrl,
  "************************************************",
);

// Create a function to handle inserts
export default defineNuxtComponent({
  data() {
    return {
      chats: [] as { id: number; message: string; created_at: Date }[],
      text: "",
    };
  },
  mounted() {
    this.getChat();
    // Listen to inserts
    setInterval(async () => {
      this.getChat();
    }, 5000);
  },
  methods: {
    ja() {
      return ja;
    },
    formatDistanceToNow,
    async getChat() {
      let { data: todos, error } = await supabase
        .from("todos")
        .select("*")
        .order("id", { ascending: false })
        .range(0, 9);

      if (!todos) return;
      this.chats = todos;
    },
    async submit() {
      const { data, error } = await supabase
        .from("todos")
        .insert([{ message: this.text }]);
      if (error) console.error(error);
      this.text = "";
      this.getChat();
    },
  },
});
</script>

```

getChatをsetTimeIntervalで実行して、submitできるだけです。
永続化用にsupabaseを使っていますがなんでもよいです。
iframeを使ってstatus pageもいい感じに見えるようにしておきます。
あとはVercelとかに適当にデプロイしていい感じに動いてたらめっちゃええ感じですね。

それでは楽しいみすきーライフを。


この文章は加筆・訂正リクエストやコメント歓迎です。