memo

思いついたこと、やってみたことをテキトウに残していく。

HEAD /

name last modified size description
archives/ 2017-09-23 01:19  
codes/ 2011-04-22 02:17  
images/ 2015-06-18 01:22  
patches/ 2015-04-07 00:11  
tags/ 2016-06-25 11:45  

HEAD / archives / 2017-09 / 2017-09-23.rst

flatpak で linux 版 steam client を動かす

linux 版の steam client は、 ubuntu 用のバイナリしか無い(らしい)とか、 32bit 版のライブラリが要求される(らしい)とかで、特にちょっとマイナーなディストリにインストールするのはクソめんどくさい(らしい)。

ということで、 flatpak を使って動かす。

steam client は flathub にあるので、まずはこの repository を追加する:

$ flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

そしたら steam client をインストール:

$ flatpak --user install flathub com.valvesoftware.Steam

で、実行:

$ flatpak run com.valvesoftware.Steam

HEAD / archives / 2017-05 / 2017-05-20_1.rst

rust で WSGI サーバーを書いてみる

cpython crate は rust から python インタプリタを呼び出すことにも使うことができる。

ということで、試しに雑 WSGI サーバーを書いてみた。

https://github.com/nakamuray/wsgi-server

取り敢えずテキトウに用意した django アプリケーションの admin 画面が表示される程度には動いたので、満足。

HEAD / archives / 2017-05 / 2017-05-20.rst

rust で python 拡張モジュールを作る方法

cpython crate を利用することで、 rust で python 拡張モジュールを簡単に書くことができる。

また、 setuptools-rust を利用することで、そうして書かれたモジュールを含む python パッケージを簡単に書くことができる。

ということで、試しにフィボナッチ数を計算する拡張モジュール (関数) を書いてみた。

https://github.com/nakamuray/fib

せっかくなのでマルチスレッドで並列計算させるようにしてみたけど、ちゃんと CPU 400% とか使えてるようなので、満足。

HEAD / archives / 2017-02 / 2017-02-20.rst

flatpak に入れた python アプリケーションの起動がやけに遅い

先日 flatpak に python アプリケーションを入れてみたわけだけど、 flatpak をかぶせることによる起動コストの増加はどんなもんだろうか、 と気になったので、 time を使ってテキトウに計ってみる。

普通に実行した場合:

(venv) $ time konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
konnyaku --help  0.74s user 0.06s system 99% cpu 0.806 total

0.8秒くらい。

続いて flatpak に入れたヤツ:

$ time flatpak run org.u7fa9.konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
flatpak run org.u7fa9.konnyaku --help  0.06s user 0.02s system 2% cpu 2.899 total

2.9秒くらい。

思ってた以上に遅い。何だこれ。

調査

ひとまず python ではなくて、明らかに一瞬で実行が終わるはずのコマンドを実行させてみる:

$ time flatpak run --command=/bin/true org.u7fa9.konnyaku
flatpak run --command=/bin/true org.u7fa9.konnyaku  0.06s user 0.02s system 36% cpu 0.229 total

0.2秒くらい。これが flatpak を立ち上げるのに必要なコスト。じゃあさっきの2秒近い差はどっから出てきた?

python アプリケーションで起動が思いのほか遅いというような場合、 .pyc ファイルを作成し忘れたケースが考えられる。 flatpak では runtime/application は read-only で mount されるため、パッケージング時に含めそびれると、実行時には作成できない (作成しても保存できない) ので、毎回 .py ファイルを読み込むことになる。

ファイルがあるか確認してみる。

$ flatpak run --command=/bin/sh org.u7fa9.konnyaku
~ $ ls -l /app/lib/python3.5/site-packages/konnyaku/
total 36
-rwxr--r--  231 nakamuray nakamuray         0 Jan  1  1970 __init__.py
drwxr-xr-x    2 nakamuray nakamuray         8 Jan  1  1970 __pycache__
-rwxr--r--    2 nakamuray nakamuray      6281 Jan  1  1970 cli.py
-rwxr--r--    2 nakamuray nakamuray      1599 Jan  1  1970 concurrentutils.py
-rwxr--r--    2 nakamuray nakamuray        39 Jan  1  1970 exceptions.py
-rwxr--r--    2 nakamuray nakamuray      3287 Jan  1  1970 models.py
-rwxr--r--    2 nakamuray nakamuray      3594 Jan  1  1970 tasks.py
~ $ ls -l /app/lib/python3.5/site-packages/konnyaku/__pycache__/
total 31
-rwxr--r--    2 nakamuray nakamuray       136 Jan  1  1970 __init__.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      6502 Jan  1  1970 cli.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      1915 Jan  1  1970 concurrentutils.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray       310 Jan  1  1970 exceptions.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      3957 Jan  1  1970 models.cpython-35.pyc
-rwxr--r--    2 nakamuray nakamuray      3004 Jan  1  1970 tasks.cpython-35.pyc

ちゃんとある...が、なんだこのタイムスタンプは?

原因

どうやら flatpak (が内部で使ってる ostree) はファイルの更新日時等々を保存しないらしく、 そのためそこからファイルを取り出すと 1970-01-01 00:00:00 (UTC), unix time で "0" が更新日時として設定されるらしい。

さて、 python は .pyc ファイル中に元のソースファイルの更新日時を保存していて、 これが現在のソースファイルの日時と異なっている場合はファイル内容に変更があると見なし、 .pyc を読み込まないようになっている。

で、全てのファイルの更新日時が 1970-01-01 00:00:00 に 変更 されてしまったため、 全ての .pyc ファイルでこのチェックに引っかかるようになってしまい、 また先述したように新規で .pyc ファイルを作成することも出来ないので、 結果として毎回 .py ファイルを読み込むようになってしまっているようだ。

その分が起動時間の差に現れていると考えられる。

対策

.pyc ファイル中に記録されている更新日時情報が 1970-01-01 00:00:00 を指すように、データを書き換える。

この情報は 4-7 bytes の範囲に書かれているようなので、 dd を使ってゼロ埋めしてやればよい。

ということで、前回のパッケージングスクリプトに以下のような変更を加えて作り直す:

diff -r 43abc9291d3b flatpak/build-konnyaku-runtime.bash
--- a/flatpak/build-konnyaku-runtime.bash       Mon Feb 20 15:21:54 2017 +0900
+++ b/flatpak/build-konnyaku-runtime.bash       Mon Feb 20 21:23:24 2017 +0900
@@ -53,6 +53,10 @@
 run_in_rootfs apk update
 run_in_rootfs apk add "${RUNTIME_PACKAGES[@]}" || true

+# XXX: fix .pyc's timestamp information to match "1970-01-01 00:00:00",
+#      files checked out from ostree will be.
+run_in_rootfs find /usr/lib/python3.5 -name "*.pyc" -exec dd if=/dev/zero of="{}" bs=4 seek=1 count=1 conv=notrunc ";" 2>/dev/null
+
 # create sdk based on runtime
 cp -a "${rootfs_dir}" "${sdk_dir}"/

diff -r 43abc9291d3b flatpak/build-konnyaku.bash
--- a/flatpak/build-konnyaku.bash       Mon Feb 20 15:21:54 2017 +0900
+++ b/flatpak/build-konnyaku.bash       Mon Feb 20 21:23:24 2017 +0900
@@ -22,5 +22,10 @@
 flatpak build "${build_location}" python3 -m venv /app
 flatpak build --share=network "${build_location}" /app/bin/pip install -U pip
 flatpak build --share=network "${build_location}" /app/bin/pip install "${URL}"
+
+# XXX: fix .pyc's timestamp information to match "1970-01-01 00:00:00",
+#      files checked out from ostree will be.
+flatpak build "${build_location}" find /app -name "*.pyc" -exec dd if=/dev/zero of="{}" bs=4 seek=1 count=1 conv=notrunc ";" 2>/dev/null
+
 flatpak build-finish "${BUILD_FINISH_ARGS[@]}" "${build_location}"
 flatpak build-export "${REPOSITORY}" "${build_location}" "${VERSION}"

結果

$ time flatpak run org.u7fa9.konnyaku --help
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --db-debug / --no-db-debug
  --help                      Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information
flatpak run org.u7fa9.konnyaku --help  0.06s user 0.02s system 7% cpu 1.139 total

直接実行した場合の0.8秒と比べて、0.3秒くらいの差。 true を実行した時の0.2秒にかなり近くなった。

余談

flatpak-builder にはコレと同様の対応処理が入っているようなので、 コイツで python アプリケーションが普通にパッケージングできるようになれば、こんなこと気にしなくてよくなるのかも。

HEAD / archives / 2017-02 / 2017-02-10.rst

Jinja2 の async support を試した

Jinja2 にバージョン 2.9 で async support が入ったとのこと。

  • Environment 作成時に引数 enable_async=True を渡すことで有効になる
  • テンプレート中の関数呼び出しが、 (結果が coroutine であれば?) 自動で await されるようになる
  • Template.render_async() を呼ぶことで、テンプレートをレンダリングする coroutine が得られる

ということで動かしてみる:

import asyncio
import jinja2


# テキトウに非同期な関数を用意する
async def do_something(value: int) -> int:
    await asyncio.sleep(1)
    return value + 1


async def main():
    # `enable_async=True` を渡して Environment 作成
    env = jinja2.Environment(enable_async=True)
    env.globals.update({
        'do_something': do_something,
    })
    tmpl = env.from_string('''
    {#- テンプレート中では、非同期関数は普通の関数と同じように呼び出せる #}
    result: {{ do_something(1) }}
    ''')

    # .render_async() で coroutine が得られる
    coro = tmpl.render_async()
    print(repr(coro))
    # coroutine を await することでレンダリング結果が手に入る
    txt = await coro
    print(txt)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

実行結果:

<coroutine object Template.render_async at 0x7ff2038688e0>

    result: 2

さて、ドキュメントには asyncio がどうのこうのと書いてあるが、 これは Template.render() などの同期的なメソッドが呼ばれた際に、 内部で asyncio の event loop を取得して coroutine を実行し、結果を得ているという話っぽい。

なので、 .render_async() などを呼んでいる分には、 asyncio 以外の event loop でも使えそう。

ということで twisted と組み合わせて動かしてみる:

import jinja2

from twisted.internet import defer


def sleep(delay: int) -> defer.Deferred:
    d = defer.Deferred()

    from twisted.internet import reactor
    reactor.callLater(delay, d.callback, None)

    return d


# テキトウに非同期な関数を用意する
async def do_something(value: int) -> int:
    await sleep(1)
    return value + 1


# Deferred を返す版も試しておく
def do_something_deferred(value: int) -> defer.Deferred:
    d = sleep(1)
    d.addCallback(lambda _: value + 1)
    return d


async def main():
    # `enable_async=True` を渡して Environment 作成
    env = jinja2.Environment(enable_async=True)
    env.globals.update({
        'do_something': do_something,
        'do_something_deferred': do_something_deferred,
    })
    tmpl = env.from_string('''
    {#- テンプレート中では、非同期関数は普通の関数と同じように呼び出せる #}
    result: {{ do_something(1) }}
    {#- Deferred を返す関数も同様に、普通に呼び出せる #}
    result from deferred: {{ do_something_deferred(1) }}
    ''')

    # .render_async() で coroutine が得られる
    coro = tmpl.render_async()
    print(repr(coro))
    # coroutine を await することでレンダリング結果が手に入る
    txt = await coro
    print(txt)


if __name__ == '__main__':
    from twisted.internet import task
    task.react(lambda _: defer.ensureDeferred(main()))

実行結果:

<coroutine object Template.render_async at 0x7f16bbea5ca8>

    result: 2
    result from deferred: 2

HEAD / archives / 2017-02 / 2017-02-09.rst

flatpak を使って python application をパッケージングしてみる試み、その2

前回 は httpie をパッケージングしてみたので、今度は自作のアプリケーションをパッケージングしてみようとしたわけだが。

コケる

このようなビルドスクリプトを用意して:

#!/bin/bash
set -e

REPOSITORY="${HOME}/repos/flatpak/u7fa9.org"

PACKAGE="konnyaku"
URL="git+https://github.com/nakamuray/konnyaku.git"
VERSION="master"
BUILD_FINISH_ARGS=(
  --share=network
  --command=konnyaku
)

_APP="org.u7fa9.${PACKAGE}"
_RUNTIME="org.freedesktop.BasePlatform"
_SDK="org.freedesktop.BaseSdk"
_RUNTIME_VERSION="1.4"

build_location="$(mktemp -d "${_APP}".XXXXXXX)"

flatpak build-init "${build_location}" "${_APP}" "${_SDK}" "${_RUNTIME}" "${_RUNTIME_VERSION}"
flatpak build "${build_location}" python3 -m venv /app
flatpak build --share=network "${build_location}" /app/bin/pip install -U pip
flatpak build --share=network "${build_location}" /app/bin/pip install "${URL}"
flatpak build-finish "${BUILD_FINISH_ARGS[@]}" "${build_location}"
flatpak build-export "${REPOSITORY}" "${build_location}" "${VERSION}"

実行:

$ bash ./build-konnyaku.bash
Collecting pip
... snip ...
Collecting git+https://github.com/nakamuray/konnyaku.git
  Cloning https://github.com/nakamuray/konnyaku.git to /tmp/pip-arprb4sp-build
    /usr/lib/python3.4/distutils/dist.py:244: UserWarning: 'licence' distribution option is deprecated; use 'license'
      warnings.warn(msg)
... snip ...
Collecting lxml (from konnyaku==0.0.1)
  Using cached lxml-3.7.2.tar.gz
    Building lxml version 3.7.2.
    Building without Cython.
    ERROR: b'ERROR: /usr/bin/xslt-config should not be used, use an alternative such as pkg-config\n'
    ** make sure the development packages of libxml2 and libxslt are installed **
    Using build configuration of libxslt --should-not-have-used-/usr/bin/xslt-config
    Traceback (most recent call last):
... snip ...
    ----------------------------------------
    Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-zzfiqm16/lxml

コケた。

どうも xslt-config が意図した値を返してないっぽい。

対応方針

さて、 flatpak の流儀としては、こういう時の正しい対応は「 /app 以下に libxslt をビルド・インストールする」となる (多分)。

が、自分で依存ライブラリを一つずつビルド・インストールしていくとか面倒くさいので、今回は「 alpine ベースでこのアプリケーション専用の runtime/sdk を作成し、使用する」という対応をしてみる。

runtime/sdk の作成

こんな感じのスクリプトを用意して:

#!/bin/bash
set -e

IMAGE_URL="https://nl.alpinelinux.org/alpine/v3.5/releases/x86_64/alpine-minirootfs-3.5.1-x86_64.tar.gz"
REPOSITORY_PATH="${HOME}/repos/flatpak/u7fa9.org"
APP_NAME="org.u7fa9.konnyaku"
RUNTIME_VERSION="3.5-1"
RUNTIME_NAME="${APP_NAME}.Runtime"
SDK_NAME="${APP_NAME}.Sdk"

RUNTIME_PACKAGES=(
  python3
  libxml2
  libxslt
  ca-certificate
)
SDK_PACKAGES=(
  git
  gcc
  musl-dev
  linux-headers
  python3-dev
  libxml2-dev
  libxslt-dev
)


image_filename="${IMAGE_URL##*/}"

if [[ ! -e "${image_filename}" ]]; then
  wget "${IMAGE_URL}"
fi

runtime_dir=$(mktemp -d "${RUNTIME_NAME}-${RUNTIME_VERSION}".XXXXXXXXXX)
sdk_dir=$(mktemp -d "${SDK_NAME}-${RUNTIME_VERSION}".XXXXXXXXXX)

rootfs_dir="${runtime_dir}"/rootfs
mkdir -p "${rootfs_dir}"
tar xpf "${image_filename}" -C "${rootfs_dir}" --exclude="./dev/*"

run_in_rootfs() {
  /usr/libexec/flatpak-bwrap \
    --unshare-ipc \
    --unshare-pid \
    --unshare-uts \
    --setenv PATH "/bin:/usr/bin:/sbin:/usr/sbin" \
    --bind "${rootfs_dir}" / \
    --bind /etc/resolv.conf /etc/resolv.conf \
    --proc /proc --dev /dev --tmpfs /tmp \
    "$@"
}
run_in_rootfs apk update
run_in_rootfs apk add "${RUNTIME_PACKAGES[@]}" || true

# create sdk based on runtime
cp -a "${rootfs_dir}" "${sdk_dir}"/

# as flatpak link (mount) runtime's files as a "/usr", copy all important files to /usr and use it as a "files"
pushd "${rootfs_dir}"
cp -a ./etc ./usr/
cp -a ./bin/* ./usr/bin
cp -a ./sbin/* ./usr/sbin
# to overwrite symlinks which look back to /lib it self, remove it before
yes | cp -a --remove-destination ./lib/* ./usr/lib
popd

cat > "${runtime_dir}"/metadata <<EOF
[Runtime]
name=${RUNTIME_NAME}
runtime=${RUNTIME_NAME}/x86_64/${RUNTIME_VERSION}
sdk=${SDK_NAME}/x86_64/${RUNTIME_VERSION}
EOF

flatpak build-export --runtime --files=rootfs/usr "${REPOSITORY_PATH}" "${runtime_dir}" "${RUNTIME_VERSION}"

# setup sdk, same as runtime
rootfs_dir="${sdk_dir}"/rootfs

run_in_rootfs apk add "${SDK_PACKAGES[@]}" || true

pushd "${rootfs_dir}"
cp -a ./etc ./usr/
cp -a ./bin/* ./usr/bin
cp -a ./sbin/* ./usr/sbin
# to overwrite symlinks which look back to /lib it self, remove it before
yes | cp -a --remove-destination ./lib/* ./usr/lib
popd

cat > "${sdk_dir}"/metadata <<EOF
[Runtime]
name=${SDK_NAME}
runtime=${SDK_NAME}/x86_64/${RUNTIME_VERSION}
EOF

flatpak build-export --runtime --files=rootfs/usr "${REPOSITORY_PATH}" "${sdk_dir}" "${RUNTIME_VERSION}"

実行。(一般ユーザーで apk を実行すると色々動かないので、 sudo で。) (コンソール出力記録し忘れた。)

改めてアプリケーションのパッケージング

runtime/sdk を先程作成したものに切り替えて

diff -r d1d5803c4e99 flatpak/build-konnyaku.bash
--- a/flatpak/build-konnyaku.bash       Thu Feb 09 21:22:13 2017 +0900
+++ b/flatpak/build-konnyaku.bash       Thu Feb 09 21:22:20 2017 +0900
@@ -12,9 +12,9 @@
 )

 _APP="org.u7fa9.${PACKAGE}"
-_RUNTIME="org.freedesktop.BasePlatform"
-_SDK="org.freedesktop.BaseSdk"
-_RUNTIME_VERSION="1.4"
+_RUNTIME="${_APP}.Runtime"
+_SDK="${_APP}.Sdk"
+_RUNTIME_VERSION="3.5-1"

 build_location="$(mktemp -d "${_APP}".XXXXXXX)"

再度パッケージング実行。

$ flatpak --user install u7fa9 org.u7fa9.konnyaku.Sdk
Installing: org.u7fa9.konnyaku.Sdk/x86_64/3.5-1 from u7fa9

Scanning metadata: 290
$ bash build-konnyaku.bash
Collecting pip
  Using cached pip-9.0.1-py2.py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 8.1.1
    Uninstalling pip-8.1.1:
      Successfully uninstalled pip-8.1.1
Successfully installed pip-9.0.1
Collecting git+https://github.com/nakamuray/konnyaku.git
  Cloning https://github.com/nakamuray/konnyaku.git to /tmp/pip-orfzyndp-build
Collecting aiohttp (from konnyaku==0.0.1)
  Using cached aiohttp-1.3.0.tar.gz
Collecting click (from konnyaku==0.0.1)
  Using cached click-6.7-py2.py3-none-any.whl
Collecting cssselect (from konnyaku==0.0.1)
  Using cached cssselect-1.0.1-py2.py3-none-any.whl
Collecting lxml (from konnyaku==0.0.1)
  Using cached lxml-3.7.2.tar.gz
Collecting pyxdg (from konnyaku==0.0.1)
Collecting sqlalchemy (from konnyaku==0.0.1)
Collecting chardet (from aiohttp->konnyaku==0.0.1)
Collecting multidict>=2.1.4 (from aiohttp->konnyaku==0.0.1)
  Using cached multidict-2.1.4.tar.gz
Collecting async_timeout>=1.1.0 (from aiohttp->konnyaku==0.0.1)
  Using cached async_timeout-1.1.0-py3-none-any.whl
Collecting yarl>=0.8.1 (from aiohttp->konnyaku==0.0.1)
  Downloading yarl-0.9.2.tar.gz (125kB)
    100% |████████████████████████████████| 133kB 1.0MB/s
Installing collected packages: chardet, multidict, async-timeout, yarl, aiohttp, click, cssselect, lxml, pyxdg, sqlalchemy, konnyaku
  Running setup.py install for multidict ... done
  Running setup.py install for yarl ... done
  Running setup.py install for aiohttp ... done
  Running setup.py install for lxml ... done
  Running setup.py install for konnyaku ... done
Successfully installed aiohttp-1.3.0 async-timeout-1.1.0 chardet-2.3.0 click-6.7 cssselect-1.0.1 konnyaku-0.0.1 lxml-3.7.2 multidict-2.1.4 pyxdg-0.25 sqlalchemy-1.1.5 yarl-0.9.2
Please review the exported files and the metadata
Commit: 82aac3014529a0400b080f7092fff457fb7900c13bc57fa09d76b7ef72778eb7
Metadata Total: 349
Metadata Written: 173
Content Total: 1420
Content Written: 1156
Content Bytes Written: 27112616 (27.1 MB)

今度はうまくいった。

インストールして実行

$ flatpak --user install u7fa9 org.u7fa9.konnyaku
Required runtime for org.u7fa9.konnyaku/x86_64/master (org.u7fa9.konnyaku.Runtime/x86_64/3.5-1) is not installed, searching...
Found in remote u7fa9, do you want to install it? [y/n]: y
Installing: org.u7fa9.konnyaku.Runtime/x86_64/3.5-1 from u7fa9

252 metadata, 3929 content objects fetched; 18779 KiB transferred in 24 seconds

Installing: org.u7fa9.konnyaku/x86_64/master from u7fa9

175 metadata, 1145 content objects fetched; 8243 KiB transferred in 7 seconds

$ flatpak run org.u7fa9.konnyaku
Usage: konnyaku [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --help                Show this message and exit.

Commands:
  add      add site
  check    check site updates
  links    list links
  list     list sites
  modify   modify site
  oneshot  add & check but not save
  remove   remove site
  show     show site information

今後の方針について

今後の application/runtime/sdk を更新していく方針についても考える。

  • OS パッケージ側 (alpine 側) に更新があったら、 runtime/sdk のみを作成し直す
  • アプリケーション側を更新したら、 application 側のみを作成し直す
  • アプリケーション側を更新し、新たに OS 側に依存パッケージが増えたら、 runtime/sdk を作り直してバージョンを上げ、 application はその新しいバージョンを使用するように作り直す

という感じかなぁ。

HEAD / archives / 2017-02 / 2017-02-07.rst

tmux で shift + 矢印キーが効かない件

.tmux.confset-window-option -g xterm-keys on と設定して解決。

HEAD / archives / 2017-02 / 2017-02-06.rst

flatpak を使って python application をパッケージングしてみる試み

flatpak って?

http://flatpak.org/

Linux 方面の最近の流れとして、デスクトップアプリケーションとかもコンテナ技術でパッケージング・動作させようぜ、というのがあり、その一つ。 xdg-app と呼ばれていたもの。

デスクトップアプリケーション向けなので、 docker と違って一般ユーザーで普通に実行できたりとか、 X/Wayland とやり取りできたりとか、その他色々ある。

出てくる用語としては、

  • application
    • アプリケーション
    • 実行ファイルと、必要があればその依存ライブラリなどがパッケージングされている
    • 実行時は /app 以下に mount される
  • runtime
    • 複数の application から参照される共通のシステムイメージ部分
    • 実行時は /usr 以下に mount される
  • sdk
    • runtime + ビルドに必要なモノ
    • コンパイラとかヘッダファイルとかが入っている
    • アプリケーションをパッケージングするときはこちらのイメージを使ってビルドを通し、 実行時は runtime を使ってディスク容量を節約する、という使い分けがされる (多分)
  • repository
    • application や runtime が置かれてるトコ

辺り。

使い方としては、まず repository を登録し、次に repository から application をインストールし、そして実行する。 という感じ。

パッケージングしてみる

さて、パッケージングしてみる。

パッケージング対象はとりあえず何でもいいのだけど、 python 製のアプリケーションでたまに名前を聞くものとして、 httpie にしよう。

まずは runtime/sdk として何を利用するかだが、 GNOME な人々が配っている org.freedesktop.BasePlatform に python も含まれているので、コレを使おう。

$ flatpak --user remote-add --if-not-exists gnome https://sdk.gnome.org/gnome.flatpakrepo
$ flatpak --user install gnome org.freedesktop.BasePlatform
Installing: org.freedesktop.BasePlatform/x86_64/1.4 from gnome

6 delta parts, 64 loose fetched; 56494 KiB transferred in 20 seconds
$ flatpak --user install gnome org.freedesktop.BaseSdk
Installing: org.freedesktop.BaseSdk/x86_64/1.4 from gnome

11 delta parts, 21 loose fetched; 98421 KiB transferred in 23 seconds

続いて実際のパッケージング作業。

ここでは flatpak builder という、設定ファイルから全部いい感じにやってくれるツールが存在するのだが、 今の所 autotools やら cmake やら前提になっていて、 python アプリケーションのパッケージングには使えない。 ということで、諸々手動でやる。

流れとしては、

  1. flatpak build-init で、簡単な雛形ディレクトリを作成し、
  2. flatpak build で、その雛形 + runtime/sdk 内に入ってコマンドを実行、インストール作業を行い、
  3. flatpak build-finish でメタデータの設定やらを行って、
  4. flatpak build-export で repository に書き出し。

という感じ。

今回はコレを shell script にまとめました。

build-httpie.bash:

#!/bin/bash
REPOSITORY="${HOME}/repos/flatpak/u7fa9.org"

PACKAGE="httpie"
VERSION="0.9.9"
BUILD_FINISH_ARGS=(
  --share=network
  --command=http
)

_APP="org.u7fa9.${PACKAGE}"
_RUNTIME="org.freedesktop.BasePlatform"
_SDK="org.freedesktop.BaseSdk"
_RUNTIME_VERSION="1.4"

build_location="$(mktemp -d "${_APP}".XXXXXXX)"

flatpak build-init "${build_location}" "${_APP}" "${_SDK}" "${_RUNTIME}" "${_RUNTIME_VERSION}"

# 先述したように、 application は /app 以下に mount されるので、この中に必要なものを全て入れる必要がある。
# 今回は /app 以下に virtualenv を作成し、 httpie を pip install する。
flatpak build "${build_location}" python3 -m venv /app
flatpak build --share=network "${build_location}" /app/bin/pip install "${PACKAGE}==${VERSION}"

flatpak build-finish "${BUILD_FINISH_ARGS[@]}" "${build_location}"
flatpak build-export "${REPOSITORY}" "${build_location}" "${VERSION}"
$ bash build-httpie.bash
You are using pip version 6.0.8, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting httpie==0.9.9
  Using cached httpie-0.9.9-py2.py3-none-any.whl
Collecting Pygments>=2.1.3 (from httpie==0.9.9)
  Using cached Pygments-2.2.0-py2.py3-none-any.whl
Collecting requests>=2.11.0 (from httpie==0.9.9)
  Using cached requests-2.13.0-py2.py3-none-any.whl
Installing collected packages: requests, Pygments, httpie



Successfully installed Pygments-2.2.0 httpie-0.9.9 requests-2.13.0
Please review the exported files and the metadata
Commit: 5c202dac8309b51af168f71c70a92ea564b536f67a5e50f5b6181c6b7a5f4f39
Metadata Total: 277
Metadata Written: 139
Content Total: 1324
Content Written: 1262
Content Bytes Written: 16052289 (16.1 MB)

できあがり。

公開する

Repository を HTTP でアクセスできるどこかにコピーして置くだけ。

https://u7fa9.org/flatpak/repo/

インストールする

別環境にて。 gnome と上記の repository を追加して、インストールする。

$ flatpak --user remote-add --if-not-exists gnome https://sdk.gnome.org/gnome.flatpakrepo
$ flatpak --user remote-add --no-gpg-verify u7fa9 https://u7fa9.org/flatpak/repo/
$ flatpak --user install u7fa9 org.u7fa9.httpie
Required runtime for org.u7fa9.httpie/x86_64/0.9.9 (org.freedesktop.BasePlatform/x86_64/1.4) is not installed, searching...
Found in remote gnome, do you want to install it? [y/n]: y
Installing: org.freedesktop.BasePlatform/x86_64/1.4 from gnome

6 delta parts, 64 loose fetched; 56494 KiB transferred in 20 seconds
Installing: org.u7fa9.httpie/x86_64/0.9.9 from u7fa9

139 metadata, 1261 content objects fetched; 5783 KiB transferred in 2 seconds

実行する

$ flatpak run org.u7fa9.httpie https://u7fa9.org/
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 337
Content-Type: text/html; charset=utf-8
Date: Mon, 06 Feb 2017 11:34:21 GMT
ETag: "4ce914bc-151"
Last-Modified: Sun, 21 Nov 2010 12:46:52 GMT
Server: nginx

<html>
  <head>
    <title>index</title>
    <link rel="stylesheet" href="/static/style.css" type="text/css">
  </head>
  <body>
    <ul>
      <li><a href="/memo/">memo</a></li>
      <li><a href="http://github.com/nakamuray">github</a></li>
      <li><a href="http://twitter.com/nakamuray">twitter</a></li>
    </ul>
  </body>
</html>

動いた。

HEAD / archives / 2016-12 / 2016-12-27.rst

古い jupyter notebook (<4.3) は危ないので気をつけようという話

概要

古い jupyter notebook (バージョン 4.3 未満) は DNS rebinding attack に対して脆弱です。 そのため、ローカルで notebook 動作中に悪意あるウェブページを閲覧しただけで、任意コードの実行を許す恐れがあります。

バージョン 4.3 からデフォルトで有効になった token 認証によってこの攻撃は防げるので、さっさと最新版に上げときましょう。

デモ

http://nbdemo.u7fa9.org/

お手元で pip install "notebook<4.3" して、 jupyter-notebook を起動してから上記 URL にアクセスしてみてください。 待っているとそのうち電卓が起動すると思います。

(一応 notebook-4.2.3 に対して Ubuntu と OSX 上で、 firefox と chrome で動作を確認してます。)

説明

  • DNS rebinding attack によって、悪意あるウェブページ上で動作する javascript からローカルで動作する jupyter notebook に対して、任意の HTTP リクエストを送ることが出来ます。
  • 古い jupyter notebook はリクエストの認証や Host ヘッダの確認といった対策を行っていないので、このリクエストを普通に受け入れてしまいます。
  • jupyter notebook に任意のリクエストを送れるということは、要するに任意の python コードを実行できるということです。
  • バージョン 4.3 から token 認証がデフォルトで有効となりました。 攻撃者は token を知ることが出来ないため、 notebook へのリクエストを成功させることが出来ません。 よって、コード実行なども出来ません。
  • ということで、 jupyter notebook は最新にしておきましょう。

メモ

コード実行までに必要な notebook の API について。

  • 新しい .ipynb ファイルの作成

    $ curl -d '{"type":"notebook"}' http://localhost:8888/api/contents | jq .
    {
      "format": null,
      "name": "Untitled.ipynb",
      "last_modified": "2016-12-25T09:00:00.000000+00:00",
      "writable": true,
      "path": "Untitled.ipynb",
      "content": null,
      "created": "2016-12-25T09:00:00.000000+00:00",
      "type": "notebook",
      "mimetype": null
    }
    
  • どの kernel が利用できるのか調べる。

    $ curl http://localhost:8888/api/kernelspecs | jq .
    {
      "default": "python3",
      "kernelspecs": {
        "python3": {
          "name": "python3",
          "resources": {
            "logo-32x32": "/kernelspecs/python3/logo-32x32.png",
            "logo-64x64": "/kernelspecs/python3/logo-64x64.png"
          },
          "spec": {
            "language": "python",
            "argv": [
              "/venv/bin/python3.5",
              "-m",
              "ipykernel",
              "-f",
              "{connection_file}"
            ],
            "env": {},
            "display_name": "Python 3"
          }
        }
      }
    }
    
  • kernel の起動

    $ curl -d '{"notebook": {"path": "Untitled.ipynb"}, "kernel": {"id": null, "name": "python3"}}' http://localhost:8888/api/sessions | jq .
    {
      "kernel": {
        "name": "python3",
        "id": "4102da2b-bb4c-47f9-8660-cf5b84779d07"
      },
      "id": "92aa4c5c-d83d-4924-8bcc-80bb523f9f19",
      "notebook": {
        "path": "Untitled.ipynb"
      }
    }
    
  • kernel に接続

    ws://localhost:8888/api/kernels/<KERNEL-ID>/channels?session_id=<SESSIONID> に websocket で接続する。

    (<KERNEL-ID> のトコには上記で返ってきた kernel.id を入れる。)

    (<SESSIONID> はテキトウにランダム生成すればよさそう。)

  • コードの実行

    以下のような JSON 文字列を websocket に send する:

    {
      "header": {
        "msg_id": "<MESSAGEID>",
        "username": "username",
        "session": "<SESSIONID>",
        "msg_type": "execute_request",
        "version": "5.0"
      },
      "metadata": {},
      "content": {
        "code": "print('hello')",
        "silent": false,
        "store_history": true,
        "user_expressions": {},
        "allow_stdin": true,
        "stop_on_error": true
      },
      "buffers": [],
      "parent_header": {},
      "channel": "shell"
    }
    

    (<SESSIONID> には上記 <SESSIONID> と同じ値を渡しとけばよいっぽい。)

    (<MESSAGEID> は何でもよさそう。)

HEAD / archives / 2016-11 / 2016-11-26.rst

Twisted が async def な coroutine に対応したらしいので、試してみる

あとついでに asyncio な reactor も試したりとか。

'''
twisted が async def な coroutine に対応したらしいのでそれ使ってみるのと、
ついでに asyncio な reactor 使って、その中で twisted と asyncio 両方の関数を
呼んでみるテスト。
'''
import asyncio
from twisted.internet import asyncioreactor
# twisted の reactor として、 asyncio ベースの物を使うよう設定する。
# (eventloop として、デフォルトではグローバルなもの使うとか書いてあるど、
# 実際は内部で新しく作成した eventloop を使ってるっぽい?)
asyncioreactor.install(asyncio.get_event_loop())

from twisted.internet import defer
from twisted.web import client as twClient
from aiohttp import client as aioClient


async def main(reactor=None):
    # twisted を通してページを取得する
    page1 = await twClient.getPage(b'http://localhost/')

    # asyncio (aiohttp) を通してページを取得する
    res = await _fut2deferred(aioClient.get('http://localhost/'))
    page2 = await _fut2deferred(res.read())

    # 両者はもちろん同じもの
    assert page1 == page2

    print('done')


# ensureDeferred は Deferred が出てくる coroutine を期待しているので、
# Future をテキトウに Deferred に変換するヤツ用意する。
# (将来的には twisted がこういう機能提供するつもりらしい?)
def _fut2deferred(fut):
    fut = asyncio.ensure_future(fut)
    d = defer.Deferred()
    fut.add_done_callback(lambda _: d.callback(fut.result()))
    # TODO: errback

    return d


if __name__ == '__main__':
    from twisted.internet import task

    # coroutine は ensureDeferred を使って Deferred に変換する。
    # asyncio とは違って、 reactor やらが勝手にやってくれるということは無さそう?
    task.react(lambda reactor: defer.ensureDeferred(main(reactor)))
powered by blikit