Hexo(静的サイトジェネレータ)でサイトリニューアルした話

特にリニューアルがしたかったワケではないんですが、「Hexo使ってみたい」という、いつもの手段が目的化するアレでサイトをリニューアルしました(元々はpandocで自前のシェル組んでました)

Hexoとは

  • Nodeで走る静的サイトジェネレータ
  • Pluginが滅茶苦茶豊富でほぼnpmでサクッと入ると思う
  • Themeもかなりある。中国で人気があるようで、langとか多少手直し必要なやつもある

環境

  • Node v9.5.0 (anyenvとndenv使用)
  • サーバはNginx使うのでhexo serverは未使用
  • Droneでデプロイ (Droneの話はこちら)

環境構築

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// anyenvでndenvを入れる
$ anyenv install ndenv
$ exec $SHELL -l

// ndenvでnodeを入れる
$ ndenv install v9.5.0
$ mkdir mysites
$ echo "v9.5.0" > mysites/.node-version
$ cd mysites

// npmでhexo-cliを入れる
$ npm init
$ npm install hexo-cli --save

// hexoで最初のブログ作成(※「myblog/public/」がDocumentRootになります)
$ ./node_modules/hexo-cli/bin/hexo init myblog
$ cd myblog/
$ npm install

基本的なコマンド

1
2
3
4
5
6
7
8
// 記事作成
$ ./../node_modules/hexo-cli/bin/hexo new (記事タイトル)

// 静的ファイル作成(「public/」配下にhtmlが出来ます)
$ ./../node_modules/hexo-cli/bin/hexo generate

// 静的ファイル削除(「public/」配下全部消えます)
$ ./../node_modules/hexo-cli/bin/hexo clean

絶対無料枠内でサーバ構築するマン(GCP)

最近まわりでサーバ欲しい、という、エンジニア/非エンジニアの人がちらほらいるのでまとめてみました

サーバ:Google Compute Engine

(※以下は、2018年時点の情報です)
スペックは限定されますが、永年無料です(1年間の無料クーポンの事と勘違いしてる方がいたので強調)
インスタンスタイプは「f1-micro」、リージョンは「us」を選択します
ディスクは30GBまで増やせます

本当に最低限のスペックしかないので出来る事は限られますが、勉強用途には作って破棄して出来るので最適だと思います

ドメイン:MyDNS

10年以上お世話になっているMyDNSさんです
アカウント取得後、STEP 4にあるIP通知を定期的にしてあげれば、GCE側でIPが変わっても大丈夫です
(※固定IPで登録もできたはず)
もしくはこちらもどうぞ → MyDNSのIP通知をdockerコンテナでやるやつ作った

メール送信(SMTP):Sendgrid

今のところFreeプランでも十分事足りてるのでこれで。

公式:SendGrid でのメールの送信 | Compute Engine ドキュメント | Google Cloud Platform

監視:Mackrel

SSL証明書:Lets’s Encrypt

Apache Drillを2ヶ月程使ってみて覚えた内容のメモ(Drillbit Clusterとか)

前回:Apache Drillを使ってみたのでメモ (インストールや基本操作など)

Drillbit Cluster: 概要

Installing Drill on the Cluster - Apache Drill

ZooKeeper: インストール

1
2
3
4
5
6
# cd /usr/local/lib/
# wget http://ftp.jaist.ac.jp/pub/apache/zookeeper/zookeeper-3.4.11/zookeeper-3.4.11.tar.gz
# tar zxvf zookeeper-3.4.11.tar.gz -C /usr/local/sbin/
# cd /usr/local/sbin/zookeeper-3.4.11
# mv conf/zoo_sample.cfg conf/zoo.cfg
# sed -ri "s/^#maxClientCnxns=60/maxClientCnxns=20/" conf/zoo.cfg

Drillbit: 設定ファイルなど

1
2
3
4
5
# cat apache-drill-1.12.0/conf/drill-override.conf | tail -n 4
drill.exec: {
cluster-id: "drillbits",
zk.connect: "[ZooKeeperのIP]:2181"
}

Drillbit: 基本操作

1
2
# apache-drill-1.12.0/bin/drillbit.sh
Usage: drillbit.sh [--config|--site <site-dir>] (start|stop|status|restart|run) [args]
  • Webインターフェイス:Starting the Web Console
  • レポートもWeb側に出るので基本そっち見た方が良い
  • ポートの競合か何かの理由?により、バージョンによって一部の使用ポートが変更されたらしいので、バージョンが古い場合はポート番号を要確認

SQLのメモ

  • drillbitの一覧
1
select * from sys.drillbits;
  • 接続中のdrillbitの確認
1
select hostname from sys.drillbits where `current` = true;
  • sqllineから設定値変更
1
2
3
alter system set `planner.slice_target` = 110000;

SELECT * FROM sys.options where name = 'planner.slice_target';

Drill commands cheat sheet | Open Knowledge Base

1
alter session set `exec.enable_union_type` = true;
1
select * from members where ILIKE((age), '^[0-9]$')
  • カラム名が予約語と被る事が結構多かった(たまたま?)ので、被る場合はバッククォートで囲む:Reserved Keywords - Apache Drill
1
2
// 例:filterというカラムをwhere句で絞る
select * from dfs.`xxxxxxx.csv` as tbl where tbl.`filter` = 'hoge'

vue,vue-routerでとりあえずSPA作るまでのメモ

ソース:nobiki/vue-test

RailsのTurbolinksを使いたくなかったのでフロントエンドで完結出来るようにvueやvue-routerなどを使ってみた

環境

  • anyenv
  • ndenv
  • webpack
  • bulma (こちらのテンプレートを借りてます)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ndenv version
v8.1.0 (set by /workspace/vue-test/.node-version)

$ npm ls --depth=0
/workspace/vue-test
├── css-loader@0.28.9
├── extract-text-webpack-plugin@3.0.2
├── style-loader@0.20.1
├── vue@2.5.13
├── vue-loader@14.1.1
├── vue-router@3.0.1
├── vue-template-compiler@2.5.13
├── vuex@3.0.1
└── webpack@3.10.0

Webpackに関して:WebpackでcssとjsをBundleしたメモ

サイトマップ

トップページに3つリンクがあるだけの、とりあえずシンプルなやつです

1
2
3
4
トップ
├── ページ1
├── ページ2
└── ページ3

Webpack: 設定とファイルツリー

rulesに、vue-loaderの設定を追加resolve.aliasに、vue,vuex,vue-routerの設定を追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /workspace/vue-test/webpack.config.js

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: {
"common": path.join(__dirname, "./webpack/common/entry.js"),
"app/top": path.join(__dirname, "./webpack/app/top/entry.js"),
},
output: {
filename: "./public/assets/[name]/bundle.js"
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({fallback:'style-loader',use:'css-loader'})
},
{
test: /\.(jpg|gif|png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
},
]
},
resolve: {
alias: {
'vue': 'vue/dist/vue.esm.js',
'vuex': 'vuex/dist/vuex.esm.js',
'vue-router': 'vue-router/dist/vue-router.esm.js'
}
},
plugins: [
new ExtractTextPlugin("./public/assets/[name]/bundle.css")
]
};

webpack関係の物は「webpack」ディレクトリに入ってます

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ tree /workspace/vue-test/webpack/

/workspace/vue-test/webpack/
├── app
│   └── top
│   ├── components
│   │   ├── index.vue
│   │   ├── linkA.vue
│   │   ├── linkB.vue
│   │   └── linkC.vue
│   ├── css
│   │   └── top.css
│   ├── entry.js
│   ├── js
│   │   └── top.js
│   ├── router
│   │   └── routes.js
│   └── view
│   ├── indexView.vue
│   ├── linkAView.vue
│   ├── linkBView.vue
│   └── linkCView.vue
└── common // →「common」ディレクトリ内はbulma関係のファイルなのでVueとは無関係

Vue: Router

URLとViewを紐づけるやつ

1
2
3
4
5
6
7
8
9
10
11
12
$ cat /workspace/vue-test/webpack/app/top/router/routes.js
import indexView from './../view/indexView.vue';
import linkAView from './../view/linkAView.vue';
import linkBView from './../view/linkBView.vue';
import linkCView from './../view/linkCView.vue';

export default [
{ path: '/', component: indexView },
{ path: '/linkA', component: linkAView },
{ path: '/linkB', component: linkBView },
{ path: '/linkC', component: linkCView },
];

Vue: View

h2タグが1つあるだけのシンプルなやつです

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat /workspace/vue-test/webpack/app/top/view/linkAView.vue

<template>
<div class="wrap linkA">
<h2>linkA</h2>
</div>
</template>

<style scoped>
.wrap {
padding: 0 10px;
}
</style>

<script>
import linkA from './../components/linkA.vue';
export default {
components: {
linkA
},
}
</script>

Vue: Component

今回は画面遷移を確認するだけなので内容は何も無いよう

1
2
3
4
5
6
7
8
9
$ cat /workspace/vue-test/webpack/app/top/view/linkAView.vue
<template>
<div>
</div>
</template>

<script>
export default { methods: {} }
</script>

Vue: top.js

elの行で、*[data-route]を指定したら一括でrouter設定できるかなと思ってやってみたけどダメだったので3つ書いてるけどほかによい方法があるかもしれない(今回はなるべく掘り下げずシンプルに)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Vue from 'vue';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import routes from '../router/routes.js';

Vue.use(Vuex);
Vue.use(VueRouter);

const router = new VueRouter({
routes
});

new Vue({
el: "*[data-route='contents']",
router: router,
});

new Vue({
el: "*[data-route='topNav']",
router: router,
});

new Vue({
el: "*[data-route='footNav']",
router: router,
});

Webpack: bundle

1
$ ./node_modules/.bin/webpack -p

HTML: index.html

長いので必要な部分だけ抜粋してます

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="ja">
<head>
<link rel="stylesheet" type="text/css" href="/assets/common/bundle.css">
<link rel="stylesheet" type="text/css" href="/assets/app/top/bundle.css">
</head>
<body>
<nav class="navbar is-white topNav">
<div class="container">
<div class="navbar-menu" data-route="topNav">
<div class="navbar-start">
<router-link class="navbar-item" to="/">トップ</router-link>
<router-link class="navbar-item" to="/linkA">リンクA</router-link>
<router-link class="navbar-item" to="/linkB">リンクB</router-link>
<router-link class="navbar-item" to="/linkC">リンクC</router-link>
</div>
</div>
</div>
</nav>
<section class="container">
<div class="columns">
<div class="column is-10">
<div id="contents" class="box tile content" data-route="contents">
<router-view></router-view>
</div>
</div>
</div>
</section>
<footer class="footer">
<div class="container">
<hr>
<div class="content has-text-centered" data-route="footNav">
<p>
<router-link to="/">トップ</router-link> |
<router-link to="/linkA">リンクA</router-link> |
<router-link to="/linkB">リンクB</router-link> |
<router-link to="/linkC">リンクC</router-link>
</p>
</div>
</div>
</footer>
<script async type="text/javascript" src="/assets/common/bundle.js"></script>
<script async type="text/javascript" src="/assets/app/top/bundle.js"></script>
</body>
</html>

<router-link to="[リンク先]>は、DOMがロードされた時にはaタグになります

<router-view>に、リンク先の内容が出力されます

rsyncのメモ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 基本
$ rsync -avz /path/from/ /path/to/

-z, --compress
転送時に gzip 圧縮を使用する。
-v, --verbose
コピーするファイル名を標準出力する。
-a, --archive
-rlptgoD と同義。(--recursive --links --perms --times --group --owner --devices)
-r, --recursive
ディレクトリを再帰的にコピーする。このオプションを指定していないと、rsync はディレクトリを全くコピーしない。
-l, --links
シンボリックリンクを DEST で作り直す。
-p, --perms
DEST のパーミッションを SOURCE と同じにする。
-t, --times
ファイルの転送時に、修正時間情報もリモートへ転送する。
-g, --group
DEST の所有グループを SOURCE と同じにする。スーパーユーザで実行しないと所有グループを変更できない。
グループ ID (GID) を使う場合は --numeric-ids オプションを追加する。
-o, --owner
DEST の所有者を SOURCE と同じにする。スーパーユーザで実行しないと所有者を変更できない。
ユーザ ID (UID) を使う場合は --numeric-ids オプションを追加する。
-D
デバイスファイルとスペシャルファイルを維持する。
--devices --specials と同義。

dru-run(-n)

1
$ rsync -avz -n /path/from/ /path/to/

fromに無いファイルを削除して完全同期(–delete)

1
$ rsync -avz --delete /path/from/ /path/to/

一部のファイルを除外したりする(–exclude, –include)

左から順に判定

1
$ rsync -avz --exclude="*.csv" --exclude="*.json" --include="*" /path/from/ /path/to/

–exclude, –includeを外部ファイルから(–exclude-from, –include-from)

1
$ rsync -avz --exclude-from=exclude.txt --include-from=include.txt /path/from/ /path/to/

別途sshコマンド指定が必要な場合の例

基本的には~/.ssh/config側で解決するのがいいです

1
$ rsync -avz -e "ssh -p [ポート] -i [秘密鍵]" /path/from/ [SSHユーザ]@[SSHホスト]:/path/to/

rsyncの「/tmpに一時保存してからコピー」する動作を「直接上書きコピー」するようにする(–inplace)

大きなファイルをrsyncする際の処理時間短縮などに有効

1
$ rsync -avz --inplace /path/from/ /path/to/

rsyncコマンドに帯域制限をかける(–bwlimit=[KB/sec])

1
$ rsync -avz --bwlimit=5120 /path/from/ /path/to/

vim-table-modeが手放せなくなってきたので自分用メモ

最近とあるビッグデータのベンチマーク作業をしてて、計測結果を都度重いExcelに記録していくのはストレスなので、Vim上でvim-table-modeを使ってたらいろいろ出来る事がわかってきたので忘れないように覚えた事をメモ

基本操作

1
2
// table mode ON/OFF
<Leader>tm
1
2
3
4
5
// 入力
|a|b|c|

// 出力
| a | b | c |
1
2
3
4
5
6
7
8
9
// 入力
|a|b|c|
||
|ddd|eee|fff|

// 出力
| a | b | c |
|-----|-----|-----|
| ddd | eee | fff |

CSVを基にテーブルにする

1
2
3
4
5
6
7
// CSVテキスト
a,b,c,d,e
aa,bb,cc,dd,ee

// ↑をビジュアルモードで選択して「:Tableize/,」 ※「,」がデリミタ指定
| a | b | c | d | e |
| aa | bb | cc | dd | ee |

列を削除

1
2
3
4
5
6
7
8
9
10
11
// 元のテーブル
| a | b | c |
| a | b | c |
| a | b | c |
| a | b | c |

// 削除したい列(例:b)の上にカーソルを移動して「<Leader>tdc」
| a | c |
| a | c |
| a | c |
| a | c |

その他

1
2
// vim: 折り返さない
:set nowrap

Amazon Dash ButtonでSSL証明書を更新する

長らく買ったまま放置してたDash Buttonを何かに使おうと思って、ボタンをぽちったらSSL証明書(LetsencryptのDV証明書)の更新を行うようにしてみました

環境

実際のところ、ただ更新するだけならBitbucketとDroneを経由する必要はありませんが、もともと手動でタグ付けを行っていた環境があったのと、いつ更新したかをgitのログで管理出来る仕組みをそのままにしたかったので、このようにしました

Dash Buttonのハック

ハックというほどの事でもないですが、WiFi設定とMacアドレスが確認出来る所までやります

ウチの環境はMACアクセス制限をかけてたので、上記の記事を参考に、Fingアプリを使いました

Raspberry PI: anyenvを入れてpyenv環境をつくる

pyenv経由でやったほうが後々何かと役に立つので、anyenvでpyenvを入れます

※anyenvのインストール手順はリンク先の通りなのでここでは割愛します

1
2
3
4
5
6
7
8
9
// pyenvを入れる
$ anyenv install pyenv
$ exec $SHELL -l

// virtualenvも使えるようにする
$ git clone https://github.com/yyuu/pyenv-virtualenv ~/.anyenv/envs/pyenv/plugins/pyenv-virtualenv

// Pythonを入れる
$ pyenv install 3.6.2

Raspberry PI: virtualenvで環境を用意する

1
2
3
// パスなどは任意
$ cd /path/to/project
$ pyenv virtualenv 3.6.2 dash-button

Raspberry PI: pipでライブラリをインストール

こちらのライブラリを使わせてもらいます

1
$ pip install amazon-dash

単純なechoのみのymlを作成し、動作を確認します

1
2
3
4
5
6
7
8
9
$ vim amazon-dash.yml

settings:
delay: 10
devices:
xx:xx:xx:xx:xx:xx: ← Dash ButtonのMACアドレス
name: Nescafe
user: pi
cmd: echo "hoge"
1
2
3
4
// 権限はrootにする必要がある
$ sudo chown root:root amazon-dash.yml
$ sudo amazon-dash run
hoge ← Dash Buttonを押してhogeが出たらOK

Bitbucket: SSL更新のためのリポジトリを作る

(※設定ファイルをリポジトリ管理しない場合は飛ばして下さい)

letsencryptとHSTSでDockerコンテナ内にある自サイトをSSL化してみた時のメモの手順にあるように、設定ファイルはiniにしてあるので、このiniファイルのバージョン管理と同時に、このリポジトリにタグが切られたらWebサーバ側のSSL更新フラグを立てるようにします

Drone: 作ったリポジトリのpipelineを設定

(※設定ファイルをリポジトリ管理しない場合は、.drone.ymlの「script」の内容を、amazon-dash.ymlで実行して先に進んで下さい)

Drone自体の構築は、JenkinsからDroneに乗り換えてみた話などを参考にしてください

Droneの「Settings」にアクセスし、「Repository Hooks」の「tag」のみにチェックが入っているのを確認して保存

.drone.ymlは以下のように作成します

1
2
3
4
5
6
7
8
9
10
11
12
13
pipeline:

ssl-update:
host: "[certbot-autoを実行するホスト]"
username: "drone"
port: 22
image: appleboy/drone-ssh
secrets: [ ssh_key ]
script:
- "echo '1' > /var/tmp/cert-update"
- "chmod 666 /var/tmp/cert-update"
when:
event: tag

やっている事は単純で、certbot-autoを実行するホスト側の/var/tmp/cert-updateファイルに、「1」というフラグを立てに行っているだけです。
なので、Dashボタンが押されたら即、/var/tmp/cert-updateを更新しに行くように作成しても同じですので、
リポジトリ管理しない方はDash Button側のymlでそのように作成します

certbot: 証明書の更新

先ほど立てたフラグを検知してcertbot-autoを実行するスクリプトをcronで回します

1
2
$ crontab -l
0 18 * * * script -a /path/to/log/ssl-`date +\%Y\%m\%d`.log -c /path/to/cron.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat cron.sh

#!/bin/bash
STATUS=`cat /var/tmp/ssl-update`

if [[ "1" == ${STATUS} ]]; then
echo "0" > /var/tmp/ssl-update

echo "+++ Try ssh Web1 /etc/letsencrypt/ini/web1.ini Run the certbot."
ssh web1 /usr/local/bin/certbot-auto certonly --config /etc/letsencrypt/ini/web1.ini --non-interactive --agree-tos --expand
echo "+++ Try ssh Web2 /etc/letsencrypt/ini/web2.ini Run the certbot."
ssh web2 /usr/local/bin/certbot-auto certonly --config /etc/letsencrypt/ini/web2.ini --non-interactive --agree-tos --expand
echo "+++ Try ssh Web3 /etc/letsencrypt/ini/web3.ini Run the certbot."
ssh web3 /usr/local/bin/certbot-auto certonly --config /etc/letsencrypt/ini/web3.ini --non-interactive --agree-tos --expand

else
echo "/var/tmp/ssl-update is '0'. Skip for ssl-update."
fi

自分用sshチートシート

SSH関連の記事が増えてきたのでここまでの記事も含めてまとめ

~/.ssh/config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Host [設定名]
HostName [SSH接続先]
Port [ポート番号]
User [ユーザ名]
IdentityFile [鍵ファイル(あれば)]

// known_hostsのチェックをしない
StrictHostKeyChecking no

// 多段
ProxyCommand ssh -W %h:%p [踏み台ホスト]

// サーバ側の応答確認(keep-alive)
ServerAliveInterval 10

// 応答確認して応答が無い場合のリトライ回数(デフォルトは3)
ServerAliveCountMax 5
1
2
// 接続
$ ssh [設定名]

ポートフォワード

1
2
// -fN:バックグラウンド実行 ServerAliveInterval:Keep-aliveを送る
$ ssh -o ServerAliveInterval=30 -fN -L 8080:[接続先ホスト]:80 [踏み台ホスト]

パスワード自動入力: sshpassコマンド

1
2
3
4
5
6
7
8
// インストール(debian)
$ sudo apt-get install -y sshpass

// インストール(msys2)
$ pacman -S sshpass

// パスワードの自動入力
$ sshpass -p "[PASSWORD]" ssh user@hostname

他には、expectコマンドなどもあります。(※expectはSSH以外の用途にも使える)

パスワード自動入力: ssh-add

そもそも鍵ファイル自体にパスフレーズがかかってるのを省略したい場合

1
2
3
4
5
6
7
8
9
10
11
12
// MSYS2の場合のみeval実行
$ eval `ssh-agent`
Agent pid 3244

// 追加
$ ssh-add [鍵ファイル]
Enter passphrase for [鍵ファイル]: (パスフレーズ入力)
Identity added: [鍵ファイル]

// 確認
$ ssh-add -l
2048 SHA256:9wdnLaXG7EApk7b8jlAW7H4xpcjdCO8UrRJyus2QKyE [鍵ファイル] (RSA)

permission denied対策

1
2
// デバッグログ
$ ssh -vvv
  • ホームディレクトリ:755
  • .sshディレクトリ:700
  • authorized_keys:600
  • 秘密鍵:600
  • .sshディレクトリに[秘密鍵].pubが置きっぱなしだと出る場合がある