ぽよメモ

レガシーシステム考古学専攻

Travis CI + Codecov + NoseでCIしてSlackに通知

きっかけ

これまでもTravis CIを使ってきていて、それ自体にはそんなに戸惑うことは無かったんですが、カバレッジも取得したくなったりしたので少し調べたら日本語情報があまりなかったのでここに残しておくことにしました。

環境

開発環境は以下

必要なPythonモジュールなど

  • nose 1.3.7
  • codecov 2.0.15
  • coverage 4.5.1

各種サービスのセットアップ

Travis CI

Circle CIがどうにも複雑に見えてしまいずっとTravisを使っているため、今回もTravisです。
特に難しいところはなく、普通にサインアップしてCIしたいリポジトリを有効にするだけです。Build only if .travis.yml is present にチェックを入れておくと良いと思います。

また、シークレットなトークンを暗号化するのに使うtravisというコマンドをgemでインストールします。

$ gem install travis
$ travis login

.travis.ymlの文法エラーでCIをパスできないとめちゃくちゃ悲しくなるのでlintコマンドを覚えると良いと思います。

$ cat .travis.yml
language: python
python:
  - "3.5"
  - "3.6"
script:
  - nosetests -v
$ travis lint
Hooray, .travis.yml looks valid :)

Codecov

codecov.io

Codecovはカバレッジを表示してくれるWebサービスです。Pythonにはcodecovというpipでインストールできるモジュールがあります*1

同様のサービスとしてCoverallsなどがありますが、あまりデザインが好みで無かった上、Python用のパッケージが2種類あってうーんという感じだったのでこちらにしました*2

上記リンクから、Githubアカウント、Gitlabアカウント、Bitbucketアカウントでサインアップできます。それぞれのオンプレ版にも対応しているみたいなので、かなり良さそうです。また、Teamでの使用もパブリックリポジトリなら無制限?のようです。プライベートリポジトリでの利用を検討している場合はリポジトリごと、またはユーザ数単位での課金になるようです*3

f:id:pudding_info:20180311181631p:plain

有効にするリポジトリを選択するとトークンが表示され、カバレッジリポートのアップロードをしろみたいな画面になりますが、Travis CIから使うなら無視して良いです

f:id:pudding_info:20180311181256p:plain

設定はもう終わりです(すごい)。

リポジトリのセットアップ

テストする

僕はずっとnoseを使っているので、今回もこれで行きます。フォルダ構成は以下の様にしました。student_portal_crawlerが今回テストするモジュールです。

$ tree -L 3 -I venv
.
├── LICENSE
├── README.md
├── requirements.txt
└── student_portal_crawler
    ├── __init__.py
    ├── browser.py
    ├── page
    ├── parser
    └── test
        ├── __init__.py
        └── test_script.py

noseのインストールおよびテスト実行は下記の様にします。テストの書き方についてはここでは解説しません。

$ pip install nose
# テストの実行
$ nosetests
.............................
----------------------------------------------------------------------
Ran 20 tests in 0.680s

OK
# テストの実行とカバレッジの計算
$ pip install coverage
$ nosetests --with-coverage

このままでは使用しているモジュール全てのカバレッジを取得してしまうので、--cover-packageオプションで取得するモジュールを指定してやります。今回の場合はstudent_portal_crawlerになります。

$ nosetests --with-coverage --cover-package=student_portal_crawler
.............................
Name                                                  Stmts   Miss Branch BrPart  Cover
---------------------------------------------------------------------------------------
student_portal_crawler/__init__.py                        1      0      0      0   100%
student_portal_crawler/browser.py                        20      0      2      0   100%
student_portal_crawler/page/__init__.py                   1      0      0      0   100%
student_portal_crawler/page/base.py                      34      4      6      1    78%
student_portal_crawler/parser/__init__.py                 4      0      0      0   100%
student_portal_crawler/parser/base.py                    38      0     10      0   100%
student_portal_crawler/parser/lec_info.py                18      0      6      0   100%
student_portal_crawler/parser/static.py                   5      0      0      0   100%
student_portal_crawler/parser/utils.py                    9      0      2      0   100%
student_portal_crawler/shibboleth_login/__init__.py       1      0      0      0   100%
student_portal_crawler/shibboleth_login/login.py         53      2      8      0    97%
---------------------------------------------------------------------------------------
TOTAL                                                   184      6     34      1    95%
----------------------------------------------------------------------
Ran 29 tests in 0.319s

OK

.travis.ymlを書く

シンプルにPythonのバージョンいくつかでテストし、パスしたらCodecovにカバレッジを送信、Slackに結果を通知します。

まずは自分のSlackのIntegrationからTravis CIを追加します。Hubotとかで節約している人も居るかとは思いますが、僕は面倒なので豪華に行きます。

f:id:pudding_info:20180311184429p:plain

追加したら投稿するチャンネルを決め、トークンを取得します。

f:id:pudding_info:20180311184634p:plain

Travis CIでの使い方についての説明の中に、トークンの暗号化についての項目があるのでコピペして実行します。事前に.travis.ymlがないとエラーが出るので作成しておきます。

f:id:pudding_info:20180311184825p:plain

$ touch .travis.yml
$ travis encrypt "{{チーム名}}:{{トークン}}" --add notifications.slack
$ cat .travis.yml
notifications:
  slack:
    secure: npdNlesznnxrC7KsuB7....

Travis CIで利用可能なPythonバージョンは複数有り、最新は3.8-devnightlyまで使える*4ようなのですが、

Recent Python development branches require OpenSSL 1.0.2+. As this library is not available for Trusty, 3.7-dev, 3.8-dev, and nightly do not work (or use outdated archive).

という記述があり、どうやったら使えるのか分からず3.6-devまでしか使っていません…*5

最終的に下記の様にしました。

language: python

python:
  - "3.5"
  - "3.5-dev"
  - "3.6"
  - "3.6-dev"

install:
  - pip install -r requirements.txt
  - pip install codecov coverage

script:
  - nosetests -v --with-coverage --cover-package=student_portal_crawler

after_success:
  - codecov

notifications:
  email: false
  slack:
    secure: npdNlesznnxrC7K...

after_successcodecovコマンドを実行することで、Codecovにカバレッジリポートが送信されます。トークンなどを加える必要は有りません
また、Slackへの通知のみで十分なのでメール通知はオフにしています。

.coveragercを書く

このままではテストコードまで含めたカバレッジがcodecovに表示されてしまったり、テストしないコード(デバッグ用のステートメントなど)まで含まれてしまうため、.coveragercという設定ファイルを置いて制御します。

[run]
branch = True
source = student_portal_crawler

[report]
exclude_lines =
    if TYPE_CHECKING:
    if __name__ == .__main__.:
ignore_errors = True
omit =
    student_portal_crawler/test/*

typingモジュールを使用して型ヒントの定義をしているので、常にFalseとなり実行されないif TYPE_CHECKING:という文が所々に現れるのでこれを排除しています。
また、実際の実行コード(if __name__ == '__main':)も排除しています。

READMEへバッジを貼る

CI回してカバレッジ取得する最大の目的といっても過言ではない、READMEへのバッジを貼ります。

Travis CI

https://travis-ci.org/ユーザ名/リポジトリ名にアクセスし、リポジトリ名の横に表示されているバッジをクリックすると、形式を選択してコピーできます。

f:id:pudding_info:20180311200415p:plain

Codecov

https://codecov.io/gh/ユーザ名/リポジトリ名/settings/badgeにアクセスすると形式を選択してコピーできます

f:id:pudding_info:20180311200633p:plain

後はpushすればCIが走ります。

結果を確認

Githubへpushしてしばらく待つとテストが完了します。

f:id:pudding_info:20180311203617p:plain

こんな風にSlackに通知が来ます。

f:id:pudding_info:20180311203517p:plain

Codecovに自動でカバレッジが送信され、以下の様に表示されます。

f:id:pudding_info:20180311204144p:plain

また、リンクを開いていくとどのモジュールのカバレッジが低いのか丸わかりな上、各ファイルのどの行がテストされていないのかも一発で分かります。

f:id:pudding_info:20180311204324p:plain

まとめ

取り扱いが面倒なことが多いトークン等の設定が必要ないため、Codecov + Travis CIはなかなかオススメの組み合わせかと思います。
どんどんunittestを書いて徳を積んでいきましょう!

*1:基本Travisから叩くときにしか使用しないので、ローカルにはインストールしていません。

*2:h-miyako.hatenablog.com

*3:Educationプランの問い合わせをするとプライベートリポジトリの数を尋ねられたので、学生でプライベートリポジトリでも使いたい〜って場合は連絡してみると良いと思います。

*4:Building a Python Project - Travis CI

*5:trustyがダメでpreciseならokみたいなことは無いだろうと思って試していません

macOSで任意のWi-Fiへ接続・切断時に任意のシェルスクリプトを実行させる

きっかけ

弊大学では学内ネットワークとして,学生が自分の持つアカウントで認証すれば使用できる認証付き無線LANが用意されています.ありがたいことに「自動プロキシ検出」さえオンにしていれば自動で設定されるため苦労はしませんが,自宅や研究室などプロキシの存在しない環境においてこの設定をしているとアプリケーションの通信に影響が出ることがあります(下記記事参照)

poyo.hatenablog.jp

macOSには「ネットワーク環境」という設定があり,環境に応じて設定を一括で変更できるプロファイル変更機能が備わっているのですが,自動で切り替わるとかそいう頭の良いことをしてくれません.これをもっと簡単に,そして自動でやりたいなと思い最初は自作し始めたのですが良いツールを見つけたのでやめました.

Hammerspoonの紹介

www.hammerspoon.org

Hammerspoonは上記ページにあるとおり,luaを用いて自分でロジックを書いて様々な動作を自動化するためのソフトウェアです.hsというモジュールにhammerspoonによる独自の拡張が沢山含まれており,非常に幅広い動作を実現しています.
luaは簡単な言語ですが色々と癖があるので,慣れていないと引っかかるところがたまにあります.

環境

インストールは以下から可能です.

github.com

使用する機能

WiFi切り替え検知

実はこれは既に公式ページのGetting Startedに書いてあります.
しかしこのサンプルは一つのSSIDについて接続切断を見ているのみで,複数のSSIDについて接続・切断に対応させようと思うと割と面倒です.加えて言うと,新しいSSIDに対応させようとする度にこの~/.hammerspoon/init.luaを編集して追加しないといけないとなると,やがて肥大化してパンクすることが目に見えています.

とはいえ基本は上記ページの hs.wifi.watcherを使用します.

シェルスクリプト実行

hs.execute()というメソッドによりシェルスクリプト実行が可能です.また,launchdなどを用いて実行させる場合などはパスが/usr/bin:/bin:/usr/sbin:/sbinにしか通っていないのですが,先述のメソッドの第二引数にtrueを渡すとどうも~/.bashrcを読み込んでから実行してくれるようです*1

ただしその場合はオーバーヘッドが大きいため,オプションにより制御できるようにしたいと思います.

実装

ちゃんと色々検証したわけではないので自己責任でお願いします.

構成

そこで下記のような構成にしたいと思います

config.json

下記のようにし,~/.hammerspoon/以下に配置します.

{
    "base": {
        "notify": false,
        "shell": "/bin/bash",
        "with_user_env": true
    },
    "networks": [
        {
            "SSID": "ssid1",
            "profile": "proxy",
            "pre_hook": "hoge.sh",
            "post_hook": "fuga.sh"
        },
        {
            "SSID": "ssid2",
            "pre_hook": "poyo.sh",
            "post_hook": "piyo.sh"
        }
    ]
}
  • notify: 通知を有効化するフラグ.今はconfig.iniを読み込むときに通知を出している.デフォルトはtrue
  • shell: シェルスクリプトを実行するシェル.デフォルトは/bin/bash
  • with_user_env: hs.execute()の第二引数に渡す値.ユーザが定義したパスを読み込んだ状態で実行するかどうか.デフォルトはtrue

config.iniに変更があったときに自動で再読み込み

~/.hammerspoon/init.luaに記述していきます.

-- init
HOME = os.getenv("HOME")
CONFIG_HOME = HOME .. "/.hammerspoon/"
CONFIG_FILE = CONFIG_HOME .. "config.json"

-- key
NETWORKS_KEY = "networks"
SHELL_KEY = "shell"
BASE_CONFIG_KEY = "base"
NOTIFY_KEY = "notify"
USER_ENV_KEY = "with_user_env"
SSID_KEY = "SSID"
PROFILE_KEY = "profile"
PRE_HOOK_KEY = "pre_hook"
POST_HOOK_KEY = "post_hook"

-- default settings
NOTIFY = true
SHELL = "/bin/bash"
USER_ENV = true
DEFAULT_NW_PROFILE = "Automatic"

function hasKey(tbl, key)
    for k, v in pairs (tbl) do
        if k == key then 
            return true 
        end
    end
    return false
end

function readConfig(files)
    -- jsonファイルを開いて一行ずつ読み出し,decode
    local fp = io.open(files[1], "r")
    local json = fp:read("*a")
    fp:close()
    local config = hs.json.decode(json)
    if hasKey(config, BASE_CONFIG_KEY) then
        for k, v in pairs(config[BASE_CONFIG_KEY]) do
            hs.settings.set(k, v)
        end
    end
    -- Default valueをセット
    if not hasKey(config, NOTIFY_KEY) then
        hs.settings.set(NOTIFY_KEY, NOTIFY)
    end
    if not hasKey(config, SHELL_KEY) then
        hs.settings.set(SHELL_KEY, SHELL)
    end
    if not hasKey(config, USER_ENV_KEY) then
        hs.settings.set(USER_ENV_KEY, USER_ENV)
    end
    -- Networkプロファイルをロード
    if hasKey(config, NETWORKS_KEY) then
        hs.settings.set(NETWORKS_KEY, config[NETWORKS_KEY])
    end
    if hs.settings.get(NOTIFY_KEY) then
        local notify_msg = string.format("display notification \"Reloaded\" with title \"WLAN Profiles\"")
        hs.osascript.applescript(notify_msg)
    end
end

-- exec
readConfig({CONFIG_FILE})
hs.pathwatcher.new(CONFIG_FILE, readConfig):start()

Hammerspoonではhs.settingsというキーバリューストア的なものを提供していたのでこれを使えと言うことなのかと,動的なデータのほとんどはこれを介して出し入れしています*2

ネットワーク環境切り替え

これはシェルスクリプトnw_profile_changer.sh)を置いておくことにします.

こちらからアイデアを頂きました.

qiita.com

currentlocation=`networksetup -getcurrentlocation`

if test $currentlocation = $1; then
    return
fi
scselect `scselect | grep ${1} | cut -b 4-40`

これを~/.hammerspoon/Scripts以下に置いておきます.

WiFi切り替え時に実行

SCRIPTS_PATH = CONFIG_HOME .. "Scripts/"
PROFILE_CHANGE_SCRIPT = SCRIPTS_PATH .. "nw_profile_changer.sh"

-- tmp
prevSSID = hs.wifi.currentNetwork()

-- シェルスクリプトを実行する関数
function execHook(path, profile)
    if path then
        local script = SCRIPTS_PATH .. path
        local shell = hs.settings.get(SHELL_KEY)
        local with_user_env = hs.settings.get(USER_ENV_KEY) 
        hs.execute(shell .. " " .. script, with_user_env)
    end
end

-- ネットワーク環境を切り替える関数
function loadNwProfile(profile_name)
    local command = nil
    local shell = hs.settings.get(SHELL_KEY)
    local with_user_env = hs.settings.get(USER_ENV_KEY)  
    if profile_name then
        command = shell .. " " .. PROFILE_CHANGE_SCRIPT .. " " .. profile_name
    else
        command = shell .. " " .. PROFILE_CHANGE_SCRIPT .. " " .. DEFAULT_NW_PROFILE
    end
    hs.execute(command, with_user_env)
end

function nwProfileChanger(ssid, profile, connection)
    if connection then
        -- 接続時にプロファイルを変更
        loadNwProfile(profile[PROFILE_KEY])
        if hasKey(profile, PRE_HOOK_KEY) then
            execHook(profile[PRE_HOOK_KEY])
        end
    else
        if hasKey(profile, POST_HOOK_KEY) then
            execHook(profile[POST_HOOK_KEY])
        end
    end
end

function ssidChangedCallback(watcher, msg, interface)
    local newSSID = hs.wifi.currentNetwork()
    if prevSSID ~= newSSID then
        for i, nw in ipairs(hs.settings.get(NETWORKS_KEY)) do
            if not prevSSID and newSSID and nw[SSID_KEY] == newSSID then
                -- 接続時
                nwProfileChanger(newSSID, nw, true)
                break
            elseif prevSSID and not newSSID and nw[SSID_KEY] == prevSSID then
                -- 切断時
                nwProfileChanger(prevSSID, nw, false)
                break
            end 
        end
    end
    prevSSID = newSSID
end

-- SSID変更を監視
hs.wifi.watcher.new(ssidChangedCallback):start()

解説は省きますが,SSID変更を検知してそのSSIDに合致する設定が書かれていた場合,ネットワーク環境変更およびシェルスクリプト実行を行います.

全体は下記にあります.

任意のWiFi接続・切断時に任意のシェルスクリプトを実行するHammerspoonの設定 · GitHub

Hammerspoonをリロード

メニューバーまたはコンソールからReload Configします.

これで準備は完了です!

応用

研究室のWiFiに接続する度に論文提出までの期限を通知

このためだけにGoで超簡単に書きました.

github.com

下記のようなシェルスクリプトを用意し,~/.hammerspoon/when_connect_lab.shとして配置します.

limit=`go-sotsuron-counter`
osascript -e 'display notification "'"${limit}です"'" with title "Welcome to Laboratory"'

config.jsonに記述

{
    "base": {
        "notify": false,
        "shell": "/bin/bash",
        "with_user_env": true
    },
    "networks": [
        {
            "SSID": "ラボのSSID",
            "pre_hook": "when_connect_lab.sh"
        }
    ]
}

はい!こうなります!

f:id:pudding_info:20180116165550p:plain

思わず画面をたたき割らないように注意しましょう.

gitのプロキシ設定を行う

こういう用途が本命です. プロキシのオンオフスクリプトをそれぞれ書きます.

~/.hammerspoon/proxy_on.sh

# git
git config --global http.proxy http://hoge.com:8080
git config --global https.proxy https://hoge.com:8080

# その他
launchctl setenv HTTP_PROXY http://hoge.com:8080
launchctl setenv http_proxy http://hoge.com:8080
launchctl setenv HTTPS_PROXY https://hoge.com:8080
launchctl setenv https_proxy https://hoge.com:8080
launchctl setenv ALL_PROXY http://hoge.com:8080
launchctl setenv all_proxy http://hoge.com:8080

~/.hammerspoon/proxy_off.sh

[2018/1/30 追記] [http][https]セクションが.gitconfigに大量に追記されてしまう問題を修正しました

# git
git config --global --unset http.proxy
git config --global --unset https.proxy
git config --global --remove-section http
git config --global --remove-section https

# その他
launchctl unsetenv HTTP_PROXY
launchctl unsetenv http_proxy
launchctl unsetenv HTTPS_PROXY
launchctl unsetenv https_proxy
launchctl unsetenv ALL_PROXY
launchctl unsetenv all_proxy

config.jsonに記述

{
    "base": {
        "notify": false,
        "shell": "/bin/bash",
        "with_user_env": true
    },
    "networks": [
        {
            "SSID": "ProxyありのSSID",
            "profile": "プロキシ設定したネットワーク環境名", 
            "pre_hook": "proxy_on.sh",
            "post_hook": "proxy_off.sh"
        }
    ]
}

exportではなくlaunchctl setenvするのがミソです.
適当なWiFiで設定してみて実際に動くか確かめると良いと思います.

デバッグ目的だけならosascript -e 'display notification "hoge" with title "test"'とか書くと実行された場合に通知が出て良いです.

課題

  • 突貫で書いたので雑
  • たまにjson読み込みでエラーを吐く(init.lua再読込で直る).原因不明.
2018-01-16 16:53:19: ********
2018-01-16 16:53:19: 16:53:19 ERROR:   LuaSkin: hs.pathwatcher callback error: The data couldn窶冲 be read because it isn窶冲 in the correct format.
stack traceback:
    [C]: in function 'hs.json.internal.decode'
    /Users/pudding/.hammerspoon/init.lua:42: in function 'readConfig'
2018-01-16 16:53:19: ********

まとめ

自動で設定できるとはいえ,例えばシェルは開き直さないと環境変数が反映されないとかそういうのがあるので,移動時にちゃんと後始末しない(ウィンドウ開けたら開けっぱなし)な人は気をつけないとつらくなります.

…卒論書きます.

*1:zshなどに関しては未検証です

*2:luaではlocalの指定が無い限りグローバル変数になるという特性があるため,複雑化してきたときに思わぬ働きをしてしまわないよう,こうした機構があると便利なのかも知れません.

Octopassで簡単ユーザ管理

きっかけ

ちょっとした技術コミュニティを作りたくなって友人と学内で一つ非公式サークルを立ち上げました.
当然のようにVPSを借り,Webサーバを立ち上げ,Wikiを建て…なんていうのを考えているとき,ふと「ユーザ管理面倒だな.LDAPほど大げさじゃない何か良いものはないだろうか」と考えてググったら出てきました.

github.com

Githubをバックエンドとして使用してユーザ認証できるなんてステキ!と飛びついてみたら簡単にできて最高だったという話です.

環境

メンバーもまだ少なく,ほとんど創立メンバーの友人と二人で折半するので費用は抑えたい…ということでさくらのVPSのメモリ1GBプランを契約.年額一括1万円程度で固定IPのあるサーバが使えるのはありがたいです.

OSには使い慣れているUbuntuを選択しました.

導入

基本は下記ページ及びgithubのREADMEに従うだけです.

linyo.ws

下記操作は全てさくらのVPSのコントロールパネルからアクセスできるシリアルコンソールから行っています.

Octopassのインストール

UbuntuCentOSならソースからコンパイルしなくてもpackagecloudに既に用意されています.ありがたい.

$ sudo apt-get update
$ sudo apt-get upgrade
$ curl -s https://packagecloud.io/install/repositories/linyows/octopass/script.deb.sh | sudo bash
$ sudo apt-get install octopass

Personal Access Tokenの取得

https://github.com/settings/tokens/newからログインして新しいトークンを作成します.

このとき,必要な権限はadmin:orgの中のread:orgのみで良いようです.

f:id:pudding_info:20171211000509p:plain

設定ファイルの作成

設定ファイルのテンプレートが用意されているのでmvします.

$ sudo mv /etc/octopass.conf.example /etc/octopass.conf
$ sudo vim /etc/octopass.conf

設定ファイルの必要な項目を埋めます.僕は下記のコメントを外しているところのみを埋めています.

# O C T O P A S S

# Required
Token           = "{Your token}"

## Use team
Organization    = "{Your org}"
Team            = "{Your team}"

## Use collaborators
#Owner           = "yourname"
#Repository      = "yourrepository"

# Default
#Endpoint        = "https://api.github.com/"
#Group           = ""
Home            = "/home/%s"
Shell           = "/bin/bash"
#UidStarts       = 2000
#Gid             = 2000
#Cache           = 300
Syslog          = true

# Advanced
#SharedUsers     = [ "admin", "deploy" ]

情報が取れるか確認してみます.

$ sudo octopass passwd
hoge:x:120000:2000:managed by octopass:/home/hoge:/bin/bash
fuga:x:130000:2000:managed by octopass:/home/fuga:/bin/bash

こんな感じでチームに所属しているメンバーが一覧で表示できるはずです.

sshdやpamの設定

これらをsshdやpamから叩けるようにします.とはいってもgithubとかにあるコードをそのままコピペすれば動きます.ステキ.

/etc/ssh/sshd_config

デフォルトではパスワードによるsshなどが可能なので制限します.

# rootログイン禁止
PermitRootLogin no 

# 鍵認証にoctopassをrootから叩いて使用
AuthorizedKeysCommand /usr/bin/octopass
AuthorizedKeysCommandUser root

# 公開鍵認証を有効に
RSAAuthentication yes
PubkeyAuthentication yes

# パスワード認証を無効
PasswordAuthentication no

# PAMを使用
UsePAM yes

/etc/pam.d/sshd

@include common-authは元から記述されているのでコメントアウトします.

#@include common-auth
auth requisite pam_exec.so quiet expose_authtok /usr/bin/octopass pam
auth optional pam_unix.so not_set_pass use_first_pass nodelay
session required pam_mkhomedir.so skel=/etc/skel/ umask=0022

/etc/nsswitch.conf

これを触ったのは初めてだったのですが,名前解決の順序を決定するための設定のようです*1

passwd, shadow, groupの3項目を下記のように変更.

passwd:     files octopass sss
shadow:     files octopass sss
group:      files octopass sss

Githubへの鍵の登録

既にやってるひとは飛ばして構わないです.

ここの操作はMacBook Pro上で行っていますが,やることとしてはWindowsLinuxでも同じで

  1. 公開鍵と秘密鍵のペアを作る
  2. Githubに公開鍵を登録

の2つだけです.

鍵ペアの作成

ssh-keygenコマンドを使用します.-Cはコメントで,末尾に付加されるユーザ名@ホスト名を上書きします(オプション).

$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/{key name} -C "youremail@hoge.com"

公開鍵の中身をクリップボードにコピーします.

$ pbcopy < ~/.ssh/{key name}.pub

登録

  1. https://github.com/settings/keys からログイン
  2. 右上のNew SSH keyをクリック
  3. わかりやすいTitleを入力してKeyセクションに先ほどコピーした公開鍵をペースト
  4. Add SSH keyをクリック

SSHしてみる

クライアントPCからアクセスしてみます.今後のためにまず~/.ssh/configに設定を追記しておきます.sshするポートを変更している人などは適宜読み替えてください.

ServerAliveInterval 60

Host hoge
    HostName {address of your server}
    User {your github name}
    Port 22
    IdentityFile ~/.ssh/{key name}

いざログイン(初回はAre you sure you want to continue connecting (yes/no)?みたいなメッセージが出ます)

$ ssh hoge
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-62-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

SAKURA Internet [Virtual Private Server SERVICE]

Last login: Sun Dec 10 00:00:00 2017 from 000.00.00.0
{your github name}@{your host}: 

わーいやったー!

sudoしたい

ここまででsshはできましたが,実はこのままではsudoできません./etc/octopass.confGroupsがユーザの所属するグループを決定するのですが,これをsudoとかにしてもsudoはできません.どうやるんだーーーーとなってissueで質問させていただきました.

github.com

まさかその日中にレスポンスを頂けるとは…ありがたい.

というわけで,/etc/sudoers.d/以下に設定を追記します.

sudoerへのグループの追加

/etc/octopass.confGroupsを設定しなかった場合,初期値はGithub Teamのチーム名が使用されます(ここではhogeとします).

このhogeグループに対してパスワードを要求せずsudoする権限を与える必要があるようです.(ここからはサーバ上での作業です.さくらのVPSのシリアルコンソールから作業しました*2

$ sudo visudo -f /etc/sudoers.d/hoge

立ち上がるのがvimじゃなくていつも戸惑うのですが,下記のように記述します.

%hoge ALL=(ALL) NOPASSWD: ALL

これで保存するとhogeグループのユーザはパスワード無しでsudoできるようになります*3

まとめ

Team単位で認証できるため,例えば新しく鯖管を育成したいとなったときもユーザ作って〜鍵登録して〜とかせず,Teamにメンバーを追加するだけで済みます.また,複数サーバを建てた際にも最初にこれを導入さえしてしまえば他のメンバがログインして作業できるため効率的です.

サーバ数台程度の規模ならばGithubAPI制限にかかる可能性は低いですし,速度面も特に気になりませんでした.Githubが落ちてるときはどうせまともに作業できないのでゆっくりコーヒーでも飲んで待ちます.

あとは複数ロールへの対応が欲しい…sudo不可のTeamを追加して運用できれば小規模チームにとってはかなり最高なソリューションなのでは.
以上,導入してみた所感でした.

*1:riceplanting.hatenablog.jp

*2:Githubアカウントをつかってsshしてもsudo権限なくて作業できないため

*3:ちょっと怖いですが正直sshの公開鍵認証が破られた時点で終わりみたいなものという気がしているので現状あまり気にしていません.sshコネクション張ったままPCを放置していたら知らん…そんな奴うちには居ないはず…