WPP
Synology NAS の ssh を公開鍵認証にする

パスワードログインでも何も困っていないのだが、ユーザーのパスワードを迂闊にパスワードマネージャーが生成したものに変えてしまうと ssh に入るのが非常に面倒なので鍵認証に変更しておく。

詳細は、参考リンクまんまだが、

  1. コントロールパネルで ssh を有効にする
  2. パスワードログインで ssh に入る
  3. ~/.ssh/authorized_keys に公開鍵をコピペ

/etc/ssh/sshd_config は多分修正の必要がない。

鍵ファイルはどっかで使っているものでもいいし、新たに作ってもいい。

セキュリティ観点でいえば鍵ファイルも使い回さない方がいいのだろうが、増えてくると鍵ファイルそのものの管理も面倒になるのでバランスが難しい。

仮にローカルにある鍵ファイルをひとたび見れるような状態になった場合、鍵ファイルをいくつも持っていたからといってセキュリティ強度は変わらない。どの鍵がどこのものかを見つけるのが面倒なだけでおそらく見つけることはできる。鍵ファイルの名前に使用するサイト名などを含めていたら、実質的に1つの鍵ファイルを使いまわすのとほぼ変わらない。

参考

Synology DiskStation で SSH 接続を公開鍵認証方式にする #synology – Qiita

Google sitekit が重いので Independent analytics に変えてみた

数ヶ月前から WordPress の統計プラグインを Google sitekit にしていたのだが、管理者側のページがあまりに遅くて閉口していた。サーバが弱いのが根本的な原因ではあるが NAS で動かしているのでそこをどうにかすることは今のところ考えていない。サーバ建てるのはいいが管理が面倒なので NAS に含まれるパッケージの方が扱いが楽だと感じている。

Claude に sitekit の代替を聞いてみると Independent Analytics を勧めてきたので使ってみて様子を見よう。

Independent Analytics – Google Analytics Alternative for WordPress – WordPress plugin | WordPress.org

特に設定は必要ないみたい。

以前は、WP Statistics を使っていたのだけれどバージョンアップのたびに動作が不安定になり、意味不明な挙動をするようになってやめたのでこれで 3 代目になる。

タダで使おうとするのがいけないのだが、WP のプラグインは商売っ気が強すぎていまいちいいのがない。まあ、仕方ないことだとは思うが。。。

簡単に使えるいい UCM があれば乗り換えてもいいのだけれど。。。

Linux + OceanAudio でリモートのフォルダを扱うときは CIFS (Samba)を使うほうがいい

さて、これも自分向けメモではあるがきっと数カ月後には忘れているだろうから記録する。

状況

どんな状況かといいうと LM22.1 (Ubuntu 24.04) の Cinnamon デスクトップでリモートの CIFS (Windowsのファイル共有)フォルダをマウントすると gvfs でマウントされる。最近のデスクトップでは同じようなものも多いと思う。

gvfs でマウントすると CIFS でマウントした場合とはパスが異なる。これが原因で gvfs 経由のファイルアクセスには失敗することがある。具体的には

gvfs の例
/run/user/1000/gvfs/smb-share:server=192.168.1.xx,share=datafolder

cifs の例
/mnt/shared/datafolder

こんな感じで “,” や “:” などが意図しない場所に存在するために gvfs を考慮していないアプリだと上手く動作しないことがあるらしい。

要するに fopen() なんかに gvfs のパスをそのまま渡しても単に失敗するのだと推測される。

そんな場合は、素直に mount -t cifs でマウントすればいい。

$ sudo mount -t cifs -o uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777,username=USER //SERVERl/PATH /mnt/MOUNT

コマンドの以下の部分は、適宜環境に合わせる必要がある

USER: リモートの共有フォルダにアクセスする際のユーザー名

SERVE: リモートの共有フォルダのサーバーアドレス

PATH: 共有フォルダのパス

MOUNT: マウントしたいフォルダ名

ちょっと長いのでスクリプトにしておいたほうがいいと思う。

Rock64 のDietpi 9.15.2 でコンソールが表示されないときの対処方法

Rock64 というラズベリーパイもどきをスモールサーバとして使用しているのですが、そこで動く OS: Dietpi を新たに入れ直したところコンソール画面が表示しない現象が発生しました。

以前のバージョンではこういった現象はおきませんでしたが、インストーラーなんかも少し変わっていたので起動周りでなにか変更があったのかもしれません。

ディスプレイからは信号なしのアラートがでるので、detect を失敗しているかディスプレイが対応していないモードで出力しているのかもしれません。中華製モバイルディスプレイなので何か作りがいい加減なのが根本原因かもしれませんが、真相はわかりません。

dietpi-config でコンソールの解像度(要するに kernel の起動パラメータ)を変更出来るようなのでやってみます。

やってみるとわかるのですが、dietpi-config でリセットしてから再セットする必要があるようです。

  1. sudo dietpi-condig でコンフィグツールを起動
  2. 1. Display Options に入る
  3. 0. DietPi-Display mode and rotation (beta) に入る
    こんな項目は以前なかったと思う。
  4. Reset を選択
  5. <Exit> を選び再起動する
  6. 再起動後、2度目の sudo dietpi-config
  7. 1. Display Options に入る
  8. mode に入り 1024×768 など写りそうなモードを選択する
  9. <Exit> を選び、また再起動

この手順でコンソールを表示するようになった。

まあ、通常は ssh しかしないので画面表示できなくても問題はないが、この手の SBC は初回起動時から ssh 接続まではどうしても画面があったほうがいいので一応対処した。

最近の Raspberry OS だとそこら辺のネットワークを埋め込んで SD に焼けるので初回からディプレイなしでいけるのはいいところ、とは言え画面あったほうが便利ではある。

LM22.1 (Ubuntu 24.04) の Viber で日本語入力できるようにする。

さて、とある取引先と Viber でやり取りをしているのだが、Linux Mint だと日本語入力できなかったので対処したという話。

作業内容からすると ibus をインストールして Viber で認識できるようにしたって感じでしょうか。

環境

  • Linux Mint 22.1 (cinammon)
  • Fcitx + Mozc
  • 公式の viber.AppImage を /opt/viber/ に配置

手順

Claude がわりといい感じで見つけてくれるので早速Claude に聞いてみると

# ダウンロードした AppImage を移動
$ mv viber.AppImage /opt/viber

$ sudo apt install ibus-mozc


$ ibus-setup
# 起動したら Mozc を追加する

# ここで再起動かデスクトップに再ログインしないと ibus がアクティブにならないかも

# 以下を .profile か .bashrc に入れろって指示だったが今回は、起動スクリプトで直接指定した。
# export GTK_IM_MODULE=ibus
# export QT_IM_MODULE=ibus
# export XMODIFIERS=@im=ibus

$ vi /opt/viber/launch.sh
#!/bin/bash
GTK_IM_MODULE=ibus QT_IM_MODULE=ibus XMODIFIERS=@im=ibus /opt/viber/viber.AppImage
:wq

$ chmod 755 /opt/viber/launch.sh

# メニューから起動できるように .desktop を作成
# Ubuntu だと場所や方法が異なるかも
# アイコンは LM の場合は入っていたのでそれを利用、なければ適当に。
$ vi ~/.local/share/applications/viber.desktop
[Desktop Entry]
# Name はお好みで
Name=Viber - AppImage
#Exec=/opt/viber/viber.AppImage
Exec=/opt/viber/launch.sh
Comment=
Terminal=false
PrefersNonDefaultGPU=true
Icon=/opt/assets/viber.svg
Type=Application
:wq

デスクトップのメニューから Viber を起動すると ibus 経由で日本語入力が出来るようになった。

参考

linuxmintでブラウザーでは日本語打てるんですがvIberだと英語… – Yahoo!知恵袋
ibus にしか対応していないという示唆

dietpi の mariadb にローカルからログイン

いつも忘れるのでメモ。

dietpi 公式の mariadb にはルートユーザーは コンソールにログインするときの root ユーザーとそのパスワードとなっているが、通常ユーザで ssh したコンソールでは mariadb -u root -p が動作しない。

$ sudo mariadb
とすることで mariadb のコンソールに接続することができる。

参考

Databases and Data Store Software Options – DietPi.com Docs

WordPress パスワードを強制リセット

これは、ネット上でみるが自分向けのショートカットのためにメモ。

新規で WordPress をインストールしたのだが、ユーザー作成まで完了しました。しかしパスワードマネージャーが生成したパスワードが自動で記憶されていない状態でした。クリップボードにパスワードを保存していません。

悪い事に、メールサーバーの設定も完了していなかったので、メールによるパスワードリセットもできません。

こんな場合は、DB のパスワードを直接書き換える必要があります。

手順

下記の例では、ワードプレスにログインするユーザは、wp とします。適宜変更してください。

# 新パスワードの md5sum ハッシュを取る (-n で改行を取らないとハッシュ値が異なるので注意)
$ echo -n 'newpass' |md5sum
xxxxx

$ mariadb -u ワードプレスDBのユーザーID -p
mariadb > use wordpress;

# ユーザー名等を確認する
mariadb > select id, user_login, user_pass from wp_users; 

# 変更
mariadb > update wp_users set user_pass=''xxxxx" where user_login="wp"

ネット上では、md5sum を取る方法はいろいろ見つかるし、そもそも mariadb の md5() 関数でもできるはず。

NextCloud のデータの場所を変更する方法

さて、Synology NAS の HDD 動作要件が厳しくなって Compatibility List にないドライブでは動作しないように仕様変更されているらしい。いま時点ではたまたま動作している HDD を使っているので問題はないが、いつも適当にいい感じのドライブを買っているだけなのでいちいち気にしなけばいけないのは面倒だ。

5 年以上 NAS を使ってきているがドライブの故障にはでくわしたことがない、それ以前に容量が涸渇してより大きな HDD に買い替えているので RAID である必要もないのかもしれない。

などと考えており、代替策の 1 つとして NextCloud を検証している。

前置きが長くなったが、NextCloud のデータの保存場所を変更する方法をメモ。

環境

OS: Dietpi 9.15.2
NextCloud: 31.0.8

方法

どうやら、NextCloud は、1 つのフォルダに共有ファイルと関連するデータ (DB等) をまとめて管理するらしい。

config.php に datadirectory という項目があるので新しいパスにしてやる。

config.php は、/var/www/nextcloud/config/config.php にあった。(ディストリやインストール方法によって異なるのでこの辺から探してほしい)

<?php
$CONFIG = array (
  'passwordsalt' => 'XSqB+XXXXX',
  'secret' => 'O5PwBJjAkatBYxt876LL9ZA4ibfThOxkpXXXXXX',
  'trusted_domains' => 
  array (
    0 => 'localhost',
1 => '*',
  ),
  // これを変える
  // 'datadirectory' => '/mnt/dietpi_userdata/nextcloud_data',
  'datadirectory' => '/new_path',

  'dbtype' => 'mysql',
  'version' => '31.0.8.1',
'hashingThreads' => 4,
'memcache.local' => '\\OC\\Memcache\\APCu',
'filelocking.enabled' => true,
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' => array ('host' => '/run/redis/redis-server.sock', 'port' => 0,),
  'overwrite.cli.url' => 'http://localhost/nextcloud',
  'dbname' => 'nextcloud',
  'dbhost' => 'localhost',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'oc_admin',
  'dbpassword' => 'cCD^4wy2Z&EpN]/w(GnQc_pq9h7TZy',
  'installed' => true,
  'instanceid' => 'ocmwukkz4mzw',
);

それだけだとデータが何もないので、もともとのデータディレクトリからデータをコピーし、chown をして所有者を調整する必要がある。

# ここでは例として /new_path にデータディレクトリを設定する
$ sudo cp -r /mnt/dietpi_userdata/nextcloud_data /new_path


# nextcloud が使うユーザに所有権を設定。(ディストリビューションなどにより実際のユーザー名、グループ名は合わせてほしい)
$ sudo chown -R www-data: /new_path

これでNextCloud が新しいフォルダを使うようになる。

NextCloud だとWeb やアプリ経由でのファイル共有は、ほぼ NAS と遜色ない。開発ペースの速さやアプリの多さはむしろ勝っているかもしれない。ここに Portainer を追加してコンテナを動かすようにすればいい感じになるかも。

が、Samba と macOS のAFP で行うローカルのファイル共有プロトコルを別管理しなければいけないのはちょっと面倒だということに改めて気付かされた。

YAMAHA RTX830 IKEv2 リモートアクセスを設定2

以前、YAMAHA RTX1210 を Asahiネット IPIP + 固定IPアドレスで IKEv2 リモートアクセスを設定 でIKEv2 リモートアクセスを設定して LAN 内に外から入れるようにしていたのですが、このままだと Android 端末からインターネット接続ができないことがわかったので修正

動機

Android からIKEv2 リモートアクセスしつつ、インターネットに抜ける。

LinegeOS に限った話なのかもしれないが、IKEv2 VPN 接続をアクティブにするとすべてのパケットが VPN 経由になるっぽい。

なので、RTX側でVPN > インターネット抜けの設定をしてやる必要がある。

環境等

前回とはルーターが RTX1210 から RTX830 に変わっています。hair-pin ナットを使いたかったので変更したのだが、これは IKEv2 とは無関係なので気にしなくていい

  • Asahiネット 光 の 1G のコース 10G だと「固定IPアドレスオプション」が使えない
  • 固定IPアドレスオプション
  • その結果、IPv4 over IPv6接続 (IPIP)
  • ひかり電話なし
  • ONU のみ

ネットワーク設計

RTX のインターフェイスはこんな感じ。

RTX: 192.168.1.254
LAN2: IPoE 接続
LAN1: 192.168.1.0/24 の宅内LAN
Tunnel1: IPIP でここで IPv4 に変換
Tunnel10: IKEv2 10.20.30.1-10.20.30.10 を払い出すようにする

固定IPv4: 157.107.YYY.ZZZ (適宜振り出されたIPに変えること)

RTX の設定した内容

ポイントは、nat 設定と dns 設定を Android 端末に通知する必要があるらしい。

この設定では 10.20.30.1-10.20.30.10 を IKEv2 で接続してきた端末に払出しているが、もし 192.168.1.1-192.168.1.10 のような LAN1 と同じアドレス帯を使用するなら、以下のようにすればいい。

ipsec ike mode-cfg で 192.168.1.1-10 のアドレスを指定します。 nat descripter inner では 10.20.30.0 帯を設定する必要がなくなります。dns host の設定も同じように 10.20.30.0 帯をはずします。

ipsec ike mode-cfg address pool 1 192.168.1.1-192.168.1.10
nat descriptor inner 20000 192.168.1.1-192.168.1.254
dns host lan1

なので、LAN1 と同じアドレス帯から切り出したほうが RTX の設定は簡単かもしれません。家庭用なら端末が数十台以上とかはなさそうなのでいいかもしれません。

# RTX830 Rev.15.02.31 (Fri Jul  5 10:40:25 2024)
# MAC Address : ac:44:f2:62:53:ee, ac:44:f2:62:53:ef
# Memory 256Mbytes, 2LAN
# main:  RTX830 ver=00 serial=M5B066419 MAC-Address=ac:44:f2:62:53:ee MAC-Address=ac:44:f2:62:53:ef
# Reporting Date: Aug 13 10:22:24 2025
login password *
administrator password *
login user admin *
user attribute connection=serial,telnet gui-page=dashboard,lan-map,config login-timer=1800
user attribute admin connection=serial,telnet,remote,ssh,sftp,http gui-page=dashboard,lan-map,config login-timer=1800
console character ja.utf8
system packet-buffer small max-buffer=5000
system packet-buffer middle max-buffer=7500
ip routing process fast
ip route default gateway tunnel 1
ip route 10.20.30.0/24 gateway tunnel 10
ipv6 routing process fast
ipv6 prefix 1 ra-prefix@lan2::/64
ip lan1 address 192.168.1.254/24
ip lan1 proxyarp on
ipv6 lan1 address ra-prefix@lan2::9d6b:XXXX:0/64
ipv6 lan1 prefix change log on
ipv6 lan1 rtadv send 1 o_flag=on
ipv6 lan1 dhcp service server
switch control use lan1 on terminal=on
description lan2 Asahi-NET
lan linkup send-wait-time lan2 5
ip lan2 intrusion detection in on
ip lan2 intrusion detection in ip on reject=off
ip lan2 intrusion detection in ip-option on reject=off
ip lan2 intrusion detection in default off
ipv6 lan2 secure filter in 101000 101002 101003 101100 101101 101102 102001 102002
ipv6 lan2 secure filter out 101099 dynamic 101080 101081 101082 101083 101084 101085 101098 101099
ipv6 lan2 dhcp service client ir=on
ngn type lan2 ntt

# iOS 用の L2TP 設定
pp select anonymous
 pp bind tunnel16-tunnel17
 pp auth request chap-pap
 pp auth username vpn1 pass1234
 pp auth username vpn2 pass1234
 ppp ipcp ipaddress on
 ppp ipcp msext on
 ip pp remote address pool 192.168.1.21-192.168.1.30
 ip pp mtu 1258
 pp enable anonymous

# 4 over 6 IP接続
tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint remote address 2001:c28:1:300::1008
 ip tunnel mtu 1460
 ip tunnel secure filter in 1101 1503 1521 1522 1523 1997
 ip tunnel secure filter out 1002 1003 1004 1005 1006 1007 1008 9999 dynamic 3001 3002 3003 3004 3098 3099
 ip tunnel nat descriptor 20000
 ip tunnel intrusion detection in on
 ip tunnel intrusion detection in ip on reject=off
 ip tunnel intrusion detection in ip-option on reject=off
 ip tunnel intrusion detection in default off
 tunnel enable 1

# IKEv2 VPN
tunnel select 10
 tunnel template 11
 description tunnel "IKEv2 tunnel"
 tunnel encapsulation ipsec
 ipsec tunnel 10
  ipsec sa policy 10 10 esp
  ipsec ike version 10 2
  ipsec ike keepalive log 10 off
  ipsec ike keepalive use 10 on rfc4306 10 3

  # 固定IP を自端末側に設定
  ipsec ike local name 10 157.107.XXX.XXX ipv4-addr
  ipsec ike pre-shared-key 10 text ike-vpn-1234
  ipsec ike remote name 10 vpn fqdn
  ipsec ike mode-cfg address 10 1
 l2tp tunnel disconnect time off
 l2tp keepalive use on 10 3
 l2tp keepalive log off
 l2tp syslog off
 ip tunnel tcp mss limit auto
 tunnel enable 10

# iOS 用 L2TP 設定(暗号化)
tunnel select 16
 tunnel template 17
 tunnel encapsulation l2tp
 ipsec tunnel 16
  ipsec sa policy 16 16 esp aes256-cbc sha256-hmac
  ipsec ike keepalive use 16 off
  ipsec ike nat-traversal 16 on
  ipsec ike pre-shared-key 16 text vpn1234
  ipsec ike remote address 16 any
 l2tp tunnel disconnect time off
 l2tp keepalive use on 10 3
 l2tp syslog off
 ip tunnel tcp mss limit auto
 tunnel enable 16

ip filter 1001 reject 192.168.1.0/24 * * *
ip filter 1002 reject * 192.168.1.0/24 * * *
ip filter 1003 reject * * udp,tcp 135 *
ip filter 1004 reject * * udp,tcp * 135
ip filter 1005 reject * * udp,tcp netbios_ns-netbios_ssn *
ip filter 1006 reject * * udp,tcp * netbios_ns-netbios_ssn
ip filter 1007 reject * * udp,tcp 445 *
ip filter 1008 reject * * udp,tcp * 445
ip filter 1101 pass * * icmp * *
ip filter 1503 pass * 192.168.1.244 tcp * https
ip filter 1521 pass * 192.168.1.254 * * 500
ip filter 1522 pass * 192.168.1.254 esp
ip filter 1523 pass * 192.168.1.254 udp * 4500
ip filter 1600 pass 157.107.76.213 192.168.1.0/24 tcp 5001 *
ip filter 1997 reject * *
ip filter 1998 pass * * tcp * *
ip filter 1999 pass * * udp * *
ip filter 9999 pass * * * * *
ip filter 500000 restrict * * * * *
ip filter dynamic 3001 * * ftp
ip filter dynamic 3002 * * www
ip filter dynamic 3003 * * https
ip filter dynamic 3004 * * sip
ip filter dynamic 3098 * * tcp
ip filter dynamic 3099 * * udp
nat descriptor type 20000 masquerade hairpin=on

# 固定IP
nat descriptor address outer 20000 157.107.xxx.xxx

# IKEv2 も nat の内側にあることを伝える
nat descriptor address inner 20000 192.168.1.1-192.168.1.254 10.20.30.1-10.20.30.10

nat descriptor sip 20000 on
nat descriptor masquerade incoming 20000 reject 
nat descriptor masquerade static 20000 1005 192.168.1.244 tcp https
nat descriptor masquerade static 20000 2001 192.168.1.254 esp
nat descriptor masquerade static 20000 2002 192.168.1.254 udp 500
nat descriptor masquerade static 20000 2003 192.168.1.254 udp 4500
ipsec auto refresh on
ipsec ike remote name 11 vpn1 fqdn

# IKEv2 でつかう IP アドレス
ipsec ike mode-cfg address pool 1 10.20.30.1-10.20.30.10/32

ipsec transport 1 16 udp 1701
ipsec transport 2 17 udp 1701
ipv6 filter 101000 pass * * icmp6 * *
ipv6 filter 101002 pass * * udp * 546
ipv6 filter 101003 pass * * 4
ipv6 filter 101099 pass * * * * *
ipv6 filter 101100 pass * * tcp * www
ipv6 filter 101101 pass * * tcp * 5001
ipv6 filter 101102 pass * * tcp * 5006
ipv6 filter 102001 pass * * * 500
ipv6 filter 102002 pass * * esp
ipv6 filter 102003 pass * * udp * 4500
ipv6 filter dynamic 101080 * * ftp
ipv6 filter dynamic 101081 * * domain
ipv6 filter dynamic 101082 * * www
ipv6 filter dynamic 101083 * * smtp
ipv6 filter dynamic 101084 * * pop3
ipv6 filter dynamic 101085 * * submission
ipv6 filter dynamic 101098 * * tcp
ipv6 filter dynamic 101099 * * udp
syslog host 192.168.1.244
syslog notice on
telnetd host lan1
dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.1.100-192.168.1.249/24
dhcp scope option 1 dns=192.168.1.245,192.168.1.254,9.9.9.9


# lan1 と IKEv2 から dns にアクセスを許可する
dns host lan1 10.20.30.1-10.20.30.10
dns service fallback on
dns cache max entry 1024
dns server dhcp lan2 edns=on
dns private address spoof on
schedule at 1 startup * lua emfs:/v6plus_map_e.lua

schedule at 2 */* 05:43:21 * ntpdate ntp.nict.jp syslog
l2tp service on
external-memory boot permit off
statistics traffic on
statistics nat on
alias cc="clear log"
alias jj="show log|grep -i reje"
alias rr="show log reverse"
alias sa="show ipsec sa"
embedded file v6plus_map_e.lua <<EOF
UPD_SV = "https://v6update.asahi-net.or.jp/prefix"
USERNAME = "P74587912"
PASSWORD = "DKIQWFFC"
WAN_IF = "LAN1"

LOG_PTN = "Add%s+IPv6%s+prefix.+%(Lifetime%:%s+%d+%)%s+via%s+" .. WAN_IF .. "%s+by"
LOG_LEVEL = "info"
LOG_PFX = "[v6plus]"
FAIL_MSG = "Failed to notify IPv6 address to the update server. (remaining retry: %d time(s))"

function logger(msg)
rt.syslog(LOG_LEVEL, string.format("%s %s", LOG_PFX, msg))
end

local rtn, count, log, result
local req_t = {}
local res_t
req_t.url = string.format("%s?user=%s&pass=%s", UPD_SV, USERNAME, PASSWORD)
req_t.method = "GET"
while true do
rtn = rt.syslogwatch(LOG_PTN)
if rtn then
count = 3
while true do
res_t = rt.httprequest(req_t)
if res_t.rtn1 then
logger("Notified IPv6 address to the update server.")
if res_t.code == 200 then
result = "Succeeded"
else
result = "Failed"
end
log = string.format("%s to update IPv6 address. (code=%d, body=%s)", result, res_t.code, res_t.body)
logger(log)
break
end
count = count - 1
if count > 0 then
logger(string.format(FAIL_MSG, count))
rt.sleep(10)
else
logger("Failed to notify IPv6 address to the update server.")
break
end
end
end
end
EOF

これでようやく Android から IKEv2 接続しつつインターネットまで問題なく動作するようになりました。なかなかむずかしい。

参考

リモートアクセスVPN(IKEv2)の設定手順 – 由緒正しい公式のガイド

Kivy を Pyinstaller で Windows Exe 化するときにした諸々

久々に Kivy でプログラムをつくったのですが.Exe するときにハマったのでメモ。自分の状況にピッタリマッチする記事がなかなかなかったので誰かの役にたつかも。

Kivy で UI をつくり .mp4 ファイルの生成するアプリを作成しました。 .py から import 部分を抜き出すとこんな感じになっています。

ソースは app.py という名前です。

ざっくりとしたモジュール構成は、
Kivy のいくつかの UI 部品 (ビデオ pane を含む) を配置し、 plyer でポップアップ通知がでるというものです。

pyinstaller のインストール方法や作りたい実行ファイルと同じ OS でやる必要があるとかそういったことは特に解説しません。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.video import Video
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.popup import Popup
from kivy.uix.filechooser import FileChooserListView

from plyer import notification

# このへんは、 pyinstaller が自動的に "よしな" にししてくれます。
import os
import platform
from typing import Callable, Optional
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Optional
from enum import Enum

pyinstaller 自体はとても簡単に使えるのでおむろに pyinstall --onefile app.py してみます。app.exe と app.spec ファイルが生成されます。

が、 app.exe を実行してみるといろいろファイルが足りないようで追加していききます。

sdl2 と angle を取り込む

詳細な仕組みはよくわかっていませんが、Kivy で GUI を表示するとsdl2 か glew を使うものらしいです。先程つくった .exe は sdl を使っているようなのでそれを明示してやります。

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle

deps = []
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]

exe = EXE(
  ...
  # [] のあたりに入れる
  *deps,
  ...
)

app.py にも KIVY_GL_BACKEND = ‘angle_sdl2’ を設定してやります。

今度は、編集した app.spec を使って pyinstaller app.spec を実行しします。 誤ってさっきと同じ app.py を指定するとせっかく変更した app.spec が上書きされるので注意してください。

gstreamer の取込み

app.exe を実行してみると、どうやら gstreamer 関係の初期化に失敗するようです。

なので app.spec に追記していきます。

ビデオ再生には、ffpyplayer が使われるという記述もみうけられるのですが、試した環境では gstreamer が使われていたので素直にそれに従います。
ffpyplayer の場合にも同じ様に import と deps に追加してやれば多分うまくいきます。

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle

deps = []
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]
deps += [Tree(p) for p in gstreamer.dep_bins]

exe = EXE(
  ...
  # [] のあたりに入れる
  *deps,
  ...
)

再度 pyinstaller app.spec を実行する。

今度は、pywin32 のエラーが出た。pywin32 は おそらく app.py で明示的に Kivy のタイマー処理を使っている箇所がありそこで使われているっぽい。

pywin32 は今までと異なり pyinstaller から collect_all というモジュールをインポートしてこないといけないらしい。

from PyInstaller.utils.hooks import collect_all

pywin32_datas, pywin32_binaries, pywin32_hiddenimports = collect_all('pywin32')

a = Analysis(
  ...
  hidenimports = [
    'win32timezone',
    'win32api',
    'win32con'
  ] + pywin32_hiddenimports
  ...
)

でここまでの内容をまとめると app.spec はこうなった。

# -*- mode: python ; coding: utf-8 -*-

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle, gstreamer
from PyInstaller.utils.hooks import collect_all

deps =[]
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]
deps += [Tree(p) for p in gstreamer.dep_bins]

pywin32_datas, pywin32_binaries, pywin32_hiddenimports = collect_all('pywin32')

a = Analysis(
    ['app-v18.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[
        'win32timezone',
        'pywintypes',
        'win32api',
        'win32con',
        'plyer',
        'plyer.platforms',
        'plyer.platforms.win',
        'plyer.platforms.win.notification'
    ] + pywin32_hiddenimports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    # set deps
    *deps,
    exclude_binaries=False,
    name='app', # exe の名前なので好きに変えていい
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

Kivy をパッキングするときは割とハマるので、次回はここをベースにしてなんとかしたい。

にしてもインストーラ系のビルドは、動作確認が面倒で、時間かかるのであまり好きにはなれない。

参考

Build with PyInstaller, cannot find proper OpenGL after lock windows with “Win+L” · Issue #7177 · kivy/kivy