privateなgemサーバー構築のお話


## 目的

privateなgemサーバーを構築したい。
geminaboxやgemirroがよく使われているらしい。

* https://github.com/geminabox/geminabox
* https://github.com/PierreRambaud/gemirro

ここではgeminaboxを使う。

## 構築環境

* CentOS 7.2（vagrant bento）
* geminabox
* puma
* nginx（yum package）

## 設置手順

rubyが設置されてない場合はyumやrbenvで設置する。この説明は割愛する。

bundlerの設置

```sh
gem install bundler
```

アプリケーションのディレクトリを生成する。

```sh
mkdir gembox
cd gembox
bundle init
```

アプリケーションサーバーとしてpumaを使う。
bundle initで生成されたGemfileを下記のように編集する。

```ruby
# frozen_string_literal: true
source "https://rubygems.org"

# gem "rails"
gem "geminabox"
gem "puma"
```

別にglobalのruby環境やsystemのruby環境で設置しても構わない。
共通的なものだけglobalのruby環境に設置するのが自己ルールであるだけだ。

```sh
bundle install --path vendor/bundle
```

config.ruを生成する。

```ruby
require "rubygems"
require "geminabox"

Geminabox.data = "./data" # ... or wherever
run Geminabox::Server
```

data、config、tmpディレクトリを生成する。
dataはgemファイルの保管箱である。
unixソケットを使うので、socketsディレクトリも作ろう。

```sh
mkdir data
mkdir config
mkdir -p tmp/pids
mkdir tmp/sockets
```
puma.rbを生成する。

```ruby
mkdir config
vi config/puma.rb

app_root = "#{File.expand_path("../..", __FILE__)}"
pidfile "#{app_root}/tmp/pids/puma.pid"
state_path "#{app_root}/tmp/pids/puma.state"
bind "unix://#{app_root}/tmp/sockets/puma.sock"
directory app_root
threads 5,5
workers 2
environment 'production'
daemonize true
```

daemon化したいと思い、daemonize trueにしたがこのせいでsystemdで動けなくなり、とても苦労した。
あとで説明する。今は必要なので、そのままおいて。

bundle execは面倒だからpumaコマンドは生成しておく。

```sh
bundle binstubs puma
```

binディレクトリが生成されpumaやpumactlコマンドが生成されているはずだ。

ここまでするとプロジェクトの中身はこうなる。

```sh
.
├── Gemfile
├── Gemfile.lock
├── bin
│   ├── puma
│   ├── pumactl
│   └── rackup
├── config
│   └── puma.rb
├── config.ru
├── data
├── tmp
│   ├── pids
│   └── sockets
└── vendor
    └── bundle
```

pumaの起動・終了

```sh
bin/pumactl -F config/puma.rb start
bin/pumactl -F config/puma.rb stop
```

nginxの設置
epelが設置されていない場合は先に設置する。
yumリポジトリを登録ではなくyumパッケージで設置する。

```sh
sudo yum install epel-release -y
sudo yum install nginx -y
```

nginx.confファイルを下記のように編集する。

```sh
sudo vi /etc/nginx/nginx.conf

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user vagrant;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  gembox;
        client_max_body_size 100M;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
    }
}
```

vagrantユーザでpumaが実行されるので、nginxのworkerプロセスもvagrantで実行される必要がある。
そうしないと502 Bad Gatewayエラーが発生する。
そのときのエラーログはこうだ。

```sh
cat /var/log/nginx/error.log

...
2017/04/07 01:43:53 [crit] 13203#0: *1 connect() to unix:/home/vagrant/gembox/tmp/sockets/puma.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.33.1, server: _, request: "GET / HTTP/1.1", upstream: "http://unix:/home/vagrant/gembox/tmp/sockets/puma.sock:/", host: "gembox"
...
```

* serverの設定
  - include /etc/nginx/conf.d/gembox_server.conf

```nginx
upstream gembox {
  server unix:/home/vagrant/gembox/tmp/sockets/puma.sock;
}
```

* locationの設定
  - include /etc/nginx/default.d/gembox_server.conf

```nginx
location / {
  proxy_pass http://gembox;
  proxy_set_header Host $http_host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```

nginxの起動・終了・状態・リロード

```sh
sudo systemctl start nginx.service
sudo systemctl stop nginx.service
sudo systemctl status nginx.service
sudo systemctl reload nginx.service
```

おまけにsystemdについて

* https://wiki.archlinuxjp.org/index.php/Systemd
* http://qiita.com/a_yasui/items/f2d8b57aa616e523ede4

ここまでするとちゃんとgeminaboxのページが表示されるはずだが、もし表示されなかった場合は80ポートLISTENされているのか、確認する。
firewalld含めて確認する必要がある。

geminaboxページが表示されたら、gemファイルを１個選んでアップロードしてみよう。
必ずアップロードするんだ。アップロードするとgem保管箱の仕組みが作られるみたい。
空いていたdataディレクトリにファイルやディレクトリが生成される。

```sh
data
├── _cache
├── _temp
├── gems
├── latest_specs.4.8
├── latest_specs.4.8.gz
├── prerelease_specs.4.8
├── prerelease_specs.4.8.gz
├── quick
├── specs.4.8
└── specs.4.8.gz
```

gemファイル１個だけアップロードする場合はこれでも悪くないが、複数のgemをアップロードするにはコマンドが便利。

```
mac（ホスト） - vagrant（ゲスト、geminaboxサーバー）

vi /private/etc/hosts
192.168.33.10 gembox
```

ホストからゲストへ複数のgemファイルをアップロードしてみよう。

```sh
gem inabox *.gem -g http://gembox
```

500エラーが発生した。

```html
Pushing yard-0.9.8.gem to http://gembox/...
ERROR:  Error (500 received)

<html>
<head><title>500 Internal Server Error</title></head>
<body bgcolor="white">
<center><h1>500 Internal Server Error</h1></center>
<hr><center>nginx/1.10.2</center>
</body>
</html>
```

nginxのworkerプロセスはvagrantなので、「/var/lib/nginx」をownerがvagrantではないとgemファイルのアップロードができない。
そのときのエラーログはこうだ。

```sh
2017/04/07 10:58:21 [crit] 4488#0: *15 open() "/var/lib/nginx/tmp/client_body/0000000001" failed (13: Permission denied), client: 192.168.33.1, server: gembox, request: "POST /upload HTTP/1.1", host: "gembox"
```

ownerをvagrantに変更する。（vagrant、ゲスト側）

```sh
sudo chown -R vagrant /var/lib/nginx
```

もう一度やってみる。
アップロードファイルのサイズが大きい場合はエラーが発生する。

```sh
Pushing nokogiri-1.7.0.1.gem to http://gembox/...
ERROR:  Error (413 received)

<html>
<head><title>413 Request Entity Too Large</title></head>
<body bgcolor="white">
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx/1.10.2</center>
</body>
</html>
```

そのときのエラーログはこうだ。（vagrant、ゲスト側）

```sh
2017/04/07 11:50:52 [error] 4488#0: *70 client intended to send too large body: 9148319 bytes, client: 192.168.33.1, server: gembox, request: "POST /upload HTTP/1.1", host: "gembox"
```

このようなときは、nginx.confファイルの中でclient_max_body_sizeを適当に大きいサイズで変更しよう。

もう一度やってみる。
アップロードしたgemファイルが表示されるはずだ。

```
http://gembox
```

これでおわり！
ではない。

pumaをsystemdのサービスに登録して使う。

```sh
sudo vi /etc/systemd/system/puma.service

[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
User=vagrant
Group=vagrant
PIDFile=/home/vagrant/gembox/tmp/pids/puma.pid
WorkingDirectory=/home/vagrant/gembox
ExecStart=/usr/bin/bash -lc 'bin/pumactl -F config/puma.rb start'
ExecReload=/bin/kill -s USR1 $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
TimeoutSec=300
Restart=always

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

systemdの詳細は割愛する。
Software Design 2015年2月号のLinux systemd入門を参考

おまけにpuma関連signal

* https://github.com/puma/puma/blob/master/docs/signals.md

__ExecStartがサービスをstartするとき実行されるコマンドだが、「/usr/bin/bash -lc 'puma起動コマンド'」で実行しないといけない。__
そうしないとvagrantではないrootユーザーのログインシェルで実行される。

__puma.rbファイルからdaemonize trueの設定を消す！__
systemdのサービスがdaemon化してくれることだから要らないし、むしろこれのせいでサービスがずっとfailedになってしまう。

おしまい。