CentOS 7: 2つのネットワークに繋ぐ

web サーバにしている PC を2つのネットワークに繋ぐようにしたら問題が発生した。よく理解できてないけど、なんとか解決したので書く。

状況整理

まずは、2つ目のネットワークに繋ぐ前の状況を整理しておく。

ウチには2つのインターネット回線があって、それぞれに web サーバを公開している。1つは光回線で1つ目の web サーバのほか、いくつかある PC は全てこのネットワークに繋がっている。もう1つは去年の12月に導入した WiMAX で、2つ目の web サーバだけが有線で繋がっている(クレードルを介している)。2つのネットワークはウチの中では独立していて繋がっていない。

なので、2つ目の web サーバ――面倒だからホスト名で呼ぼう、bigswifty だ――を操作したい時には直接操作するか、インターネット経由で ssh 接続する必要があった。まぁ、これで用は足りてたんだけど、インターネット経由だとホスト名が長いのが微妙だった。

ついでに先に書いておこう、bigswifty の OS は CentOS 7.6 だ。

今日突然思いついた

ほぼ1年間、上記の状態で使ってたんだけど、今日突然思いついた。bigswifty は有線で WiMAXに繋がってるけど、WiFi もあるじゃないか。WiFi で光回線側のネットワークに繋げば、いつもメインで使ってる apostrophe からインターネットを経由しないで ssh できるようになるはずだ。

というわけで、さっそく bigswifty に直接ログインして、WiFi を有効にし、光回線側のネットワークに繋いだ。これで apostrophe から bigswifty に、インターネットを経由しなくても繋がるようになった。やったぜ!

問題発覚

ところがしばらくして問題が発覚した。bigswifty が web サーバとして機能してないのだ。具体的に言うと、インターネット経由で bigswifty からファイルをダウンロードしようとしても Nginx のエラーページが表示されて、ダウンロードできない。

試しに WiFi を無効にしてみると、ちゃんとファイルをダンロードできるようになった。

よくわからないけど解決編

いろいろググった結果、↓このページを見つけた。

 cf. 複数NICでのデフォルトゲートウェイ設定方法 – maruko2 note

ルータの DHCP 機能にまかせていたルーティングを手動で設定してやればいいようだ。これをスタティックルートというらしい。

じゃ、手順を始めよう。まずはデフォルトゲートウェイを設定する。/etc/sysconfig/network ファイルにつぎのように記述する。

GATEWAY=192.168.100.1

デフォルトゲートウェイは WiMAX 端末だ。つぎに、光回線側のネットワークと通信するための設定を書く。WiFi のインターフェイス名は wlo1 なので、/etc/sysconfig/networs-scripts ディレクトリの下に route-wlo1 というファイルを作って、つぎのようにする。

192.168.1.0/24 via 192.168.1.1

192.168.1.0/24(光回線のネットワークのこと)にアクセスするには 192.168.1.1(光回線側のルータ)を経由しろ、っていうことだと思う。

そして network を再起動。

[takatoh@bigswifty ~]$ sudo systemctl restart network

これで作業は終わり。

はたして、無事、光回線側からも ssh で繋がるし、インターネットからもファイルをダウンロードできるようになった。

[追記:11/25]

現在11月25日午前4時過ぎ。インターネット経由でファイルをダウンロードしようとしたら、Nginx のエラーページが表示されてダウンロードできなかった。

ごめん、上に書いた方法じゃダメだったみたいだ。いったんはちゃんと繋がった(ホントだよ!)から解決できたものだと思ったんだけど……

ローカルネットワーク(光回線側)から ssh で繋ぐことはできる。ping を試してみるとつぎの通り、応答が返ってくる。

takatoh@apostrophe $ ping bigswifty
PING bigswifty (192.168.1.13) 56(84) bytes of data.
64 bytes from bigswifty (192.168.1.13): icmp_seq=1 ttl=64 time=27.2 ms
64 bytes from bigswifty (192.168.1.13): icmp_seq=2 ttl=64 time=3.87 ms
64 bytes from bigswifty (192.168.1.13): icmp_seq=3 ttl=64 time=3.96 ms
64 bytes from bigswifty (192.168.1.13): icmp_seq=4 ttl=64 time=5.47 ms
64 bytes from bigswifty (192.168.1.13): icmp_seq=5 ttl=64 time=5.03 ms
64 bytes from bigswifty (192.168.1.13): icmp_seq=6 ttl=64 time=9.35 ms
^C
--- bigswifty ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5007ms
rtt min/avg/max/mdev = 3.875/9.153/27.218/8.282 ms

だけど、インターネット経由だと応答が返ってこない。

takatoh@apostrophe $ ping lathercraft-storage3.mydns.jp
PING lathercraft-storage3.mydns.jp (49.135.111.90) 56(84) bytes of data.
^C
--- lathercraft-storage3.mydns.jp ping statistics ---
37 packets transmitted, 0 received, 100% packet loss, time 35999ms

lathercraft-storage3.mydns.jp っていうのは、bigswifty をインターネットに公開した時のホスト名だ。DDNS の mydns を使ってる。

ここでちょっと思いついて、lathercraft-storage3.mydns.jp が現在どんなアドレスを使ってるのか確かめてみた。このサイトで確認できる。すると、49.135.102.186 だった。これは lathercraft-storage3.mydns.jp に ping を打った時に応答したアドレスと違う。

てことは、問題は DNS だ。mydns には cron にスクリプトを登録して定期的にアドレスの通知をしてるんだけど、手動でやってみた結果がこれ:

<head>
<title>Free Dynamic DNS (DDNS) for Home Server and VPS etc  | MyDNS.JP</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<LINK href="./site.css" rel=stylesheet type=text/css>

</head>
<BODY BGCOLOR="#FFFFFF"
      TEXT="#304040"
      leftmargin="0" topmargin="0" marginwidth="0" marginheight="0"
>
Login and IP address notify OK.<BR>
login_status = 1.<BR>
<BR>
<DT>MASTERID :</DT><DD>mydns882612</DD>
<DT>REMOTE ADDRESS:</DT><DD>240b:12:1461:6100:c480:657b:ef6b:892d</DD>
<DT>ACCESS DAYTIME:</DT><DD>2019/11/24 19:46:44 UTC</DD>
<DT>SERVER ADDRESS:</DT><DD>2604:180:3:ab6::245c</DD>
<BR>

</body>
</html>

REMOTE ADDRESS が通知したアドレスなんだけど、IPv6 のアドレスになっている。問題はこれかぁ。

さて、どうしよう?

カテゴリー: CentOS | コメントする

Nginx + Unicorn on CentOS 8 with SELinux

前のエントリで web アプリを Unicorn で動かすことに成功したので、今度は Nginx と連携する。

その前に unicorn.conf をちょっと修正して、ポートじゃなくて UNIX ソケットを利用するようにする。

#listen "9000"
listen "/run/lcstorage/unicorn.sock"
worker_processes 2
working_directory "/home/lcstorage/lcstorage"
pid "/home/lcstorage/lcstorage/unicorn.pid"
stdout_path "/home/lcstorage/lcstorage/unicorn.log"
stderr_path "/home/lcstorage/lcstorage/unicorn.log"
preload_app true

そしてソケットファイルを置くディレクトリを作る。

[lcstorage@rollo lcstorage]$ sudo mkdir /run/lcstorage
[lcstorage@rollo lcstorage]$ sudo chown lcstorage:lcstorage /run/lcstorage

Unicorn の準備はこれで終わり。起動しておく。

[lcstorage@rollo lcstorage]$ bundle exec unicorn -c unicorn.conf -E production

さて、つぎは Nginx の設定だ。storage1 というホスト名でアクセスできるよう、/etc/nginx/conf.d の下に storage1.conf という名前で設定ファイルを作る。

upstream lcstorage {
    server unix:/run/lcstorage/unicorn.sock;
    #server 127.0.0.1:9000;
}

server {
    # port
    listen      80;

    # server name
    server_name storage1;

    # document root
    #root        /home/lcstorage/lcstorage;

    # index
    #index       index.php index.html index.htm;

    client_max_body_size    4G;

    # log files
    access_log /var/log/nginx/storage1/access.log main;
    error_log  /var/log/nginx/storage1/error.log  warn;

    keepalive_timeout     60;
    proxy_connect_timeout 60;
    proxy_read_timeout    60;
    proxy_send_timeout    60;

    location / {
        #root       /home/lcstorage/lcstorage;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://lcstorage;
    }
}

ログを書き込むディレクトリを忘れずに作っておく。

これで準備はできたはずだ。Nginx を再起動しよう。

[takatoh@rollo conf.d]$ sudo systemctl restart nginx

で、別マシンから http://storage1/ にアクセスしてみると、結果はこうだった。

502 Bad Gateway

あー、これはあれだ、SELinux のせいだ。試しに SELinux を Permissive モードにしてみる。

[takatoh@rollo ~]$ sudo setenforce 0
[takatoh@rollo ~]$ getenforce
Permissive

この状態でもう一度アクセスしていると、ちゃんと File not found. と表示された。なんどもいうけど、返すファイルがないのでこれが正しい動作だ。

つまり、SELinux をどうにかしてやらないといけないわけだ。だけどググってみると、解決策として出てくるのは SELinux を無効にしろ、っていうのばっかり。ちゃんと使い方を説明しているページはほとんど無い。そんな中で参考になったのが以下のページ。

 cf. 【ゼロから始める】初期設定のみのLinuxサーバーに既存のRailsアプリとWebサーバーを導入する – Takeshi’s Blog

と、そこからリンクされているページ。

 cf. RedmineをCentOS 7上で動かすーUnicornとNginx編 – ソフトウェアエンジニアリング

Nginx のエラーログを調べると:

2019/11/23 11:33:23 [crit] 2526#0: *1 connect() to unix:/run/lcstorage/unicorn.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.1.11, server: storage1, request: "GET / HTTP/1.1", upstream: "http://unix:/run/lcstorage/unicorn.sock:/", host: "storage1"

となっていて、ソケットファイルにアクセスできないようだ。じゃあ、ソケットファイルのパーミッションはどうなってるかというと:

[takatoh@rollo ~]$ ls -lZ /run/lcstorage
合計 0
srwxrwxrwx. 1 lcstorage lcstorage unconfined_u:object_r:var_run_t:s0 0 11月 23 11:42 unicorn.sock

Z は SELinux のアクセス権限を表示するオプション。うん、よくわからん。SELinux が原因かどうかは /var/log/audit/audit.log を調べるとわかるらしい。再度 SELinux を有効にしてアクセスしてみてから:

[takatoh@rollo ~]$ sudo cat /var/log/audit/audit.log | grep nginx | tail
type=SERVICE_STOP msg=audit(1574476367.153:1276): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=nginx comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'UID="root" AUID="unset"
type=SERVICE_START msg=audit(1574476367.207:1277): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=nginx comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'UID="root" AUID="unset"
type=AVC msg=audit(1574476403.391:1281): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
type=SYSCALL msg=audit(1574476403.391:1281): arch=c000003e syscall=42 success=no exit=-13 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574476651.650:1290): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1
type=SYSCALL msg=audit(1574476651.650:1290): arch=c000003e syscall=42 success=yes exit=0 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574477463.658:1309): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1
type=SYSCALL msg=audit(1574477463.658:1309): arch=c000003e syscall=42 success=yes exit=0 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"
type=AVC msg=audit(1574477957.417:1347): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0
type=SYSCALL msg=audit(1574477957.417:1347): arch=c000003e syscall=42 success=no exit=-13 a0=e a1=561f05479e28 a2=6e a3=7ffe9aa20e0c items=0 ppid=2522 pid=2526 auid=4294967295 uid=977 gid=975 euid=977 suid=977 fsuid=977 egid=975 sgid=975 fsgid=975 tty=(none) ses=4294967295 comm="nginx" exe="/usr/sbin/nginx" subj=system_u:system_r:httpd_t:s0 key=(null)ARCH=x86_64 SYSCALL=connect AUID="unset" UID="nginx" GID="nginx" EUID="nginx" SUID="nginx" FSUID="nginx" EGID="nginx" SGID="nginx" FSGID="nginx"

さぱーりわからん。けど、どうやら↓ここらしい。

type=AVC msg=audit(1574477957.417:1347): avc:  denied  { connectto } for  pid=2526 comm="nginx" path="/run/lcstorage/unicorn.sock" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0

nginx が /run/lcstorage/unicorn.sock にアクセスしようとして denied になってるようだ。つまりここを何とかすればいいわけ。

SELinux の上のアクセスを許可する設定は、/var/log/audit/audit.log ファイルから audit2allow コマンドで作れるようだ。まず確認。

[takatoh@rollo ~]$ sudo grep nginx /var/log/audit/audit.log | audit2allow

#============= httpd_t ==============

#!!!! This avc can be allowed using the boolean 'httpd_can_network_connect'
allow httpd_t glance_port_t:tcp_socket name_connect;

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, httpd_graceful_shutdown, httpd_can_network_relay, nis_enabled
allow httpd_t http_port_t:tcp_socket name_connect;
allow httpd_t unconfined_t:unix_stream_socket connectto;

設定用のファイルを作るにはこうする。

[takatoh@rollo ~]$ sudo grep nginx /var/log/audit/audit.log | audit2allow -M nginx
[sudo] takatoh のパスワード:
******************* 重要 ********************
このポリシーパッケージを有効にするには、以下を実行して下さい:

semodule -i nginx.pp

指示にあるようにして、SELinux に設定する。

[takatoh@rollo ~]$ sudo semodule -i nginx.pp

さてこれでどうだろう。はたして、別のマシンからブラウザでアクセスしてみると、ちゃんと File not found. って表示された。なんどもいうけどこれが(ry。

つぎは web アプリをサービスとして登録する。/etc/systemd/system ディレクトリの下に lcstorage.service ファイルを作る。

[Unit]
Description=LatherCraft storage service
After=network.target

[Service]
Type=forking
PIDFile=/home/lcstorage/lcstorage/unicorn.pid
ExecStart=/usr/local/bin/bundle exec unicorn -c unicorn.conf -E production -D
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -QUIT $MAINPID
WorkingDirectory=/home/lcstorage/lcstorage
User=lcstorage
Group=lcstorage

[Install]
WantedBy=multi-user.target

これで起動してみる。

[takatoh@rollo system]$ sudo systemctl start lcstorage

あれ?おかしい。プロンプトが戻ってこない。ほかのターミナルからステータスを見てみると:

[lcstorage@rollo lcstorage]$ systemctl status lcstorage
● lcstorage.service - LatherCraft storage service
   Loaded: loaded (/etc/systemd/system/lcstorage.service; disabled; vendor pres>
   Active: activating (start) since Sat 2019-11-23 12:57:57 JST; 1min 14s ago
  Process: 3931 ExecStart=/usr/local/bin/bundle exec unicorn -c unicorn.conf -E production -D (code=exited, status=0/SUCCESS)
    Tasks: 6 (limit: 26213)
   Memory: 31.4M
   CGroup: /system.slice/lcstorage.service
           ├─3936 unicorn master -c unicorn.conf -E production -D  production -D
           ├─3939 unicorn worker[0] -c unicorn.conf -E production -D  productio>
           └─3942 unicorn worker[1] -c unicorn.conf -E production -D  productio>
11月 23 12:57:57 rollo systemd[1]: Starting LatherCraft storage service…
11月 23 12:57:58 rollo systemd[1]: lcstorage.service: Can't convert PID files /home/lcstorage/lcstorage/unicorn.pid O_PATH file descriptor to proper file descriptor: Permission denied
11月 23 12:57:58 rollo systemd[1]: lcstorage.service: Can't convert PID files /home/lcstorage/lcstorage/unicorn.pid O_PATH file descriptor to proper file descriptor: Permission denied

pid ファイルにアクセスできないっぽい?じゃあ、場所を変えてみようか。/home/lcstorage/lcstorage/unicorn.conf と /etc/systemd/system/lcstorage.service の両方で pid ファイルを作る場所を /run に変えてみた。

でもまだダメ。もとに戻す。もう一度やってみてもこうなる。

[takatoh@rollo system]$ sudo systemctl start lcstorage
Job for lcstorage.service failed because a timeout was exceeded.
See "systemctl status lcstorage.service" and "journalctl -xe" for details.

で、SELinux を無効にしてやってみた。

[takatoh@rollo system]$ sudo setenforce 0
[takatoh@rollo system]$ getenforce
Permissive
[takatoh@rollo system]$ sudo systemctl start lcstorage

あ、これだとすんなりいく。ブラウザからアクセスしてもうまくいった。またお前か、SELinux!

さっきとおなじように /var/log/audit/audit.log を調べてみるとこうなってた。

type=AVC msg=audit(1574483152.115:1720): avc:  denied  { open } for  pid=1 comm="systemd" path="/home/lcstorage/lcstorage/unicorn.pid" dev="dm-2" ino=24382 scontext=system_u:system_r:init_t:s0 tcontext=system_u:object_r:user_home_t:s0 tclass=file permissive=1

やっぱり pid ファイルにアクセスできないみたいだ。ポリシーを作ってやる。

[takatoh@rollo ~]$ sudo grep unicorn /var/log/audit/audit.log | audit2allow -M unicorn
[takatoh@rollo ~]$ sudo semodule -i unicorn.pp

これでどうだろう。

[takatoh@rollo ~]$ sudo setenforce 1
[takatoh@rollo ~]$ getenforce
Enforcing
[takatoh@rollo ~]$ sudo systemctl start lcstorage

お、うまくいったっぽい。ブラウザからアクセスしても大丈夫だった。

最後に。マシンを起動した時に自動起動するようにする。

[takatoh@rollo ~]$ sudo systemctl enable lcstorage
Created symlink /etc/systemd/system/multi-user.target.wants/lcstorage.service → /etc/systemd/system/lcstorage.service.

はぁ、長かった。

カテゴリー: CentOS, Ruby | コメントする

Ruby 製アプリを Unicorn で動かす

昨日に引き続きデプロイの続きをするよ。今日の作業はまず Unicorn でアプリを動かすことだ。

Gemfile に unicorn を書き足して bundle install。

source "https://rubygems.org"

gem 'sinatra'
gem 'httpclient'
gem 'json'
gem 'filestorage'
gem 'unicorn'
[lcstorage@rollo lcstorage]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/………….
Resolving dependencies…
Using bundler 2.0.2
Using filestorage 0.1.0
Using httpclient 2.8.3
Using json 2.2.0
Fetching kgio 2.11.2
Installing kgio 2.11.2 with native extensions
Using mustermann 1.0.3
Using rack 2.0.7
Using rack-protection 2.0.7
Fetching raindrops 0.19.0
Installing raindrops 0.19.0 with native extensions
Using tilt 2.0.10
Using sinatra 2.0.7
Fetching unicorn 5.5.1
Installing unicorn 5.5.1 with native extensions
Bundle complete! 5 Gemfile dependencies, 12 gems now installed.
Bundled gems are installed into ./vendor/bundle

Unicorn の設定ファイル unicorn.conf を書く。

listen "9000"
worker_processes 2
working_directory "/home/lcstorage/lcstorage"
pid "/home/lcstorage/lcstorage/unicorn.pid"
stdout_path "/home/lcstorage/lcstorage/unicorn.log"
stderr_path "/home/lcstorage/lcstorage/unicorn.log"
preload_app true

これでいいはず。試しに起動してみる。

[lcstorage@rollo lcstorage]$ bundle exec unicorn -c unicorn.conf -E production

昨日と同じく、別のコンソールからアクセス。

[takatoh@rollo ~]$ curl http://localhost:9000/
File not found.[takatoh@rollo ~]$ 
[takatoh@rollo ~]$ curl http://localhost:9000/path/to/file
File not found.[takatoh@rollo ~]$

うん、ちゃんと File not found. が返ってきてる。昨日も書いたけど、返すファイルがまだ無いのでこれは正しい動作だ。

さて、あとは Nginx との連携だけど、これはエントリを分ける。というわけでとりあえずここまで。

カテゴリー: CentOS, Ruby | コメントする

CentOS 8 のマシンに Ruby 製 web アプリをデプロイしようとしたら bundle install でコケた

前にやった時もそうだったような気がするんだよなぁ。

詳しい話に入る前に、作業環境を整理しよう。web アプリのデプロイ先は、この間買った新しい マシン rollo (OS は CentOS 8)。これにいつもの作業用メインマシン apostrophe(OS は Ubuntu 16.04 LTS)から ssh で接続して作業してる。rollo 上での作業はアプリの専用ユーザ lcstorage で行った。

まずは、BitBucket から git clone した。

GitHub じゃないのは公開したくないプライベートなプロジェクトだから。オープンにしてもいいプロジェクトは GitHub、そうでないのは BitBucket と使い分けてる。

閑話休題。git clone が終わったので、必要なライブラリをインストールすべく bundle install したらコケた。

[lcstorage@rollo lcstorage]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/………….
Using bundler 2.0.2
Fetching filestorage 0.1.0
Installing filestorage 0.1.0
Fetching httpclient 2.8.3
Installing httpclient 2.8.3
Fetching json 2.2.0
Installing json 2.2.0 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/gems/json-2.2.0/ext/json/ext/generator
/usr/bin/ruby -r ./siteconf20191122-22908-wlblo2.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/share/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/gems/json-2.2.0 for
inspection.
Results logged to
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0/json-2.2.0/gem_make.out

An error occurred while installing json (2.2.0), and Bundler cannot
continue.
Make sure that gem install json -v '2.2.0' --source 'https://rubygems.org/'
succeeds before bundling.

In Gemfile:
  json

どうも json gem が拡張ライブラリで、ビルドできなくてコケてるようだ。いろいろググって試行錯誤した結果、つぎのパッケージを dnf install したらできた。

  • gcc
  • make
  • ruby-devel
  • rpm-build

gcc や make が入ってなかったのか……

ともあれこれでインストールはできた。必要な設定ファイルやらディレクトリやらを作って、試しに起動してみた。

[lcstorage@rollo lcstorage]$ bundle exec rackup
[2019-11-22 22:32:38] INFO  WEBrick 1.4.2
[2019-11-22 22:32:38] INFO  ruby 2.5.3 (2018-10-18) [x86_64-linux]
[2019-11-22 22:32:38] INFO  WEBrick::HTTPServer#start: pid=25617 port=9292

起動は大丈夫のようだ。9292番のポートは開けてないので、別のターミナルから別のユーザで rollo に入ってアクセスしてみた。

[takatoh@rollo ~]$ curl http://localhost:9292/
File not found.[takatoh@rollo ~]$ 
[takatoh@rollo ~]$ curl http://localhost:9292/path/to/file
File not found.[takatoh@rollo ~]$

返すファイルがないので File not found. が帰ってくるのは正しい動作。どうやら大丈夫のようだ。

明日は Nginx を介して外部からアクセスできるようにする予定。

カテゴリー: CentOS, Ruby | コメントする

CentOS 8 に Nginx をインストールしてみた

インストール

dnf install コマンドでインストール。

[takatoh@rollo ~]$ sudo dnf install nginx

有効化とスタート。

[takatoh@rollo ~]$ sudo systemctl enable nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
[takatoh@rollo ~]$ sudo systemctl start nginx

ステータスの確認。

[takatoh@rollo ~]$ systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-11-19 03:01:37 JST; 8min ago
  Process: 10021 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
  Process: 10019 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
  Process: 10017 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
 Main PID: 10022 (nginx)
    Tasks: 5 (limit: 26213)
   Memory: 8.9M
   CGroup: /system.slice/nginx.service
           ├─10022 nginx: master process /usr/sbin/nginx
           ├─10023 nginx: worker process
           ├─10024 nginx: worker process
           ├─10025 nginx: worker process
           └─10026 nginx: worker process
11月 19 03:01:37 rollo systemd[1]: Starting The nginx HTTP and reverse proxy server…
11月 19 03:01:37 rollo nginx[10019]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
11月 19 03:01:37 rollo nginx[10019]: nginx: configuration file /etc/nginx/nginx.conf test is successful
11月 19 03:01:37 rollo systemd[1]: Started The nginx HTTP and reverse proxy server.

OK。ちゃんと起動したようだ。

ファイアウォールの設定

80番ポートを開ける。

[takatoh@rollo ~]$ sudo firewall-cmd --permanent --zone=public --add-service=http
success

firewalld をリロード。

[takatoh@rollo ~]$ sudo firewall-cmd --reload
success

これで OK のはず。ほかのマシンからブラウザでアクセスしてみたら、ちゃんと Nginx のデフォルトページが表示された。

カテゴリー: CentOS, Misc | コメントする

CentOS 8 をインストールしてみた

新しい PC を買ったんだ。これまで Dell か HP だったので、今回は Lenovo にしてみた。ThinkCentre M630e Tiny っていうモデル。てのひらにはちょっとあまるけど、超小型 PC と言っていい大きさだ。

で、せっかくなので最近リリースされたばかりの CentOS 8.0.1905 をインストールしてみた。プレインストールされていた Windows 10 は削除。

インストーラは CentoOS 7 と特段変わらない印象。でもデフォルトのインストールタイプ(っていうんだっけ?)が、「サーバー(GUI使用)」になってた。もちろんこれでOK。ひととおりインストールが済んで再起動すると、ちゃんと CentOS が立ち上がった。いつかの Dell の PC みたいに起動しないなんてことはなかった。いい兆候だ。あと、ホスト名は rollo にした。

日本語入力

インストール時に日本語を選んでいるので、メニューとかは日本語になってるんだけど、そのままでは入力はできないようだ。↓このページが参考になった。

cf. デスクトップ環境 : GNOME デスクトップ インストール – Server World

まずは日本語入力プログラムをインストール。

[takatoh@rollo ~]$ sudo dnf -y install ibus-kkc

CentOS 7 までの yum コマンドじゃなくて dnf っていうコマンドでパッケージをインストールするらしい。

それから、背景画面の適当なところで右クリックして設定ウィンドウをひらいて、 Region & Language に移動する。入力ソースの欄で、日本語(かな漢字)を追加すれば準備は完了。画面右上のアイコンをクリックして日本語(かな漢字)を選べば OK だ。あとは半角/全角キーで入力モードを切り替えられる。今日のこのエントリもそうやって書いている。

Ruby とか Python とか

Python が入ってない、と思ったら python3 コマンドだった。

[takatoh@rollo ~]$ python3 -V
Python 3.6.8

Python はデフォルトが 3 系になったらしい。なら python コマンドでいいだろうになんで python3 なんだ。

Ruby は入ってない。のでインストールした。

[takatoh@rollo ~]$ ruby -v
bash: ruby: コマンドが見つかりませんでした…
コマンド ruby' を提供するためにパッケージ 'ruby' をインストールしますか? [N/y] y
キューで待機中… 
パッケージの一覧をロード中。… 
以下のパッケージはインストールされるべきものです:
ruby-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    An interpreter of object-oriented scripting language
ruby-irb-2.5.3-104.module_el8.0.0+179+565e49e2.noarch    The Interactive Ruby
ruby-libs-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    Libraries necessary to run Ruby
rubygem-bigdecimal-1.3.4-104.module_el8.0.0+179+565e49e2.x86_64    BigDecimal provides arbitrary-precision floating point decimal arithmetic
rubygem-did_you_mean-1.2.0-104.module_el8.0.0+179+565e49e2.noarch    "Did you mean?" experience in Ruby
rubygem-io-console-0.4.6-104.module_el8.0.0+179+565e49e2.x86_64    IO/Console is a simple console utilizing library
rubygem-json-2.1.0-104.module_el8.0.0+179+565e49e2.x86_64    This is a JSON implementation as a Ruby extension in C
rubygem-openssl-2.1.2-104.module_el8.0.0+179+565e49e2.x86_64    OpenSSL provides SSL, TLS and general purpose cryptography
rubygem-psych-3.0.2-104.module_el8.0.0+179+565e49e2.x86_64    A libyaml wrapper for Ruby
rubygem-rdoc-6.0.1-104.module_el8.0.0+179+565e49e2.noarch    A tool to generate HTML and command-line documentation for Ruby projects
rubygems-2.7.6-104.module_el8.0.0+179+565e49e2.noarch    The Ruby standard for packaging ruby libraries
変更したまま継続しますか? [N/y] y
キューで待機中… 
認証を待ち受け中… 
キューで待機中… 
パッケージをダウンロード中… 
データを要求中… 
変更をテスト中… 
パッケージのインストール中… 
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux] 
[takatoh@rollo ~]$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]

2.5.3 がインストールされた。

というところで、今日はここまで。

カテゴリー: CentOS, Python, Ruby | コメントする

Visual Studio Community 2019

Visual Studio Community 2019 をインストールしてみた。

ダウンロードはここから、Community 版をダウンロードした。

 cf. https://visualstudio.microsoft.com/ja/downloads/

ダウンロードしたファイルは、必要なファイルをダウンロードしながらインストールするタイプのものだ。実行すると少したって、コンポーネント(っていうのか?)を選ぶ、ワークロードっていう画面になるので、今回は「.NETデスクトップ開発」を選んでみた。なんかダウンロードするファイルが1.2GBくらいあって、しばらく時間がかかる。

インストールが完了すると、Windows の再起動を促される。指示通り再起動。これで完了(たぶん)。

Windows の再起動後、スタートメニューから Visual Studio 2019 を選んで起動すると、最初はサインインの画面になった。takatoh.m@gmail.com でサインインする。初期化かなんかが行われたあと、作業できるようになる。

「新しいプロジェクトの作成」を選んでみよう。とりあえず「コンソールアプリ」。プロジェクト名を入力して「作成」をクリック。すると、どうやらサンプルとして、いわゆる hello wold プログラムのコードが書かれている画面になる。これがプロジェクトの編集画面らしい。

というわけで、ひとまずは大丈夫のようだ。あと2カ月で年も変わるし、C# でもやってみるかな。

カテゴリー: Windows | コメントする

古いエントリが正常に表示されない

いつからかわからないけど、古いエントリが正常に表示されないようになっていた。も少し正確に言うと、古いエントリのうちの一部だ。さっき気が付いた。

で、調べてみると、どうも Crayon Syntax Hightlighter を使ってるエントリがダメみたいだ。コードをハイライトしてくれるプラグインだけど、今のバージョンの WordPress ではテストされてない、っていうか最近開発が止まっているようだ。それでも少し前まではとりあえず表示はできていたのに、たぶん WordPress のどこかのバージョンアップを境にダメになったんだろう。

Crayon Syntax Hightlighter は対応する言語も多くて重宝してたんだけど(いい代替プラグインはまだ見つかっていない)、エントリ自体が表示されないんじゃ、置き換えなきゃダメだろう。

うへぇ、かなりあるぞ。

カテゴリー: Misc | コメントする

画像をフェードアウト/フェードインで自動で切り替える

うわー!もう今月終わりそう!

というわけで、Scala の学習のほうはここひと月ほど滞っているんだけど、代わりに(?)今日は、HTML と JavaScript でタイトルに書いたようなことをやってみる。

jQuery を使ってスライドショーを実現するライブラリはいろいろあるんだけど、今日はそういうのは使わないでやる。さらには、jQuery 自体も使わないでやる。やるって言ったらやる。

さて、まずは jQuery 利用バージョン。いくつかの Web ページを参考に書いた(というかほぼコピペ)のがこれ。

<!DOCTYPE html>
<html>
  <head>
    <title>Fade images</title>
    <link rel="stylesheet" href="style.css" />
    <script src="../lib/jquery-3.4.1.min.js"></script>
    <script type="text/javascript">
      $(function() {
        const $width = 960;
        const $height = 540;
        const $interval = 3000;
        const $fade_speed = 1000;
        $("#slide ul li").css({"position": "absolute", "overflow": "hidden", "width": $width, "height": $height});
        $("#slide ul li").hide();
        $("#slide ul li:first").addClass("active").show();
        setInterval(function() {
          let $active = $("#slide ul li.active");
          let $next = $active.next("li").length ? $active.next("li") : $("#slide ul li:first");
          $active.fadeOut($fade_speed).removeClass("active");
          $next.fadeIn($fade_speed).addClass("active");
        }, $interval);
      });
    </script>
  </head>
  <body>
    <div id="slide">
      <ul>
        <li><a href=""><img src="img/img1.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img2.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img3.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img4.jpg" width="960" heght="540"/></a></li>
      </ul>
    </div>
  </body>
</html>

これで、3秒ごとにフェードアウト/フェードインしながら画像が切り替わる。画像は4つ用意したけど、最後まで行ったら元に戻る。

つぎ。jQuery なしバージョン。さらにいくつかのページを参考に、試行錯誤した結果がこれ。

<!DOCTYPE html>
<html>
  <head>
    <title>Fade images</title>
    <link rel="stylesheet" href="style.css" />
    <script type="text/javascript">
      function fadeIn(el, speed) {
        el.style.transition = speed;
        el.style.opacity = "1";
      }

      function fadeOut(el, speed) {
        el.style.transition = speed;
        el.style.opacity = "0";
      }

      function slide() {
        const width = "960px";
        const height = "540px";
        const interval = 3000;  // miliseconds
        const fade_speed = "1.0s";
        const slide = document.querySelectorAll("#slide ul li");
        for (let i =0; i < slide.length; i++) {
          slide[i].style.position = "absolute";
          slide[i].style.overflow = "hidden";
          slide[i].style.width = width;
          slide[i].style.height = height;
          slide[i].style.display = "block";
          slide[i].style.opacity = "0";
        }
        const first = slide[0];
        first.classList.add("active");
        first.style.opacity = "1";
        setInterval(function() {
          let active = document.querySelector("#slide ul li.active");
          let next = active.nextElementSibling || first;
          fadeOut(active, fade_speed)
          active.classList.remove("active");
          fadeIn(next, fade_speed)
          next.classList.add("active");
        }, interval);
      }

      document.addEventListener("DOMContentLoaded", slide);
    </script>
  </head>
  <body>
    <div id="slide">
      <ul>
        <li><a href=""><img src="img/img1.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img2.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img3.jpg" width="960" heght="540"/></a></li>
        <li><a href=""><img src="img/img4.jpg" width="960" heght="540"/></a></li>
      </ul>
    </div>
  </body>
</html>

動作は同じ。

最近の流れとしては jQuery 使わないっていうのがあるみたいだけど、やっぱ使ったほうが楽ではあるよなあ。

カテゴリー: JavaScript | コメントする

2つの型クラスを使う

任意のリストの合計を計算する sum メソッドを作ったときには、「足すことのできる」型クラスとして Additive という型クラスと、そのインスタンス IntAdditive、StringAdditive を作った。

今回は、リストの平均を求める average メソッドを作ってみよう。Int に限れば次のように定義することができる。要素を合計して要素数で割っているだけだ。

scala> def average(lst: List[Int]): Int = lst.foldLeft(0)((x, y) => x + y) / lst.length
average: (lst: List[Int])Int

使い方も簡単。

scala> average(List(1, 3, 5))
res0: Int = 3

さて、この average メソッドを、Int でも Double でも使えるようにしたい。

合計を求めるのには Additive を使えばいいだろう。だけどそれだけじゃ足りない。1つには、要素数で割る必要があるので割り算もできなければならない。もう1つは、要素数を求める length メソッドは Int を返すので、これを合計の型(Int か Double)に合わせてやる必要がある。

そこで、Additive をもっと一般化して、四則演算とゼロを持つ Num という型クラスを考えよう。で、Int と Double にそれぞれ対応する IntNum と DoubleNum というインスタンスを作る。ファイルは Num.scala。

trait Num[A] {
    def plus(a: A, b: A): A
    def minus(a: A, b: A): A
    def multiply(a: A, b: A): A
    def divide(a: A, b: A): A
    def zero: A
}

object Num {
    implicit object IntNum extends Num[Int] {
        def plus(a: Int, b: Int): Int = a + b
        def minus(a: Int, b: Int): Int = a - b
        def multiply(a: Int, b: Int): Int = a * b
        def divide(a: Int, b: Int): Int = a / b
        def zero: Int = 0
    }
    implicit object DoubleNum extends Num[Double] {
        def plus(a: Double, b: Double): Double = a + b
        def minus(a: Double, b: Double): Double = a - b
        def multiply(a: Double, b: Double): Double = a * b
        def divide(a: Double, b: Double): Double = a / b
        def zero: Double = 0.0
    }
}

それから、Int から変換する FromInt という型クラスと、FromIntToInt、FromIntToDoubleというインスタンスを作る。ファイルは FromInt.scala。

trait FromInt[A] {
    def to(from: Int): A
}

object FromInt {
    implicit object FromIntToInt extends FromInt[Int] {
        def to(from: Int): Int = from
    }
    implicit object FromIntToDouble extends FromInt[Double] {
        def to(from: Int): Double = from
    }
}

sbt console を起動して2つのファイルを読み込めば、準備は完了。

average メソッドの定義は次のようになる。

scala> def average[A](lst: List[A])(implicit a: Num[A], b: FromInt[A]): A = {
     |     val length: Int = lst.length
     |     val sum: A = lst.foldLeft(a.zero)((x, y) => a.plus(x, y))
     |     a.divide(sum, b.to(length))
     | }
average: [A](lst: List[A])(implicit a: Num[A], implicit b: FromInt[A])A

ここで1つ勘違いをしていたのを白状しよう。「implicit キーワードは引数リストの先頭にしか付けられない」というので、てっきり先頭の引数だけが implicit になるのだと思っていた。実際には引数リスト全体が implicit になるんだね。というわけで、ここでは a と b が implicit parameter になっている。言い換えると Num と FromInt という2つの型クラスを使っている。

さあ、最後に試してみよう。

scala> average(List(1, 3, 5))
res0: Int = 3

scala> average(List(1.5, 2.5, 3.5))
res1: Double = 2.5

このとおり、Int にも Double にも対応した average メソッドができた。

カテゴリー: Scala | コメントする