Amazon EC2に構築したCentOS7のホスト名(hostname)を再起動しても戻らないようにする

EC2上にAWS MarketplaceのCentOS7を入れるとホスト名はデフォルトでこんな感じでした。

# hostname
ip-XX-XX-XX-XX.ap-northeast-1.compute.internal


FQDNチックな名前のhostnameにしたかったので、とりあえず以下のコマンドを実行したのですが、
OSを再起動したら元の残念なホスト名になっていました。

# hostnamectl set-hostname ホスト名

ちなみにこのコマンドは内部的には/etc/hostnameのホスト名を変更しているだけなので、
直接このファイルを編集してもOKですw


ホスト名が元に戻る原因は、OS起動時に*1cloud-initがhostnameの自動セットをやっていたからでした。
というわけでその処理を無効化します。
具体的には/etc/cloud/cloud.cfgにあるupdate_hostnameをコメント化して無効化してから、/etc/hostnameのホスト名を変更すると恒久的に変更されます。
僕の場合はhostnameは変更済みなので、後はこれだけです。

# vi /etc/cloud/cloud.cfg
cloud_init_modules:
 - migrator
 - bootcmd
 - write-files
 - growpart
 - resizefs
 - set_hostname
 # - update_hostname
 - update_etc_hosts
 - rsyslog
 - users-groups
 - ssh

これで再起動してもホスト名が変更されたままになりました。
CentOS7の新しいコマンドたちなんか好きになれないw

*1:AWS/EC2をはじめとするクラウド環境でLinuxを効率良く管理するための様々な機能を提供するパッケージ

Pythonでサードパーティーのライブラリを使わずにマルチパートフォームデータ送信で画像をアップロードする

以前の(それなりにJava7・Java8のAPIを使いつつSpring Bootでファイルアップロードの処理を書いてみた - 猫にWeb)で作成したサーバーサイドのコードに対して、画像をアップロードするクライアント側のPythonのコードです。
現場の制約でサードパーティーのライブラリを使うことができないので、自前でマルチパートフォームデータの構造を作って画像をアップロードしています。


マルチパートフォームデータとは、HTTPリクエストで複数のフォームデータ(マルチパート)を送るための形式です。大別するとHTTPヘッダ部、ボディ部、フッタ部で構成されています。さらに各データをバウンダリーという文字列で区切り、画像ファイルデータはバイナリデータにして送信します。
具体的には以下のような構成になります。バイナリデータ部分は割愛しています。
*1ブラウザのREST Client系の拡張機能では自動的に内部で構成して送信しています。

POST http://127.0.0.1:8080/upload HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: identity
content-type: multipart/form-data; boundary=PpjeR7HcV42hrmr8XhuRA4xgDZRBdA
content-length: 49753

--PpjeR7HcV42hrmr8XhuRA4xgDZRBdA
Content-Disposition: form-data; name="id"

sample.jpg
--PpjeR7HcV42hrmr8XhuRA4xgDZRBdA
Content-Disposition: form-data; name="image"; filename="sample.jpg"
Content-Type: image/jpeg

[binary data]

ソースコードはこんな感じです。

import http.client
import mimetypes
import string
import random

def post_multipart(host, port, selector, fields, files):
    content_type, body = encode_multipart_formdata(fields, files)

    if(selector.find('https') == 0):
        h = http.client.HTTPSConnection(host, port)
    else:
        h = http.client.HTTPConnection(host, port)

    h.putrequest('POST', selector)
    h.putheader('content-type', content_type)
    h.putheader('content-length', str(len(body)))
    h.endheaders()
    h.send(body)
    response = h.getresponse()
    return response.read()

def encode_multipart_formdata(fields, files):
    BOUNDARY_STR = get_random_str(30)
    CHAR_ENCODING = "utf-8"
    CRLF = bytes("\r\n", CHAR_ENCODING)
    L = []
    for (key, value) in fields.items():
        L.append(bytes("--" + BOUNDARY_STR, CHAR_ENCODING))
        L.append(bytes('Content-Disposition: form-data; name="%s"' % key, CHAR_ENCODING))
        L.append(b'')
        L.append(bytes(value, CHAR_ENCODING))
    for (key, value) in files.items():
        filename = value['filename']
        content = value['content']
        L.append(bytes('--' + BOUNDARY_STR, CHAR_ENCODING))
        L.append(bytes('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename), CHAR_ENCODING))
        L.append(bytes('Content-Type: %s' % get_content_type(filename), CHAR_ENCODING))
        L.append(b'')
        L.append(content)
    L.append(bytes('--' + BOUNDARY_STR + '--', CHAR_ENCODING))
    L.append(b'')

    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=' + BOUNDARY_STR
    return content_type, body

def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

def get_random_str(length):
    return ''.join([random.choice(string.ascii_letters + string.digits) for i in range(length)])

使い方はこんな感じです。

if __name__ == '__main__':
    host = 'httpbin.org'
    selector = 'http://httpbin.org/post'
    port = '80'
    
    file_path = "sample.jpg"
    fields = {
              'id' : file_path
             }
    content = open(file_path, 'rb').read()
    files = {'image': {'filename': 'sample.jpg', 'content': content}}

    response = post_multipart(host, port, selector, fields, files)
    print(response.decode('utf-8'))

コードはここです。
https://github.com/necoyama3/file-upload-client-sample/blob/master/NonLibraryFileUpload.py

*1:ChromeではPostman等

それなりにJava7・Java8のAPIを使いつつSpring Bootでファイルアップロードの処理を書いてみた

仕事でファイルアップロードのクライアント側を書くことになったのですが、サーバー側はチームの別の人が担当になりました。
クライアント(Python)とサーバー(C#)の言語が違うのでこんな割り振りになりました。
ただ個人的にサーバー側も作った方がテスト含めて色々都合がいいので、サーバー側も自分で書く事にしましたw
さらにどうせなら勉強したいと思っていたテクノロジーも使いたいなと思って、Spring BootとJava7、Java8のファイル周りのAPIを使って実装することにしました。


一部改変していますが、主な仕様はこんな感じです。

  • URL
/upload
  • メソッド
POST
  • パラメータ
parameter type required
name text
image file
  • レスポンスフォーマット
{
    "id":"1",
    "result":"true",
    "message":"OK"
}
  • 画像データを受け取って、ユーザーディレクトリ直下に保存する


レスポンスを固定で書くバージョンはすぐできたので、さらに独自の仕様を以下のように追加しました。

  • httpでもhttpsでもOK
  • レスポンスのidは数字の連番が返るようにする
  • パラメータ
parameter type required
name text
image file
resetCount file ×

「resetCount」に「true」を入れるとidの連番をリセット(1から開始する)して、パラメータはあってもなくても構わない。


この数字の管理をテキストファイルで保持して、この辺の処理をJava7、8らへんのAPIを使って処理してみました。


ここからはちょっと実装時にハマったポイントです。
Spring Bootで@RequestParamはそのままだと必ず必須になってしまうので、Optionalを使って対処しました。

@RequestParam("resetCount") Optional<Boolean> resetCount


Spring Bootでファイルアップロードをするときは、application.ymlに以下のパラメータでファイルのサイズをある程度指定しておかないと、大きいファイルをアップするとエラーが発生します。

multipart:
    maxFileSize: 104857KB
    maxRequestSize: 104857KB


Spring Bootでhttps通信をするときはkeytoolで証明書を作ってアプリケーションディレクトリの直下に配置して、application.ymlに以下のように指定することで可能になります。

server:
    port: 8080
    ssl:
        key-store: "server.jks"
        key-store-password: "tomcat"
        key-password: "tomcat"


作ったソースはこちら
spring-boot-file-upload-sample

自分が使っているグローバルIPアドレスを一瞬で知る方法

先日インフラチームの人がネットワークの疎通ができているか確認するときに、
Google ChromeのURLバーに「ifconfig.me」と入力してたので気になって教えてもらった。


どうやらIPアドレスやらポート番号やらmimeやら色々取得できるサービスらしい。
「ifconfig.me」だけで以下の情報を取得してくれる。


さらにcurlコマンドで色々指定して取得することもできる

ちなみに調べてたら同じようなサービスで「ifconfig.co」というのもあった。
ちょっと情報量が少ないけどポイントは押さえていて、こちらはcurlに加えてwgetやfetchコマンドも使える。
たぶん使わないけどw


これからは疎通を確認するときはドヤ顔で「ifconfig.me」をさらっと使っていこうと思う。

AlfredあらためSpotlightのインデックスを再作成する

ある作業で「/opt」配下に一時ディレクトリを作成して、作業後に「rm -fr /opt」で何気なく消したら、
実は「Homebrew Cask」が「/opt」配下をツールを管理するディレクトリとして使っていました( ̄▽ ̄;)!!ガーン
ランチャーにAlfredを使っているのですが、もれなくツールが起動できなくなりましたw


ツールを再インストールしてAlfredを起動したのですが、再インストールしたツールが認識されませんでした。
そこで調べてみると公式サイトにこのように書かれていました。

1. Can Spotlight find what you're looking for?

If Spotlight cannot find the files either, go to the Advanced tab in Alfred's Preferences and click "Rebuild OS X Metadata". This is due to the fact that Alfred and Spotlight both use the metadata gathered by OS X to find results so if Spotlight can't find your file, it's likely that it isn't correctly indexed.

Why isn't Alfred finding the file I'm looking for?

3行でまとめると

  1. AlfredはSpotlightのインデックスを使っている
  2. Spotlightのインデックスを再作成する
  3. 結果Alfredのインデックスも更新される

というわけでSpotlightのインデックスを再作成します。
手順は以下になります。

1. システムの環境設定でSpotlightを選んで「プライバシー」タブをクリックする



2. 「+」ボタンかドラッグでハードディスクを登録する



3. 追加したハードディスクを「-」ボタンをクリックして削除する

作業はこれで終わりです。


これでSpotlightを起動すると以下のようにインデックスが再作成されます。

インデックスの再作成が完了したあとにAlfredを起動したら、
無事に再インストールしたツールが認識されていました。


やっぱりディレクトリを削除するときは事前に確認しないとダメですね。

Macでパスワードつきのzipファイルを作る 完全版

後輩のid:MoChiwakiが(Mac で zip圧縮 パスワード付き! - MoChiwakiブログ)というエントリを書いていたので僕も便乗してみる。
たぶん社内資料を会社に送付するときに必要になったんだと思う。

せっかくなのでMacでできるパスワードつきzipファイルの作り方を調べてみました。

1. zipファイルを作成して、後からパスワードをつける

id:MoChiwakiが紹介している方法ですが、
zipファイルを作成してから「zipcloak」というコマンドでパスワードを後付けします。
zipファイルの作成はFinder上なら右クリックで「"ファイル名"を圧縮」を選択するか、
ターミナル上ならzipコマンドでOKです。
ターミナル上で実行する場合は以下のようになります。

$ zip tmp.zip tmp.txt
  adding: tmp.txt (stored 0%)

$ zipcloak tmp.zip
Enter password:
Verify password:
encrypting: tmp.txt

2. zipコマンドだけでパスワードつきzipファイルを作成する

zipコマンドだけでもパスワードつきzipファイルを作成することができます。
zipファイルを作成するときにオプションを指定するだけです。

#file
zip -e 圧縮後のファイル名.zip ファイルのパス

#dir
zip -e -r 圧縮後のファイル名.zip ディレクトリのパス
# file
$ zip -e tmp.zip tmp.txt
Enter password:
Verify password:
  adding: tmp.txt (stored 0%)
# dir
$ zip -e -r tmp.zip tmp
Enter password:
Verify password:
  adding: tmp/ (stored 0%)
  adding: tmp/tmp.txt (stored 0%)

3. ツールでパスワードつきzipファイルを作成する

MacZip4winというツールを使うと、
Finder上からパスワードつきのzipファイルを作成することができます。
またこのツールでzipファイルを作ると、Windowsで解凍しても文字化けしないという特徴があります。
理由は圧縮時にファイル名をShift_JISで扱うからです。
また「.DS_Store」などMac特有のファイルも圧縮時に削除してくれます。


使い方は、MacZip4winを起動すると以下のような画面が立ち上がるので、
圧縮したいファイルおよびディレクトリをドラッグアンドドロップします。

そうするとパスワード入力の画面が出てくるのでパスワードを設定したら完了です。

オプションの「Encrypt」がパスワードの有無で、
「No Compress」は無圧縮でアーカイブのみ行います。


さて、ここまで色々見てきましたが、感想としてどれも地味に面倒くさいと思ったので、
こんな関数を書いてみました。殴り書きですが多分動くと思いますw

function ze() {
    if [ $# = 0 ]; then
        echo "usage: ze [./file] [./dirctory]"
    else
        arg=$1
        if [ `echo $arg | grep '\.'` ]; then
            file_name=`echo $arg | cut -d'.' -f1`
            zip -e "$file_name.zip" $arg
        else
            zip -e -r "$arg.zip" $arg
        fi
    fi
}

はてなダイアリーってGistの貼り付けできなかったっけ??
なんかscriptタグがうまく反映されなくてできなかった。


ともかくこれを.zshrcとかの設定ファイルに書いて、
若干コマンド量を減らしてパスワードつきzipファイルを作れるようになりました。

# file
$ ze カレントのファイル

# dir
$ ze カレントのディレクトリ
# file
$ ze tmp.txt
Enter password:
Verify password:
  adding: tmp.txt (stored 0%)
# dir
$ ze tmp
Enter password:
Verify password:
  adding: tmp/ (stored 0%)
  adding: tmp/tmp.txt (stored 0%)

まとめ
好きな方法でやればいいと思うw

Windows屋なら押さえておきたいTera Termの小技5選

作業用マシンがWindowsのときにLinux系のサーバーを操作するときは、
Tera TermPoderosaPuttyなどのエミュレーターを使うと思います。


今の現場ではセキュリティの都合で認可されているTera Termしか使えないのですが、
けっこう工夫せずに使っている人が多いので、俺的Tera Termを使うときに押さえておきたい小技5選を紹介します。
バージョンは4.86です。

1. 「TTX Kanji Menu」プラグインを入れる

このプラグインをインストールすると以下のようにメニューから文字コードを変更することができます。
Tera Term本体をインストール中に出てくる「コンポーネントの選択」でチェックを入れるとインストールされます。

これで接続したサーバーによって文字コードが違ってもすぐに切りかえれることができます。

2. ショートカットを覚える

小技ではないですが気にしませんw
よく使う以下のショートカットを覚えておくと操作効率が上がります。

  • コピー : [Alt] + [C]
  • 貼り付け : [Alt] + [V]
  • 新規接続 : [Alt] + [N]
  • セッションの複製 : [Alt] + [D]
  • ログアウト : [Ctrl] + [D]

特にセッションの複製とログアウトはかなりおすすめです。
ログアウトはスイッチしているユーザーのログアウトもサーバーからのログアウトもできます。

3 ショートカットを作って自動ログインする

今の現場はサーバーにつなぐためにセキュリティの都合で踏み台サーバーを経由しなければなりません。
毎回踏み台サーバーに接続する作業は面倒なのですが、
周囲の人は意外とサーバーのIPアドレス、ユーザー、パスワードが書かれたメモを開いて打ち込んでたりします。
そんな時は自動ログインができるショートカットがおすすめです。


方法はTere Termのショートカットを作り、右クリックでプロパティを開き、リンク先に以下のように設定することで自動ログインが可能になります。

# パスワード認証の場合
"C:\Program Files (x86)\teraterm\ttermpro.exe" [IPアドレス]:[ポート番号] /auth=password /user=[ユーザー名] /passwd=[パスワード]

# 公開鍵認証の場合
"C:\Program Files (x86)\teraterm\ttermpro.exe" [IPアドレス]:[ポート番号] /auth=publickey /user=[ユーザー名] /keyfile=[鍵ファイルのパス]

これでダブルクリックするだけサーバーに接続することができます。
あとはこのショートカットをランチャーなりスタートアップに入れておけばさらに起動が楽チンになります。

4 コマンドの履歴をログに保存して、またそのログを再生する

リリース作業のときなどに使える機能ですが、コマンドの履歴をローカルのファイルに保存することができます。
方法は簡単で、メニュー → 「ファイル」 → 「ログ」をクリックするとファイル保存のダイアログが表示されるので保存先を選ぶだけです。

ログへの保存がはじまるともう1つダイアログが開き、ここから一時停止や閉じるをクリックしてログ保存を停止することができます。

保存したログはメニュー → 「ファイル」 → 「ログを再生」でログファイルを選択すると履歴を再生することができます。


ちなみにサーバーにログを保存したいときは「script」コマンドを使うとできます。
使い方はscriptコマンドを実行して、作業が終わったらexitコマンドをするだけです。
これで同じ階層に履歴が出力された「typescript」という名前のファイルが作成されます。

5 複数のサーバーに対して同時に同じコマンドを実行する

Tera Termにはブロードキャストと呼ばれる機能があり、
この機能を使うと複数のサーバーに対して同じコマンドを同時に実行することができます。


方法は複数のサーバーにTera Termで接続している状態で、
どれか1つのウィンドウのメニュー → 「コントロール」 → 「ブロードキャスト」をクリックします。
以下のウィンドウが表示されるので複数のコマンドを実行したいサーバーを選択して、
テキストボックスにカーソルを当てて、コマンドを入力すると同時に実行されます。

かなり面白い機能なので是非お試しくださいw

おまけ

Tera Termに関係なく以下のコマンドとショートカットもよく使えます。

  • コマンドの履歴のインクリメンタルサーチ : [Crtl] + [R]
  • 画面クリア : [Ctrl] + [L]
  • watchコマンドでコマンドの繰り返し実行(Usage : watch -n 秒数 定期的に実行したいコマンド)

それでは快適なTera Termライフをお送りくださいw