(なぐり書き、後から見たらわけわからんかも)ホームページ（死語）を１０年ぶりにリニューアルした話

# きっかけ

もう20年も前だろうか。あの頃はみんなホームページというものを持っていた。htmlを手打ちする者もいれば、ガッツリCGIな人もいて、中身は日記だったり（ああ、出始めた頃はブログといったな）、研究成果だったり、お気持ちの表明だったり、それはそれはいろんなページがあったものだ。
かくゆうワイ将も、固定IPとドメインとSSL証明書をとって20年ぐらいサイトを運営（というほど大層なものではないが）してきた。

だが時代は移り変わり、個人のお心を述べたり、日記を公開したり、日常のTipsを語ったり、そういう何かを発信するのはSNSが肩代わりするようになってしまった。うーん、こういう時代にわざわざ自前でサイトを持つ意味はあるのだろうか。

- 普段のお気持ち表明はTwitterでええやろ(フォロワーの集まりで形成されたコミュニティで発信する、という意味で)
- 技術的な発信はやっぱりQiitaだねガハハハ
- だがたまに動画や音声の配信をしたい、ンまァそういうサービスもあるが、24時間公開できるサイトは少ない。たとえば自宅の庭にカメラしかけて動画配信するには向かない。
- たまーに、バンド活動でファイルをやりとりする事があるけど、なんとなーくGoogle DriveやDropbox以外でやりたい。んまぁGoogle Driveの15GBを使い切るのはいつになるかわからないが、なんとなーく情報は自前で持ちたい。
- WordPressは触ってみたけど、カスタマイズの幅が多すぎるというか、そこまでの機能はいらないんだよなぁ。

などなど自分の欲する要件を色々考えた結果、結局のところ自分のサイトの立ち位置としては、色んなSNSから自分の情報をとってきて表示する**名刺的なもので良いのではないか**と思い、今回の10年ぶりの大幅リニューアルとなった。

# 基本的な仕組みの変更

これまでのサイトはだいたいこんな感じだった。

![IMG20211103192039a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111803/f3b943fe-e87c-9423-b713-4af1c4cce33f.jpeg)


cgiはrubyで組んで、テンプレートにはerbを使ってた。コンテンツ(ブログ)のデータは日付別に区切られたフォルダをクロールして最新のデータを表示するようにしてた。ブログの元データは投稿用メールアカウントにメールが届くとスクリプトを起動してメール本文からhtmlを生成して、日付別に区切られたフォルダに格納するようにしていた。今じゃあんまり推奨されない方式だけども、昔はよくそういう「メールが届いたらコマンド実行」という機能を使っていた。メールとhtmlの変換やページ内の部品の生成には自前のmarkdown to htmlなエンジンを使っていた。

今回はこれをリニューアルして、このようにした。

![IMG20211103191439a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111803/7306b48b-d19d-3e82-0bea-ded8bce6386a.jpeg)

cgiはpythonに切り替え。まず、メインのコンテンツはTwitter/Qiita/NoteのAPIで取ってきてタイムスタンプでソートして表示することにした。QiitaやNoteはコンテンツがでかいので、タイトルと本文の最初だけとってくる。メールによる更新は今回廃止（他サイトから取ってくるからいらないよね）、ページ内のコンテンツの生成は自前のmarkdownをやめてyamlで部品を読み込んで、jinja2でテンプレートに突っ込む方式になった。

今流行りのクライアントサイドを重くして、Web APIだのGraphQLだのクライアントサイドのjavascriptで全部済ます、というアプローチもあったが、うーん、自分にはちょっと思い浮かばなかった。古い人間なのでコードが外から参照されるのに抵抗があるのかもしれない。あと、SSR極めるなら、cgiにする必要もなかったかもしれない。

# ansibleによる準備

今回webサーバをapacheからnginxに切り替えた、理由はなんとなく。ンまァワイのような弱小サークルじゃぁC10K問題なんて夢のような話だけども。

とりあえず、インストール〜コンフィグファイル配信〜コンテンツ配信までをansibleで自動化してみた。まぁWebサーバは一台で運用してるから、自動化の恩恵はあまりないけども、たとえばWebサーバをRaspberry Piに移動するとか、k8sに移動するとか、そういう場面ではこれから使えるかなぁ、というアレ。

```install.yml
- hosts: web-servers
  become: yes
  become_user: root
  become_method: sudo
  tasks:
    - name: Install nginx
      apt:
        name:
          - nginx-full
          - fcgiwrap
        state: present

    - name: Install python library for CGI
      apt:
        name:
          - python3-jinja2
          - python3-yaml
          - python3-orderedattrdict
          - python3-python3-requests-oauthlib
        state: present
```

```update-conf.yml
- hosts: web-servers
  become: yes
  become_user: root
  become_method: sudo
  tasks:
    - name: copy config to /etc
      ansible.builtin.copy:
        src: "etc/nginx/"
        dest: "/etc/nginx/"

    - name: create symbolic lynk
      file:
        src: /etc/nginx/sites-available/{{ item  }}
        dest: /etc/nginx/sites-enabled/{{ item  }}
        state: link
        force: yes
      loop:
        - default
        - fugumaniacs.com

    - name: restart-services
      service: name=nginx state=restarted enabled=yes
```

```update-contents.yml

- hosts: web-servers
  become: yes
  become_user: root
  become_method: sudo
  tasks:
    - name: update contents(do rsync)
      synchronize:
        src: "var/www/"
        dest: "/var/www/"
        delete: yes

    - name: change file owner to www-data
      file:
        path: "/var/www/fugumaniacs.com/"
        state: directory
        group: www-data
        owner: www-data
        recurse: yes

    - name: enable executable index.cgi
      file:
        path: "/var/www/fugumaniacs.com/html/index.cgi"
        state: file
        owner: www-data
        mode: "0544"
      #
```

ちなみにansibleでsyncronizeを使うには、操作対象のサーバにansible操作ユーザがパスなしでsudoできなければいけないらしいので/etc/sudoersをちょこちょこっと書き換えないといけない。

```sudoconf.yml
    - name: enable ansible-remoteuser to  nopass sudo.
      lineinfile:
        path: /etc/sudoers
        state: present
        regexp: '^{{ansible_user}}\s'
        line: "{{ansible_user}} ALL=(ALL) NOPASSWD:ALL"
```

# ページの作成

ページの生成はjinja2を使って実装とコンテンツ部分を分けた。

たとえば以下のようなコンテンツ本体とテンプレートを用意しておいて

```pageparam.yml
title: "フグタシステムズ"
subtitle: "なんか色々とアレな人のステータスモニタ 2021-"
```

```template.html.j2
  
  ...
  (中略)
  ...
    
    <div id="block-top">
      <div> <a href="/" id="icon"> <img src="/icons/mecha-fugu-v01-black.png"> </
a><div id="title">{{page.title}}</div></div>
      <div id="subtitle">{{page.subtitle}}</div>
      {{page.top}}
    </div>

  ...
  ...

```

cgi本体でこれを読み込んで処理する。

```index.cgi

(中略)

page_params = {}

(中略：ここでpage_paramsのデフォルト値を読み込む)

with open("ページのパラメータ.yml") as fd:
    p = orderedattrdict.AttrDict(yaml.safe_load(fd))
    page_params.update(p)

(中略)

j2_env = jinja2.Environment(loader=jinja2.FileSystemLoader(PAGE_PARAMS_DIR,encoding='utf8'))
templ = j2_env.get_template('template.html.j2')
html = templ.render(page=page_params)

(中略)

print('Content-Type: text/html')
print('')
print(html)
```

orderedattrticって便利なんだけど、名前なんとかならんか、デフォルトで入ってていい機能だと思うけれども・・・・。

# 過去コンテンツの扱い

過去のコンテンツは、幸いにも日付別のフォルダにindex.htmlを置いてあったので、簡単なスクリプトで各日付のindex.htmlへのindex.htmlを生成して、あとは知らないぜ、となった。

```mkindexhtml.sh

#!/usr/bin/bash

echo '<html lang="ja">  <meta charset="utf-8"/><head>  <link rel="stylesheet" href="/default.css">  <link rel="icon" type="image/x-icon" href="/favicon.ico"></head>
<body>  <ul>
'

ls | grep -v `basename $0` | grep -v index.html | while read line; do echo "<li><
a href='${line}' target='_blank'>${line}</li>"; done

echo '  </ul></body></html> '

```

# icecast2まわりの設定

やったぜ、とりあえず静的コンテンツはできたぜ、となったのだが、つまんねぇので、音声配信、いわゆるインターネットラジオのインスタンスを立ててみることにした。

まずは、icecast2とliquidsoapをインストールする。

```install-icecast2.yml
- hosts: icecast2-servers
  become: yes
  become_user: root
  become_method: sudo
  tasks:
    - name: Install icecast and liquidsoap
      apt:
        name:
          - icecast2
          - liquidsoap
          - psmisc
          - ffmpeg
          - faad
          - flac
        update_cache: yes
        state: present

    ##############################################
    - name: create liquidsoap dirs
      file:
        path: "{{ item }}"
        state: directory
        owner: icecast2
        group: icecast
        mode: 0660
      loop:
        - /var/log/liquidsoap
        - /etc/liquidsoap
        - /var/run/liquidsoap
        - /var/lib/liquidsoap


    - name: upload contents
      synchronize:
        src: "{{ item }}"
        dest: /var/lib/liquidsoap/
        copy_links: yes
        delete: yes
      loop:
        - 2019-aokgf
        - 2019-varisrts
        - 2020-genpei
        - 2021-emoemonoscar
        - playlist.txt

    - name: upload liquidsoap conf
      template:
        src: main.liq.j2
        dest: /etc/liquidsoap/main.liq
        owner: icecast2
        group: icecast
        mode: 0660
        
    - name: upload liquidsoap startup
      template:
        src: liquidsoap.service.j2
        dest: /etc/systemd/system/liquidsoap.service
        owner: icecast2
        group: icecast
        mode: 0660

    - name: do daemon-reload
      command:
        cmd: "systemctl daemon-reload"

    - service: name=icecast2 enabled=yes state=stopped
    - service: name=icecast2 enabled=yes state=started

    ##############################################

    - name: apply icecast2 confs
      template:
        src: icecast.xml.j2
        dest: /etc/icecast2/icecast.xml
        owner: icecast2
        group: icecast
        mode: 0660

    - service: name=icecast2 enabled=yes state=restarted
```

liquidsoapはデフォルトでサービスとして起動しないので、以下のファイルを作ってsystemdの管理下にする。一度作ったら`systemctl daemon-reload`しないと反映されないので注意。

```/etc/systemd/liquidsoap.service
[Unit]
Description=Liquidsoap daemon
Documentation=http://liquidsoap.fm/
After=network.target

[Service]
Type=simple
User=root
Group=liquidsoap
PIDFile=/run/liquidsoap/liquidsoap.pid
ExecStart=/usr/bin/liquidsoap /etc/liquidsoap/main.liq
ExecStop=/usr/bin/killall liquidsoap

[Install]
WantedBy=multi-user.target
```

ちなliquidsoapのコンフィグファイルがね、クセありすぎじゃぁと思った。

```/etc/liquidsoap/main.liq

set("init.allow_root",true)
set("log.file",true)
set("log.file.path","/var/log/liquidsoap/liquidsoap.log")
set("log.file.append",false)
set("log.level",0)
set("init.daemon.pidfile.path", "/run/liquidsoap/liquidsoap.pid")

playlist(mode="normal","/var/lib/liquidsoap/playlist.txt")
my_music = playlist("/var/lib/liquidsoap/playlist.txt")

my_stream = fallback(
  [
    request.queue(),
    switch([
      ({0h-24h}, my_music)
    ])
  ]
)


output.icecast(
  %mp3( bitrate=24, samplerate=22050,  id3v2=true),

  host="localhost",
  port=8000,
  mount="/stream",
  password = "んーーなにかパスワード的なものソースの何かアレ",
  name="fugumaniacs icecast",
  description = "game music from fugumaniacs",
  fallible=true,

  my_stream
)

```

liquidsoapは%mp3のセクションに不要なオプションを設定すると起動しなかったりして本当に混乱した。エラーログにナニが悪いんだか全然書いてねぇとかひでぇな。

ほいでもって、icecast2のサーバが経ったら、Webサーバのnginxにリバースプロキシの設定して、Webサーバにicecast2サーバの配信をひっぱってくるように設定する。ここもansibleのtemplate使っとるけども、とりあえず一部だけ：

```/etc/nginx/site-available/サイト名

(中略)
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $host;
  proxy_redirect off;
  proxy_max_temp_file_size 0;
  location /app/icecast2/stream {
    rewrite /api/icecast2/stream01  /stream permanent;
    proxy_pass http://ローカルネットにあるicecast2サーバ:8000/stream;
  }
  
(中略)

```

liquidsoapはv4lからの配信もOKらしいので、そのうち庭の動画でも配信するようにしようかと思う。

# できあがり

できあがった結果がこれだ。

https://fugumaniacs.com

# 感想

今回やってみて思うのは、ンまァわかんない事はGoogle様に聞けばだいたいわかる、というのと、今はライブラリとか方法論が確立されてて、簡単なテンプレート適用だけだったら簡単にできるな、という事でした。なんでDjango使わなかったの？と言われると「知らんかったから」としか言えないが、ルーティングするほどたくさんコンテンツやモードがあるわけじゃないので、デカいフレームワークはいらないかな、っていう。

ンマーセキュリティ大丈夫か？とかそういう問題はあるんだが、どうするかなー、cgiやめてSSR極めるっつーのはあるかもね。

# おまけ

昔ながらのインフラの仕事がなくなりつつあるので、誰か仕事くださいっっつ。