読者です 読者をやめる 読者になる 読者になる

kumak1’s blog

kumak1のイラストや技術ログ

好きなキーボードを使い続けるためにキーボード作り始めた

技術

これは pepabo Advent Calendar 2016 - Qiita 11日目の記事です。 昨日はいつも隣の席で頑張っている yinm さんの 社会人生活をふりかえります - 楽しいだけで十分です でした。

はじめに

Amazon Dash Button が日本で開始されたり、ペパボのデザイナさんが 自宅の照明を点けたり消したりできるWebサービスを公開 していたり、いよいよIoTが特別なものではなく、身近なものになってきましたね。 「わたしだってソフトだけじゃなくてハードもエンジニアリングしたい!けど、いんたぁねっと使うの敷居高い・・」ので、まずは簡単な組み込み機器を作ってみようと思いました。

せっかくなので普段使える物を・・ということで、エンジニアらしくキーボードを作ってみます。静電容量方式の打ち心地が好みのため、仕事では REALFORCE104UG-HiPro を利用しています。が、 周りを見渡すElgo DoxKINESIS を使ってる人がいます。うぅむ・・親指で打鍵できるの便利そう・・。なので作ってみよう!

つかったもの

  • Pro Micro
    • Arduino Micro互換機
    • 500円程度で購入できてお財布に非常に優しい(Arduino Micro は3000円ほど)
  • Arduino UNO
    • AVR ライターとして
    • 3000円程度で購入できますが、部屋に転がっていたので有効活用
  • CHERRY メカニカル MXスイッチ 赤軸 + キートップ
    • キーボードのメインとなる機器
    • 安いタクトスイッチなどでもいいのですが、なんとなく拘ってみました
    • 2000円程度で揃いました
  • アルミ板
    • アルミ板自体は500円、加工代で3000円しました・・
    • 自分で加工するのがベストかもしれない

殆ど Amazon で手に入りますが、アルミ板を東急ハンズで購入、スイッチとキートップJW system から購入しました。キートップはベースとなる部分と透明なカバーで構成されていて、中にシールや紙を挟むことで好きな印字に変更できます。 Pro Micro は一昔前の USB メモリよりも小さく、初めてみたときは感動ものでした・・

f:id:kumak1:20161210212940j:plain

Pro Micro へ気軽に書き込みをできるようにする

キーボードを簡単に作るために、 Pro Micro を利用します。 Pro Micro はマイコンが1つのみで、プログラムの書き込みをした後、ずっとUSB機器として動作するため今回の用途にぴったりです。 (しかも、キーボード用のライブラリも公式で提供されているため、至れり尽くせり) 反面、プログラムの書き込みを何回も行いたい場合は面倒な操作(RSTとGNDを短絡させて8秒以内に云々・・)が必要になってしまいます。 そこで Arduino UNO を書き込み装置(AVR ライター)にしてしまい、Pro Micro へ Arduino Micro のブートローダを書き込むことで、プログラムの書き込みと実行がスムーズに行えるようにしてしまいます。 (Arduino Micro のブートローダを書き換えたら、Arduino IDE がよしなにしてくれるため、追加で書き込み放題となります)

Arduino UNO を AVR ライター化

MacArduino IDE を起動し、Arduino UNO と USB ケーブルをつないで以下画像の通り、 ボード: "Arduino/Genuino Uno" シリアルポート: "Arduino/Genuino UNO" 書込装置: "AVRISP mkII" と接続設定を行います。

f:id:kumak1:20161210174543p:plain

Arduino IDE には AVR ライターのサンプルプログラム(スケッチ)が収録されているので、これを選択、Arduino UNO へと書き込みを行います。

f:id:kumak1:20161210174935p:plain

f:id:kumak1:20161210175715p:plain

これで Arduino UNO の準備は完了です。

Pro Micro を Arduino Micro 化

早速 Arduino UNO を使って Pro Micro へ書き込みをできる様、以下の通りに接続します。

UNO Pro Micro
GND GND
10 RST
5V VCC
13 (SCLK) 15 (SCLK)
12 (MISO) 14 (MISO)
11 (MOSI) 16 (MOSI)

実際に接続した図です。

f:id:kumak1:20161210200322j:plain

接続できたら、ボード: "Arduino/Genuino Micro" シリアルポート: "Arduino/Genuino UNO" 書込装置: "Arduino as ISP" と設定し、ブートローダを書き込みます。

f:id:kumak1:20161210203106p:plain

ブートローダが書き込み終えたら、Arduino UNO はもう取り外してしまって構いません。

ハードウェアの実装

まずはアルミ板からですが、金属加工の道具を持っていないため、下の図面を Illustrator でおこして(CADとかよくわからない・・)東急ハンズのお兄さんに加工してもらいます。

f:id:kumak1:20161210214054p:plain

アルミ板の加工が終われば、スイッチの組み付けを行います。穴にピッタリはまる!とはならないため、厚手の両面テープをアルミとスイッチの間に貼り付けることで、隙間を埋め接着します。 (接着剤は取り外しできなくなるので避けた。グルーガンを持っていれば、それが最適解な気がする。)

f:id:kumak1:20161210200335j:plain

接着物はやはりピンセットで作業を行うと捗ります。

f:id:kumak1:20161210200352j:plain

接着し終えたら、 Pro Micro とクリップやジャンパピンでつなげて仮実装します。 Mac と Pro Micro も USB ケーブルで繋いでしまいましょう。

f:id:kumak1:20161211122049j:plain

ソフトウェアの実装

短押し長押し で挙動が変わるキーを実装したいなと思いました。(具体的には Karabiner で設定する様な、短押しで かな/カナ 変更、長押しで となるキー操作ができるようにしたい) サンプルプログラムは以下の通り。

#include <Keyboard.h>

const uint8_t PIN_MAX   = 21;     // Pro Micro のPIN数
const uint8_t PIN_R_B_0 = 9;      // 仮実装で使う Pro Micro のPIN番号

uint8_t keyPressedTimes[PIN_MAX]; // キーボードが押下されている時間を記録する

void setup() {
  // キーボートとして認識させるPINの初期化
  for(uint8_t i = 0; i <= PIN_MAX; i++) {
    switch (i) {
        case PIN_R_B_0:
            pinMode(i, INPUT_PULLUP);
            keyRelease(i);
            break;
    }
  }

  Keyboard.begin();
}

void loop() {
  keyRead(PIN_R_B_0, 'a', 'b');

  delay(10);
}

/**
 * PIN の状態から、キーボードを動作させる
 *
 * @param uint8_t pinNumber   キーボートとして認識するPINの番号
 * @param char onClickKeyCode 短押し時のキーの振る舞い
 * @param char onPressKeyCode 長押し時のキーの振る舞い
 */
void keyRead(uint8_t pinNumber, char onClickKeyCode, char onPressKeyCode) {
  if (digitalRead(pinNumber) == LOW) {
    if (isKeyLongPress(pinNumber)) {
      Keyboard.press(onPressKeyCode);
    } else {
      keyPress(pinNumber);
    }
  } else if (isKeyPress(pinNumber)) {
    if(isKeyLongPress(pinNumber)) {
      Keyboard.release(onPressKeyCode);
    } else {
      Keyboard.write(onClickKeyCode);
    }
    keyRelease(pinNumber);
  }
}

/**
 * キーが押されたことを記録する
 * 
 * @param uint8_t pinNumber キーボートとして認識するPINの番号
 */
void keyPress(uint8_t pinNumber) {
  if (!isKeyLongPress(pinNumber)) {
    keyPressedTimes[pinNumber]++;
  }
}

/**
 * キーが離れたことを記録する
 * 
 * @param uint8_t pinNumber キーボートとして認識するPINの番号
 */
void keyRelease(uint8_t pinNumber) {
  keyPressedTimes[pinNumber] = 0;
}

/**
 * キーが押されているか確認する
 * 
 * @param uint8_t pinNumber キーボートとして認識するPINの番号
 */
bool isKeyPress(uint8_t pinNumber) {
  return keyPressedTimes[pinNumber] > 0;
}

/**
 * キーが長押しされているか確認する
 * 
 * @param uint8_t pinNumber キーボートとして認識するPINの番号
 */
bool isKeyLongPress(uint8_t pinNumber) {
  return keyPressedTimes[pinNumber] > 20;
}

プログラムの書き込み

Mac と Pro Micro をUSBケーブルで接続し、ボード: "Arduino/Genuino Micro" シリアルポート: "Arduino/Genuino Micro" 書込装置: "AVRISP mkII" と設定し、プログラムを書き込みます。

f:id:kumak1:20161211015731p:plain

書き込み後にキーボードを押してみると、短押し時には a 、長押し時には b が入力されるようになりました!

おわりに

自作キーボードの良さは拡張性の高さにあります。 自分で好き勝手にできるので、自分の動作環境に依存するものを容赦なく作れるのが魅力でもあります。

  • ESC キーなどの単一機能を実装
  • 短押し長押し で挙動が変わるキーを実装して Karabiner への依存を減らす
  • SHIFT のトグルボタン(iOSのshift 2回押しみたいな挙動)の実装
  • API連携による、定型文tweet、プロダクトのデプロイ
  • コンソール連携による、Git pull、タスクランナーの動作

などなど、作りたい・作れるものを挙げればキリがありません。 時間とスキルの関係上、「完璧に作り終えた!」という段階までできなかったのが悔やまれますが、あとは実際に使用してみながら調整して行こうと思います。

明日のアドベントカレンダーの執筆者は zipper さんです。めっちゃテッキーな話をしてくれそうだー。

参考記事

Unity の Cube を使う時、側面の天地が揃う UV 作った

unity 技術 ゲーム制作

Unity の Cube、便利ですね。 とりあえずイメージを掴むためのワイヤーフレームとして、ポンっと追加できるの最高です。

そのまま使う時の、そうじゃない感

ただ、ベタ塗りの色を置いておく場合は良いのですが、テクスチャ画像を貼り付けると、 「違う、そうじゃない」ということもありがちです。

例としては、壁を Cube で作ってテクスチャを当てた際、 側面の上下が反転してしまうのでそのままだとチグハグになりがちです。

f:id:kumak1:20161009173713p:plain こんな感じ。矢印は全て上を向いてもらいたい。

そこで、パパっと UV を書きました。

UV について

UVの概念についてはこちらがわかりやすいかな answers.unity3d.com

要は、ポリゴンの頂点が、テクスチャのどの座標と対応するか、の対応表です。

作成した UV

こんな感じ。全部 Start に書かず、 Awake に書くことで描画遅延をなく・・せてるはず。

using UnityEngine;
using System.Collections;

public class WallUv : MonoBehaviour {
    Vector2[] uv = new Vector2[24];

    void Awake()
    {
        // 左 : -x
        uv[2].x = 1.0f; uv[2].y = 1.0f;
        uv[3].x = 0.0f; uv[3].y = 1.0f;
        uv[0].x = 1.0f; uv[0].y = 0.0f;
        uv[1].x = 0.0f; uv[1].y = 0.0f;

        // 上 : +y
        uv[4].x = 0.0f; uv[4].y = 0.0f;
        uv[5].x = 1.0f; uv[5].y = 0.0f;
        uv[8].x = 0.0f; uv[8].y = 1.0f;
        uv[9].x = 1.0f; uv[9].y = 1.0f;

        // 後 : -z
        uv[23].x = 1.0f; uv[23].y = 0.0f;
        uv[21].x = 0.0f; uv[21].y = 1.0f;
        uv[20].x = 0.0f; uv[20].y = 0.0f;
        uv[22].x = 1.0f; uv[22].y = 1.0f;

        // 前 : +z
        uv[19].x = 1.0f; uv[19].y = 0.0f;
        uv[17].x = 0.0f; uv[17].y = 1.0f;
        uv[16].x = 0.0f; uv[16].y = 0.0f;
        uv[18].x = 1.0f; uv[18].y = 1.0f;

        // 下 : -y
        uv[15].x = 1.0f; uv[15].y = 0.0f;
        uv[13].x = 0.0f; uv[13].y = 1.0f;
        uv[12].x = 0.0f; uv[12].y = 0.0f;
        uv[14].x = 1.0f; uv[14].y = 1.0f;

        // 右 : +x
        uv[6].x  = 0.0f; uv[6].y  = 0.0f;
        uv[7].x  = 1.0f; uv[7].y  = 0.0f;
        uv[10].x = 0.0f; uv[10].y = 1.0f;
        uv[11].x = 1.0f; uv[11].y = 1.0f;
    }
        
    void Start () {
        MeshFilter mf = gameObject.GetComponent<MeshFilter>();
        mf.mesh.uv = uv;
    }
}

実行結果

スクリプトを cube に Add Component して実行します。と、狙ったとおり側面は全てY軸の方向を天としました。 バッチリですね。 f:id:kumak1:20161009175022p:plain

雑記

でもま、ゲームで本気のモデルを作るときは、Cube ではなく、専用モデルとUV Blender 等々で生成、専用テクスチャをゴニョゴニョ・・なので、実用性は謎です。

UnityとPlayMakerを触り始めたのでカスタムアクション作った

ゲーム制作 unity playmaker

ゲームを継続して作っている。 じっくりと腰を据えて遊ぶ大作・・!というのも憧れはするけども、そういうのは時間も資金も潤沢な専業の会社に任せるほかない。

  • 数分脳汁を垂らしながらプレイするもの
  • 何かのながらでやるもの
  • ちょっとした時間つぶしでやるもの

こういったものが少人数開発だと狙うべき所かなぁという感触をもっている。 (自分自身のスキル的にも限界ががが) となると、いかの条件がでてくる。

  • PC・スマホ双方ビルドできる
  • 2D・3D両方共 同じ感覚で作れる
  • エコシステムが活発なもの

こうなると、Unity だなぁという感じになった。 (神こロしでは JavaScript のゲームライブラリを魔改造したけども、3Dやエコシステムに難ありだった)

Unity だけでも作りやすくはある。 んだけども、せっかくなのでロジックをもっとビジュアライズして、全体の処理をイメージしやすくしたり、 バグを発見・潰しやすくするために PlayMaker というプラグインを導入することにした。

こんな感じに矢印を繋げれば処理を作る事ができる。

f:id:kumak1:20160914221458p:plain

構成要素は大体以下の3つ。

  • ステート
    • 今どんな状態か(歩く・走る etc..)
  • イベント
    • どんな時にアクションを動作させるか(十字キーを押した・壁にぶつかった etc..)
  • アクション
    • どんな行動を起こすか(ステートの移動 ・HPを減らす etc)

イベント・アクションは初期状態で相当な数が用意されていて、これだけで大体のことができてしまう。 けれど、それらは単機能でかつ組み合わせるにも煩雑な過程を経る必要がある(一旦オブジェクト内変数に値を待避させる)など、逆に面倒なことがちょこちょことある。

けれど上手くつくられているもので、これらは自由に追加することが可能だ。 用意されている処理を Edit Script などからエディタで開き、参考にしながらオレオレ イベント・アクションを追加してサイキョーの開発環境にしていける。 今回試しに作ったのは以下2つ。

  • BoolOperatorTest
    • はじめから用意されている BoolTest の拡張。
    • BoolTest は以下の値をセットでき、変数の状態に応じてイベント発火できる。
      • 変数(bool)
      • true 時に動作するイベント
      • false 時に動作するイベント
    • この拡張では、以下の値をセットでき、より柔軟に変数の状態に応じてイベント発火できる。
      • 変数1(bool)
      • 変数2(bool)
      • 論理演算子
      • true 時に動作するイベント
      • false 時に動作するイベント
  • FsmMultipleStateTest
    • はじめから用意されている FsmStateTest の拡張
    • FsmStateTest 以下の値をセットでき、ステートの状態に応じてイベント発火できる。
      • 対象オブジェクト(fsm)
      • 予測するステート名(string)
      • true 時に動作するイベント
      • false 時に動作するイベント
    • この拡張では、以下の値をセットでき、より柔軟に変数の状態に応じてイベント発火できる。
      • 対象オブジェクト(fsm)
      • 予測するステート名(string)をカンマ区切りで複数
      • true 時に動作するイベント
      • false 時に動作するイベント

既存のものをちょいちょいと組み合わせるだけで、より自分が使いやすいものをシュッと作れて、スッと利用できるので大変よい感じである。 いくらGUIで大体作れるからといっても、効率良くしたり、少ない記述で見通し良くしようとしたらば、結局は設計とプログラムは必要なのだなとしみじみしたのであった

BoolOperatorTest.cs

using UnityEngine;
namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory(ActionCategory.Logic)]
    [Tooltip("Performs boolean operations on 2 Bool Variables.")]
    public class BoolOperatorTest : FsmStateAction
    {
        public enum Operation
        {
            AND,
            NAND,
            OR,
            XOR
        }

        [RequiredField]
        [Tooltip("The first Bool variable.")]
        public FsmBool bool1;

        [RequiredField]
        [Tooltip("The second Bool variable.")]
        public FsmBool bool2;

        [Tooltip("Boolean Operation.")]
        public Operation operation;

        [Tooltip("Event to send if the Bool variable is True.")]
        public FsmEvent isTrue;

        [Tooltip("Event to send if the Bool variable is False.")]
        public FsmEvent isFalse;

        [UIHint(UIHint.Variable)]
        [Tooltip("Store the result in a Bool Variable.")]
        public FsmBool storeResult;

        [Tooltip("Repeat every frame while the state is active.")]
        public bool everyFrame;

        public override void Reset()
        {
            bool1 = false;
            bool2 = false;
            operation = Operation.AND;
            storeResult = null;
            everyFrame = false;
            isTrue = null;
            isFalse = null;
        }

        public override void OnEnter()
        {
            DoBoolOperator();

            if (!everyFrame)
            {
                Finish();
            }
        }

        public override void OnUpdate()
        {
            DoBoolOperator();
        }

        void DoBoolOperator()
        {
            var v1 = bool1.Value;
            var v2 = bool2.Value;

            switch (operation)
            {
                case Operation.AND:
                    storeResult.Value = v1 && v2;
                    break;

                case Operation.NAND:
                    storeResult.Value = !(v1 && v2);
                    break;

                case Operation.OR:
                    storeResult.Value = v1 || v2;
                    break;

                case Operation.XOR:
                    storeResult.Value = v1 ^ v2;
                    break;
            }

            Fsm.Event(storeResult.Value ? isTrue : isFalse);
        }
    }
}

FsmMultipleStateTest

using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
    [ActionCategory(ActionCategory.Logic)]
    [ActionTarget(typeof(PlayMakerFSM), "gameObject,fsmName")]
    [Tooltip("Tests if an FSM is in the specified State.")]
    public class FsmMultipleStateTest : FsmStateAction {
        [RequiredField]
        [Tooltip("The GameObject that owns the FSM.")]
        public FsmGameObject gameObject;

        [UIHint(UIHint.FsmName)]
        [Tooltip("Optional name of Fsm on Game Object. Useful if there is more than one FSM on the GameObject.")]
        public FsmString fsmName;

        [RequiredField]
        [Tooltip("Check to see if the FSM is in this state.")]
        public FsmString stateName;

        [Tooltip("Event to send if the FSM is in the specified state.")]
        public FsmEvent trueEvent;

        [Tooltip("Event to send if the FSM is NOT in the specified state.")]
        public FsmEvent falseEvent;

        [UIHint(UIHint.Variable)]
        [Tooltip("Store the result of this test in a bool variable. Useful if other actions depend on this test.")]
        public FsmBool storeResult;

        [Tooltip("Repeat every frame. Useful if you're waiting for a particular state.")]
        public bool everyFrame;

        // store game object last frame so we know when it's changed
        // and have to cache a new fsm
        private GameObject previousGo;

        // cach the fsm component since that's an expensive operation
        private PlayMakerFSM fsm;

        public override void Reset() {
            gameObject = null;
            fsmName = null;
            stateName = null;
            trueEvent = null;
            falseEvent = null;
            storeResult = null;
            everyFrame = false;
        }

        public override void OnEnter() {
            DoFsmStateTest();

            if (!everyFrame)
            {
                Finish();
            }
        }

        public override void OnUpdate() {
            DoFsmStateTest();
        }

        void DoFsmStateTest() {
            var go = gameObject.Value;

            if (go == null)
            {
                return;
            }

            if (go != previousGo)
            {
                fsm = ActionHelpers.GetGameObjectFsm(go, fsmName.Value);
                previousGo = go;
            }

            if (fsm == null)
            {
                return;
            }

            storeResult.Value = stateName.Value.Split(',').Any(s => s == fsm.ActiveStateName);

            Fsm.Event(storeResult.Value ? trueEvent : falseEvent);
        }


    }
}

製作期間半年ちょいでブラウザゲーム「神こロし」をリリースした。

ゲーム制作 技術

ひょんなことがきっかけで、「神こロし」というブラウザゲームを作っているし、更新している事への備忘録

きっかけ

日本ではイラストレーターで知られるD.Kさんが、なにやら個人でゲームを作っていることをTwitterで知る。 どうやらキャライラストを募集していて、寄稿するとゲーム内に登場できるみたい。

Pixiv Fantasia とかやってた私としては参加せざるを得ないので、もにょもにょとした気持ちをツイート。 したら、なぜかプログラムの話題になって、あれよあれよと制作側になる。

体制は?

D.K さんがコンセプチュアルデザイン、マップデザイン、アートワーク。 私は開発だとかゲームバランスとか最終意思決定。 実際はゲーム制作進行のイロハを丁寧に教わってます・・

どんな風に作ったの?

2人でワヤワヤ話して方向性を決めて、D.Kさんが以下の様なマップを作る

うまいこと実装していくというサイクル

つくっていて思うこととか

単純に作っていて楽しいし、 いろんな絵描きの人つながり増えたし、(今までニコ動関連しかつながり広がってなかった) ゲームをキッカケとして盛り上がったりお友達になっているのをみるとほっこりする。 「繋がり」っていいなと

技術者としてのメリット

そこそこな規模で、自分で好きなようにできるプロダクトを持つのは初めてなのですごい勉強になってる。 開発も、インフラも、デザイナも、ディレクションも、カスタマーサポートも全部自分。 「職場で身近な人はこうやってたよな・・」で手探りでやっているが、 他業種の人の気持ちになれて大変よい。 人に優しくなれる。

これからについて

地味なアップデートを繰り返している。 この世界観とかを引き継いだ3Dゲームを作る話にもなっているので、頑張っていくぞという気持ちがある。

capistranoでデプロイ先にユーザ指定してwheneverを適用させたい

技術

この記事は Pepabo Advent Calendar 2015 - Qiita の19日目の記事です。

capistranoRuby で簡潔によしなにしてデプロイしてくれるので大変便利ですね。拡張ライブラリも豊富ですし Ruby の経験がほとんどなくてもそれとなく記述できて素晴らしいです。(ぺちぱーなもので・・) whenever も cron を Ruby で書けるっていいですね。バージョン管理できるし、レビューも受けやすいし、なにより crontab -r がもう怖くないw 基本的にすんなりと設定できるのですが、掲題の「capistranoでデプロイ先にユーザ指定してwheneverを適用」の時にちょっと詰まったので、備忘録がてらに記事にします。

なんでユーザ指定したいの

ユーザを分ける事自体に疑問を持つ方は、こちらの記事を読むと良さそう。 用は、デプロイする人はデプロイするだけ、バッチ実行する人はバッチ実行するだけと分けたいのです。 セキュリティ的にも、あとで調査等々する場合にも役に立ちます。 www.atmarkit.co.jp

前提

デプロイ先に以下が導入済みだとします。

  • rbenv 導入済み
  • .ruby-version ファイルで Ruby のバージョンを記録
  • バッチ実行用ユーザ(batch)がいる

やりたいこと

  1. capistrano でデプロイし、以下を実行
  2. デプロイ先で、bundle install
  3. デプロイ先で、whenever 実行

具体的には

whenever を素で実行する際の -u オプションを、capistrano でデプロイ時に適用したいわけです。

% bundle exec whenever --help
Usage: whenever [options]
    -i [identifier],                 Default: full path to schedule.rb file
        --update-crontab
    -w, --write-crontab [identifier] Default: full path to schedule.rb file
    -c, --clear-crontab [identifier]
    -s, --set [variables]            Example: --set 'environment=staging&path=/my/sweet/path'
    -f, --load-file [schedule file]  Default: config/schedule.rb
    -u, --user [user]                Default: current user
    -k, --cut [lines]                Cut lines from the top of the cronfile
    -r, --roles [role1,role2]        Comma-separated list of server roles to generate cron jobs for
    -v, --version

では capistrano の設定結果から。

Capfile.rb

# Include tasks from other gems included in your Gemfile
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'whenever/capistrano'

rbenv, bundler, whenever の gem を追加

deploy.rb

# rbenv の設定
set :rbenv_ruby, File.read('.ruby-version').strip # rbenvが.ruby-versonのRubyを使うようになる
set :rbenv_path, '/usr/local/rbenv'               # デプロイ先のrbenvのpathを指定

# bundler の設定
set :bundle_path, -> { release_path.join('vendor/bundle') } # sharedよりもreleaseのパス指定の方が、切り戻しが必要な際に楽

# whenever の設定
set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables} --user batch" }

わけわからないのは多分 whenever の設定だけかと思います。 ライブラリのソースコードを見てみると、whenever の実行オプションを指定している箇所があります。 whenever/whenever.rake at 334cfa01f373006cc032e23907b1777a8ea3f3b0 · javan/whenever · GitHub

これに --user batch を付け加えているだけです、簡単だー。 バッチユーザがデプロイユーザと同じグループに所属しているならばこれでOKですね。 もしダメな場合、以下のような荒技を使えば大丈夫・・ デプロイユーザにsudoer権限が必要になりますが、これでなんとかなってしまいます。

set :whenever_command, ->{ [:sudo, "#{fetch(:rbenv_path)}/bin/rbenv", :exec, :bundle, :exec, :whenever] }

ぷらすあるふぁ

実はこれをいじっていた当初は whenever をユーザ指定して実行がなかなかできず、task を半ば自前で作ってしまっていました。 (調度 set :whenever_update_flags のところで。tsushikazu 先輩の優しいアドバイスで解決)

「やりたいことができそうなのにできない」時は、コードリーディングをすると大体のことは解決できるので、怖がらず・面倒くさがらずにGitHubを覗くのが近道みたいです。 綺麗なコードを読むのはエンジニアの成長には欠かせませんしね。 まだまだ若手感を出して頑張っていこう。

vagrantの共有フォルダのマウントオプションについて

技術

最近、趣味で Laravel を使い始めて気付いたのでメモ。

私はvagrantで開発する際、アプリのコードはマウントして作業しています。 Laravel ではちょうど以下のようなディレクトリ構造ですね。

work
├─ application
│   ├─ app
│   │   ├─ strage
│   │    ...
│   │   
│   ├─ bootstrap
│   └─ public
└─ vagrant
     ├─ Vagrantfile
      ...

Laravel を動作させるには、work/application/app/strage ディレクトリの権限を緩めに(777とか)する必要があります。 ココでつまりました。

緩めに設定したはずなのに、動作させたときに生成されるファイル(セッションとかログとか)がいちいち書き込み禁止で生成・エラーになってしまうのです。。

Vagrantfile のマウントの記述は以下でした。

config.vm.synced_folder "../application", "/var/www/application"

解決方法

マウントオプションを設定して、ここから権限を緩める必要があったようです。

config.vm.synced_folder "../application", "/var/www/application", mount_options: ['dmode=777','fmode=777']

ネット上ではあまりこれに言及したTipsがなかったので、誰かの役に立てば・・ (基本的な事過ぎなのかもしれなくて恥ずかしいですが

vagrantのprivate_networkとforwarded_portのメモ

技術

Vagrantfile を書いていると、ipとportの関係をうっかり間違えてしまうことがあるので備忘メモ

config.vm.network :private_network, ip: "192.168.0.10"
config.vm.network :forwarded_port, guest: 3306, host: 13306

上記のように、ipとportをVMで割り当てると、 localhost でアクセスする場合はフォワードしたポートを利用し、 private_network でアクセスする場合は本来のポートでアクセスする。

イメージとしてはこんな感じ f:id:kumak1:20150525230248p:plain