クリエイター:メタボ兔

ウェブやアプリの開発者で利用する色な技術やサーバーや開発環境の設定について共有する場

Vagrantにmailhogの構築

概要

開発環境(Vagrant + ubuntu/trusty64)にメールの確認用でMailhogをインストールした話をまとめてみました。

Go言語のインストール

MailhogをインストールするためにGo言語をインストールします。

失敗

GUEST> sudo apt-get install golang-go

この方法でインストールするとOSのバージョンが低いせいかgoが1.2.1がインストールされてしまいます。このバージョンだとMailhogをインストールする際に以下のエラーが発生します。

package github.com/mailhog/MailHog
    imports github.com/gorilla/pat
    imports github.com/gorilla/context
    imports github.com/gorilla/mux
    imports context: unrecognized import path "context"
package github.com/mailhog/MailHog
    imports github.com/gorilla/pat
    imports github.com/gorilla/context
    imports github.com/gorilla/mux
    imports github.com/ian-kent/envconf
    imports github.com/ian-kent/go-log/log
    imports github.com/t-k/fluent-logger-golang/fluent
    imports github.com/tinylib/msgp/msgp
    imports github.com/philhofer/fwd
    imports github.com/mailhog/MailHog-Server/api
    imports github.com/gorilla/websocket
    imports net/http/httptrace: unrecognized import path "net/http/httptrace"

成功した物

使用する基本ツールをインストールします。

GUEST> sudo DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" install git

GO言語をダウンロードします。

GUEST> curl -O https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz

圧縮ファイルを解凍して/usr/localへ移動します。

GUEST> tar -xvf go1.9.1.linux-amd64.tar.gz
GUEST> sudo mv go /usr/local
GUEST> rm  go1.9.1.linux-amd64.tar.gz

環境設定を行います。

GUEST> touch /home/vagrant/.bash_profile
GUEST> echo "export PATH=$PATH:/usr/local/go/bin" >> /home/vagrant/.bash_profile
GUEST> echo `export GOPATH=/home/vagrant/workspace:$PATH` >> /home/vagrant/.bash_profile
GUEST> export GOPATH=/home/vagrant/workspace
GUEST> mkdir -p "$GOPATH/bin" 

Mailhogをインストール

Mailhogとmhsendmailをインストールします。

GUEST> go get github.com/mailhog/MailHog
GUEST> go get github.com/mailhog/mhsendmail
GUEST> sudo cp /home/juampy/gocode/bin/MailHog /usr/local/bin/mailhog
GUEST> sudo cp /home/juampy/gocode/bin/mhsendmail /usr/local/bin/mhsendmail

サービス登録ファイルを作成します。

GUEST> sudo vi /etc/init/mailhog.conf
description "mailhog-service"

start on started networking
stop on runlevel [!2345]

exec /usr/local/bin/mailhog -api-bind-addr 192.168.33.10:8025 -ui-bind-addr 192.168.33.10:8025 -smtp-bind-addr 127.0.0.1:1025

*「192.168.33.10」はホストからGUESTへ接続可能なIPです。127.0.0.1で設定するとHOSTから接続できないことを注意してください。

サーバーが起動時に実行するように設定します。

GUEST> sudo sysv-rc-conf mailhog on
GUEST> sudo sysv-rc-conf -list | grep mailhog

起動します。

GUEST> sudo service mailhog start

確認

http://192.168.33.10:8025」で接続してみます。

f:id:FattyRabbit:20210802233607p:plain

Vagrantの時刻のズレ

概要

Vagrantで開発環境を構成して起動時間が長くなるとPCのスリープなったりしたのが原因なのか時刻がずれたりしましたので、その対応方法を調べてみました。

ホスト川に時刻を同期させる方法

色々試しましたが、一番良い方法だと思います。Vagrantfileに以下の内容を追記し再起動します。

config.vm.provider :virtualbox do |vb|
  vb.customize ["setextradata", :id, "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled", 0]
end

その他

Dockerの環境はどうなったか。。。

Javaの呼び出し元を取得(Stacktrace)

概要

実装の時エラーのスタックトレース(Stack Trace)のように呼び出し元(階層)を表示したいとか表示したい場合がありますよね(実は自分もあまりないと思う)?その時やり方を紹介したいと思います。

ソースで説明

面倒なのでいきなりソース&コメントで何とかやります。

    /**
    * 呼び出し元のスタックを取得
    * 
    * @return Stacktraceの文字列
    */
    private String getCalleStack() {
        // RuntimeExceptionを利用してスタックトレースを取得
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        new RuntimeException().printStackTrace(pw);
        pw.flush();
        String stacktrace = sw.toString();
        
        // スタックトレースを文字列として取得し、改行で分割し、空行、"at"のみの行を捨てる
        List<String> lines = Arrays.stream(stacktrace.split("[ \t\n]"))
                .map(s -> s.trim())
                .filter(s -> s.length() > 0 && !s.equals("at") && !s.contains("java.lang.Thread"))
                .collect(Collectors.toList());

        // 例外名称を捨てる
        lines.remove(0);

        // それらを改行コードで接続する。
        return lines.stream().collect(Collectors.joining("\n"));
    }

取得したくないクラス名や内容があったら「filter」の条件を増やせば良いかと思いました。

MacOS自動アップデートの通知を無効にする

概要

会社の開発用のMac ProはOSのアップデートをしない方針のチームが多いと思います。今日は一般的な対応方法と追加処理を整理してみました。

一般的な対応

1.システム環境設定 → ソフトウェアアップデート → 詳細 を選択します

f:id:FattyRabbit:20210622092701p:plain

2.上記ウィンドウのチェックをすべて外します。チェックを外すことでOSが自動的に更新の確認をしなくなりますが、自分の好きなタイミングでアップデートできるので安心してください。

通知を無効化

一般的な対応をしても通知はいつも通り出るし、間違えてボタンを押すのが気になりますよね。ターミナルを開き以下のコマンドを実行します。

Catalinaの場合

$ sudo softwareupdate --ignore "macOS Catalina"

Big Surの場合

$ sudo softwareupdate --ignore "macOS Big Sur"

上記をセットしても更新があるとソフトウェアアップデートのアイコンに赤丸のバッチが表示されてしまいます。以下のコマンドで消すことは可能です。

$ defaults write com.apple.systempreferences AttentionPrefBundleIDs 0
$ killall Dock

ShellスクリプトのみでSentryへイベントを送信

概要

使用しているAPIが正常に動いているかチェックしてSlackへ通知することになりましたが、サーバーに勝手にライブラリ(モジュール)のインストールが出来なかったので以下のような素晴らしい物の利用ができませんでした。

blog.sentry.io

ソース

いきなりソースです。

#!/bin/bash

# Sentry info
SENTRY_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXX
SENTRY_SECRET=YYYYYYYYYYYYYYYYYYYYYYYYYY
SENTRY_PROJECTID=MMMMMMM
SENTRY_HOST=sample.com
SCRIPT_ARGUMENTS=$@
# API info
API_URL=http://sample.com/api/xxxx
API_TOKEN=XYXYXYXYXYXYXYXYXYXYXYXXY

# event payloads : https://develop.sentry.dev/sdk/event-payloads/
function sendSentryMsg()
{
    LEVEL=$1
    TYPE=$2
    HTTP_CODE=$3
    TRACE=$4
    MESSAGE=$5
    EVENT_ID=`openssl rand -hex 16`
    EVENT_TIMESTAMP=`date +"%Y-%m-%dT%H:%M:%S"`
    SENTRY_TIMESTAMP=`date +%s`

    # Create a native event
    response=$(curl -sS https://$SENTRY_KEY@xxxxxxx.ingest.sentry.io/api/$SENTRY_PROJECTID/store/ \
        -H 'Content-Type: application/json' \
        -H "X-Sentry-Auth: Sentry sentry_version=7,sentry_timestamp=$(date +"%s"),sentry_client=sentry-curl/1.0,sentry_key=${SENTRY_KEY}" \
        -d "{
            \"event_id\": \"$EVENT_ID\",
            \"culprit\": \"$0\",
            \"timestamp\": \"$EVENT_TIMESTAMP\",
            \"message\": \"$MESSAGE\",
            \"level\": \"$LEVEL\",
            \"tags\": {
                \"shell\": \"$SHELL\",
                \"server_name\": \"`hostname`\",
                \"path\": \"`pwd`\",
                \"http_code\": \"$HTTP_CODE\"
            },
            \"exception\": {
                \"values\": [
                    {
                    \"type\": \"$TYPE\",
                    \"value\": \"$TRACE\",
                    \"module\": \"__builtins__\"
                    }
                ]
            }
        }" > /dev/null 2>&1)
    # echo $response
}

function getJsonItem()
{
    JSON_STRING=$1
    ITEM_KEY=$2
    # grep -Eo '"$ITEM_KEY"[^,]*' | grep -Eo '[^:]*$'
    # | perl -pe 's/"text"://; s/^"//; s/",$//'
    echo $JSON_STRING | grep -Eo '"'$ITEM_KEY'"[^,]*' | grep -Eo '[^:]*$' | tr -d "\""
}

function checkVPPApi()
{
    {
      IFS= read -rd '' HTTP_BODY
      IFS= read -rd '' HTTP_STATUS
      IFS= read -rd '' CURL_STATUS
      IFS= read -rd '' CURL_ERROR_MSG
    } < <({ HTTP_BODY=$(curl -sSL $API_URL \
        -o /dev/stderr \
        -w "%{http_code}" \
        -H 'Content-Type: application/json' \
        -d "{
            \"token\": \"$API_TOKEN\"
        }" \
        ); } 2>&1; printf '\0%s' "$HTTP_BODY" "$?")

    # echo $HTTP_BODY
    # echo $HTTP_STATUS
    # echo $CURL_STATUS
    # echo $CURL_ERROR_MSG

    level="info"
    type="正常に終了されました。"
    trace="Success!!"
    message=""
    if [ $CURL_STATUS != "0" ]; then
        declare -a CURL_ERRORS=(
            ["1"]="CURLE_UNSUPPORTED_PROTOCOL"
            ["2"]="CURLE_FAILED_INIT"
            ["3"]="CURLE_URL_MALFORMAT"
            ["4"]="CURLE_URL_MALFORMAT_USER"
            ["5"]="CURLE_COULDNT_RESOLVE_PROXY"
            ["6"]="CURLE_COULDNT_RESOLVE_HOST"
            ["7"]="CURLE_COULDNT_CONNECT"
            ["8"]="CURLE_FTP_WEIRD_SERVER_REPLY"
            ["9"]="CURLE_REMOTE_ACCESS_DENIED"
            ["11"]="CURLE_FTP_WEIRD_PASS_REPLY"
            ["12"]="CURLE_FTP_ACCEPT_TIMEOUT"
            ["13"]="CURLE_FTP_WEIRD_PASV_REPLY"
            ["14"]="CURLE_FTP_WEIRD_227_FORMAT"
            ["15"]="CURLE_FTP_CANT_GET_HOST"
            ["16"]="CURLE_HTTP2"
            ["17"]="CURLE_FTP_COULDNT_SET_TYPE"
            ["18"]="CURLE_PARTIAL_FILE"
            ["19"]="CURLE_FTP_COULDNT_RETR_FILE"
            ["21"]="CURLE_QUOTE_ERROR"
            ["22"]="CURLE_HTTP_RETURNED_ERROR"
            ["23"]="CURLE_WRITE_ERROR"
            ["25"]="CURLE_UPLOAD_FAILED"
            ["26"]="CURLE_READ_ERROR"
            ["27"]="CURLE_OUT_OF_MEMORY"
            ["28"]="CURLE_OPERATION_TIMEDOUT"
            ["30"]="CURLE_FTP_PORT_FAILED"
            ["31"]="CURLE_FTP_COULDNT_USE_REST"
            ["33"]="CURLE_RANGE_ERROR"
            ["34"]="CURLE_HTTP_POST_ERROR"
            ["35"]="CURLE_SSL_CONNECT_ERROR"
            ["36"]="CURLE_BAD_DOWNLOAD_RESUME"
            ["37"]="CURLE_FILE_COULDNT_READ_FILE"
            ["38"]="CURLE_LDAP_CANNOT_BIND"
            ["39"]="CURLE_LDAP_SEARCH_FAILED"
            ["41"]="CURLE_FUNCTION_NOT_FOUND"
            ["42"]="CURLE_ABORTED_BY_CALLBACK"
            ["43"]="CURLE_BAD_FUNCTION_ARGUMENT"
            ["45"]="CURLE_INTERFACE_FAILED"
            ["47"]="CURLE_TOO_MANY_REDIRECTS"
            ["48"]="CURLE_UNKNOWN_TELNET_OPTION"
            ["49"]="CURLE_TELNET_OPTION_SYNTAX"
            ["51"]="CURLE_PEER_FAILED_VERIFICATION"
            ["52"]="CURLE_GOT_NOTHING"
            ["53"]="CURLE_SSL_ENGINE_NOTFOUND"
            ["54"]="CURLE_SSL_ENGINE_SETFAILED"
            ["55"]="CURLE_SEND_ERROR"
            ["56"]="CURLE_RECV_ERROR"
            ["58"]="CURLE_SSL_CERTPROBLEM"
            ["59"]="CURLE_SSL_CIPHER"
            ["60"]="CURLE_SSL_CACERT"
            ["61"]="CURLE_BAD_CONTENT_ENCODING"
            ["62"]="CURLE_LDAP_INVALID_URL"
            ["63"]="CURLE_FILESIZE_EXCEEDED"
            ["64"]="CURLE_USE_SSL_FAILED"
            ["65"]="CURLE_SEND_FAIL_REWIND"
            ["66"]="CURLE_SSL_ENGINE_INITFAILED"
            ["67"]="CURLE_LOGIN_DENIED"
            ["68"]="CURLE_TFTP_NOTFOUND"
            ["69"]="CURLE_TFTP_PERM"
            ["70"]="CURLE_REMOTE_DISK_FULL"
            ["71"]="CURLE_TFTP_ILLEGAL"
            ["72"]="CURLE_TFTP_UNKNOWNID"
            ["73"]="CURLE_REMOTE_FILE_EXISTS"
            ["74"]="CURLE_TFTP_NOSUCHUSER"
            ["75"]="CURLE_CONV_FAILED"
            ["76"]="CURLE_CONV_REQD"
            ["77"]="CURLE_SSL_CACERT_BADFILE"
            ["78"]="CURLE_REMOTE_FILE_NOT_FOUND"
            ["79"]="CURLE_SSH"
            ["80"]="CURLE_SSL_SHUTDOWN_FAILED"
            ["81"]="CURLE_AGAIN"
            ["82"]="CURLE_SSL_CRL_BADFILE"
            ["83"]="CURLE_SSL_ISSUER_ERROR"
            ["84"]="CURLE_FTP_PRET_FAILED"
            ["85"]="CURLE_RTSP_CSEQ_ERROR"
            ["86"]="CURLE_RTSP_SESSION_ERROR"
            ["87"]="CURLE_FTP_BAD_FILE_LIST"
            ["88"]="CURLE_CHUNK_FAILED"
            ["89"]="CURLE_NO_CONNECTION_AVAILABLE"
            ["90"]="CURLE_SSL_PINNEDPUBKEYNOTMATCH"
            ["91"]="CURLE_SSL_INVALIDCERTSTATUS"
            ["92"]="CURLE_HTTP2_STREAM"
            ["93"]="CURLE_RECURSIVE_API_CALL"
            ["94"]="CURLE_AUTH_ERROR"
            ["95"]="CURLE_HTTP3"
            ["96"]="CURLE_QUIC_CONNECT_ERROR"
            ["98"]="CURLE_SSL_CLIENTCERT"
        )

        level="fatal"
        type="接続エラーが発生しております。"
        trace="${CURL_ERRORS["$CURL_STATUS"]}"
        message=""
    elif [ $HTTP_STATUS -ge "400" ] && [ $HTTP_STATUS -lt "600" ]; then
        level="fatal"
        type="VPP側でエラーが発生しております。"
        trace="$HTTP_STATUS"
        # trace=$HTTP_BODY
        message=""
    fi

    # echo $level
    # echo $type
    # echo $HTTP_STATUS
    # echo $trace
    # echo $message
    # echo $HTTP_BODY

    if [ $level != "info" ]; then
        sendSentryMsg "$level" "$type" "$HTTP_STATUS" "$trace" "$message"
    fi
}

checkVPPApi

exit 0

Slackの連携

私から詳しく書く必要もないかなと思いますんで、参考サイトで。

qiita.com

docs.sentry.io