PT3サーバ自動起動・シャットダウン用スクリプト
BIOSの設定
RTCで起動できる様に設定する
Asrock H77M-ITXの場合は
Advanced Screen -> ACPI Configuration -> RTC Alarm Power On
自動復帰の確認
以下のコマンドを実行し,3分後に自動的に起動されることを確認する
$ echo `date +%s -d +3min` | sudo tee /sys/class/rtc/rtc0/wakealarm $ sudo shutdown -h now
上記のコマンドで成功しない場合はrtcwake
を使用すると成功するかもしれません
スクリプト作成
とりあえず,スクリプト全体は以下の通り.
#!/bin/python # -*- coding: utf-8 -*- import subprocess import datetime import time import logging from logging.handlers import RotatingFileHandler SUDO_PASS="実行ユーザのパスワード\n" log_handler = RotatingFileHandler( filename="/var/www/html/epgrec/video/pt3_savepower.log", mode="a", maxBytes=1000000, backupCount=3, encoding="utf-8", delay=False ) # log_handler.setLevel(logging.INFO) log_handler.setFormatter( logging.Formatter("%(asctime)s - %(levelname)-8s - %(message)s") ) logger = logging.getLogger() logger.setLevel(logging.INFO) logger.addHandler(log_handler) def is_terminal_login(): stdout, stderr = subprocess.Popen( ["/usr/bin/who"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate() result = len([line for line in stdout.split("\n") if not line == ""]) logger.info("[terminal]: %d" % result) return False if result is 0 else True def is_recording_at(): stdout, stderr = subprocess.Popen( ["sudo", "-S", "/bin/atq"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate(SUDO_PASS) result = len([line for line in stdout.split("\n") if "=" in line]) logger.info("[rec_at ]: %d" % result) logger.debug(stdout) return False if result is 0 else True def is_recording_ps(): stdout, stderr = subprocess.Popen( ["/bin/ps", "-el"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate() ps_recpt1 = [line for line in stdout.split("\n") if "recpt1" in line] result = len(ps_recpt1) logger.info("[rec_ps ]: %d" % result) logger.debug(stdout) return False if result is 0 else True def is_port_established(): stdout, stderr = subprocess.Popen( ["sudo", "-S", "/sbin/lsof", "-i", ":ssh,http,https"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate(SUDO_PASS) port_est = [line for line in stdout.split("\n") if "ESTABLISHED" in line] result = len(port_est) logger.info("[port ]: %d" % result) logger.debug(stdout) return False if result is 0 else True def convert_timestr(atq_line): result = atq_line.replace("Jan", "01") result = result.replace("Feb", "02") result = result.replace("Mar", "03") result = result.replace("Apr", "04") result = result.replace("May", "05") result = result.replace("Jun", "06") result = result.replace("Jul", "07") result = result.replace("Aug", "08") result = result.replace("Sep", "09") result = result.replace("Oct", "10") result = result.replace("Nov", "11") return result.replace("Dec", "12") def get_at_schedules(): stdout, stderr = subprocess.Popen( ["sudo", "-S", "/bin/atq"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate(SUDO_PASS) schedule = [] for line in stdout.split("\n"): if line is "": continue tmp = line.split() time_str = convert_timestr(" ".join([tmp[5], tmp[2], tmp[3], tmp[4]])) time_str = datetime.datetime.strptime(time_str, "%Y %m %d %H:%M:%S") unixtime = int(time.mktime(time_str.timetuple())) schedule.append(unixtime) return schedule def get_closest_schedule(): return sorted(get_at_schedules())[0] def is_scheduled(unixtime): return any(s == unixtime for s in get_at_schedules()) def set_getepg_schedule(unixtime): if is_scheduled(unixtime): logger.info("getepg is already scheduled") else: getepg_at = datetime.datetime.fromtimestamp(unixtime).strftime("%y%m%d%H%M") stdout, stderr = subprocess.Popen( ["/bin/at", "-f", "/var/www/html/epgrec/video/at_getepg.sh", "-t", getepg_at], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate() logger.info("[getepg ]: %s" % datetime.datetime.fromtimestamp(unixtime)) def set_rtcwake(unixtime): logger.info("[wakeup ]: %s" % datetime.datetime.fromtimestamp(unixtime)) stdout, stderr = subprocess.Popen( ["sudo", "-S", "/usr/sbin/rtcwake", "-m", "off", "-t", str(unixtime)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate(SUDO_PASS) logger.debug(stdout) def main(): logger.info("=============== START LOGGING ===============") if any([ is_terminal_login(), is_recording_at(), is_recording_ps(), is_port_established() ]): logger.info("someone using") else: now = int(time.time()) closest_waketime = now + 1800 closest_schedule = get_closest_schedule() wake_to_record = closest_schedule - 300 wake_to_getepg = closest_schedule - 1800 getepg_time = wake_to_getepg + 300 logger.info("NOW : %s" % datetime.datetime.fromtimestamp(now)) logger.info("WEPG : %s" % datetime.datetime.fromtimestamp(wake_to_getepg)) logger.info("GEPG : %s" % datetime.datetime.fromtimestamp(getepg_time)) logger.info("WREC : %s" % datetime.datetime.fromtimestamp(wake_to_record)) logger.info("CLOSE: %s" % datetime.datetime.fromtimestamp(closest_schedule)) if wake_to_getepg > closest_waketime: set_getepg_schedule(getepg_time) set_rtcwake(wake_to_getepg) elif wake_to_record > closest_waketime: set_rtcwake(wake_to_record) else: logger.info("close to wakeup time") if __name__ == '__main__': main()
グローバルスコープ
SUDO_PASS
スクリプト実行ユーザーのパスワードを入れてsudo
を実行できるようにする。
"パスワード\n"
のように末尾に\n
が入っているが,これがパスワード入力後のエンターキーの代わりになる。log_handler
に循環ログを定義する
main()
is_terminal_login()
ターミナルにログインしているユーザがいるかis_recording_at()
録画中かをat
コマンドで確認is_recording_ps()
録画中かをps
コマンドで確認is_port_established()
ssh,http,httpsのポートが使用中(接続中)であるかを確認
で,サーバの使用状況を確認し,シャットダウンして問題がなければ,
now = int(time.time())
現在時刻closest_waketime = now + 1800
次回起動の最短時刻(シャットダウンした場合に現在時刻から1800秒以内に起動する必要がある場合はシャットダウンしないようにするための値)closest_schedule = get_closest_schedule()
一番近い録画開始時刻wake_to_record = closest_schedule - 300
録画のために起動する時間(起動から録画開始まで,300秒の余裕をもたせる)wake_to_getepg = closest_schedule - 1800
時間に余裕がある場合は録画開始の1800秒前に起動しEPGの更新を行うgetepg_time = wake_to_getepg + 300
EPGの更新を行う場合にEPGの更新を開始する時刻(起動からEPG更新開始まで,300秒の余裕をもたせる)
上で設定した時刻を使用し
- 時間に余裕がある(
wake_to_getepg > closest_waketime
)場合
at
にEPG取得のスケジュールを登録した後,rtcwake
でシステムを停止する - EPG更新の余裕がない(
wake_to_record > closest_waketime
)場合は
rtcwake
でシステムを停止するだけにする - それ以外の場合 現在時刻から30分以内に録画開始する必要があるのでPCへの負荷を考慮し,シャットダウンせずに待機する
is_terminal_login()
who
を使用し,ログイン中のユーザを取得する
以下のコマンドを実行し,行数を数えることでログイン中のユーザがいるかを判断する
$ who ユーザー名 pts/0 2016-01-23 18:42 (192.168.19.5) ユーザー名 pts/1 2016-01-23 19:09 (192.168.19.5)
who
の実行結果stdout
に対し,以下の処理をすることで行数を数える。
len([line for line in stdout.split("\n") if not line == ""])
who
の実行結果を\n
で分割すると
[ "ユーザ1...", "ユーザ2...", "" ]
のように最後の改行コード後にも文字列があるように分割されるので
if not line == ""
を入れている
is_recording_at()
atq
を使用し,実行中のatジョブを取得する
$ sudo atq 524 Fri Jan 29 00:29:00 2016 a ユーザー名 455 Sat Jan 23 02:30:00 2016 = ユーザー名 465 Sun Jan 24 03:45:00 2016 a ユーザー名 466 Sun Jan 24 12:15:00 2016 a ユーザー名 467 Sun Jan 24 09:35:00 2016 a ユーザー名
sudo atq
の実行結果stdout
に対し,以下の処理で=
を含む行数を数える。
len([line for line in stdout.split("\n") if "=" in line])
is_recording_ps()
ps
を使用し,recpt1
を使用しているプロセスがあるかを確認する
$ ps -el F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD ... 0 S 1000 2936 2935 0 82 2 - 68925 hrtime ? 00:00:00 recorder.php 0 S 1000 2940 2936 0 82 2 - 2902 wait ? 00:00:00 do-record.sh 0 S 1000 2941 2940 4 82 2 - 59385 pt3_dm ? 00:02:01 recpt1 0 S 0 2942 1 0 80 0 - 61852 poll_s ? 00:00:00 pcscd ...
ps -el
の実行結果stdout
に対し,以下の処理でrecpt1
を含む行数を数える。
len([line for line in stdout.split("\n") if "recpt1" in line])
is_port_established()
lsof
を使用し,ssh,http,https
が使用されているかを確認する
$ sudo lsof -i :ssh,http,https COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 1081 root 3u IPv4 17772 0t0 TCP *:ssh (LISTEN) sshd 1081 root 4u IPv6 17783 0t0 TCP *:ssh (LISTEN) nginx 1127 root 6u IPv4 18486 0t0 TCP *:http (LISTEN) nginx 1128 nginx 3u IPv4 27768 0t0 TCP PT3:http->192.168.19.5:34158 (ESTABLISHED) nginx 1128 nginx 6u IPv4 18486 0t0 TCP *:http (LISTEN) sshd 3032 root 3u IPv4 25384 0t0 TCP PT3:ssh->192.168.19.5:60890 (ESTABLISHED) sshd 3034 pt3 3u IPv4 25384 0t0 TCP PT3:ssh->192.168.19.5:60890 (ESTABLISHED) sshd 3043 root 3u IPv4 25436 0t0 TCP PT3:ssh->192.168.19.5:60902 (ESTABLISHED) sshd 3045 pt3 3u IPv4 25436 0t0 TCP PT3:ssh->192.168.19.5:60902 (ESTABLISHED)
sudo lsof -i :ssh,http,https
の実行結果stdout
に対し,以下の処理でESTABLISHED
を含む行数を数える。
len([line for line in stdout.split("\n") if "ESTABLISHED" in line])
get_at_schedule()
atq
でスケジュールされているスケジュールの各時刻をunixtimeに変換したリストを返す
atq
からは以下のフォーマットで出力されるので
467 Sun Jan 24 09:35:00 2016 a ユーザー名
以下のようにフォーマットを変更し
convert_timestr()
関数で月コード(Jan)を数値(01)に変換する
2016 01 24 09:35:00
この文字列を以下でunixtimeに変換する
time_str = datetime.datetime.strptime(time_str, "%Y %m %d %H:%M:%S") unixtime = int(time.mktime(time_str.timetuple()))
get_closest_schedule()
get_at_schedule()
から得られたunixtimeのリストをソートし,最小値(最も近いスケジュールのunixtime)を取得する
set_getepg_schedule()
引数のunixtimeの時刻にat
にEPG更新をスケジュールする
- サーバが自動起動される前に手動でサーバを起動した場合には同じ時刻に複数のEPG更新がスケジュールする可能性があるので
is_scheduled()
で引数のunixtimeの時刻にat
にスケジュールがないかを確認する at
は直接php(getepg.php
)を実行できないため,以下のシェルスクリプトを経由してスケジュールを登録する
#!/bin/bash /var/www/html/epgrec/getepg.php
set_rtcwake()
rtcwake
を使用し,自動起動時刻を設定する
また,rtcwake
を実行すると自動的にシャットダウンされる
スクリプトのcron実行
sudo
を含むスクリプトをcronで実行すると
sudo: sorry, you must have a tty to run sudo
と言われ実行できないのでttyなしでsudo
できるようにする
今回はPT3専用のサーバなので簡単に以下のように56行目のDefaults requiretty
をコメントアウトした
$ sudo visudo # Disable "ssh hostname sudo <cmd>", because it will show the password in clear. # You have to run "ssh -t hostname sudo <cmd>". # #Defaults requiretty
以上で実行できるはずなので10分ごとにスクリプトを実行するようにする
$ crontab -e */10 * * * * python /path/to/script.py
References
RaspberryPiのSDカード寿命対策
システムはRaspbian Jessie Liteを使用した
swapを停止する
swapの使用状況を確認
$ free total used free shared buffers cached Mem: 948108 100608 847500 6432 8008 62572 -/+ buffers/cache: 30028 918080 Swap: 102396 0 102396
/etc/fstab
に以下の記述があったため
# a swapfile is not a swap partition, no line here # use dphys-swapfile swap[on|off] for that
dphys-swapfile
を停止する
$ sudo sysv-rc-conf dphys-swapfile off
再起動して確認
$ sudo reboot $ free total used free shared buffers cached Mem: 948108 166408 781700 6432 7152 128220 -/+ buffers/cache: 31036 917072 Swap: 0 0 0
RAMDiskを使う
/var/log
をRAMDiskにする際はスクリプトを作る必要があるそうだが,問題がなかったので作成しない
/tmp
/var/tmp
/var/log
をtmpfsでマウントする
$ sudo vim /etc/fstab proc /proc proc defaults 0 0 /dev/mmcblk0p1 /boot vfat defaults 0 2 /dev/mmcblk0p2 / ext4 defaults,noatime 0 1 # a swapfile is not a swap partition, no line here # use dphys-swapfile swap[on|off] for that tmpfs /tmp tmpfs defaults,noatime 0 0 tmpfs /var/tmp tmpfs defaults,noatime 0 0 tmpfs /var/log tmpfs defaults,noatime 0 0
マウントできるか確認
$ sudo rm -rf /tmp/* && sudo mount /tmp $ sudo rm -rf /var/tmp/* && sudo mount /var/tmp $ sudo rm -rf /var/log/* && sudo mount /var/log $ df -h ... tmpfs 463M 0 463M 0% /tmp tmpfs 463M 0 463M 0% /var/tmp tmpfs 463M 0 463M 0% /var/log
一応,再起動したあとに確認
$ df -h tmpfs 463M 116K 463M 1% /var/log tmpfs 463M 0 463M 0% /var/tmp tmpfs 463M 0 463M 0% /tmp
順番は変わったが,ちゃんと動いてそう
Ubuntu15.10でdockerを動かす
インストール
$ sudo apt-get -y install docker.io
ユーザ設定
dockerグループではないユーザでは以下のようなエラーが出る
$ docker images FATA[0000] Get http://%2Fvar%2Frun%2Fdocker.sock/v1.18/images/json: dial unix /var/run/docker.sock: connect: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?
sudoするかdockerを使用するユーザをdockerグループに追加すればいいので
dockerグループに追加する
$ sudo groupadd docker $ sudo gpasswd -a ${USER} docker $ sudo systemctl restart docker $ sudo reboot
dockerを試す
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE $ docker pull centos latest: Pulling from centos 47d44cb6f252: Pull complete 838c1c5c4f83: Pull complete 5764f0a31317: Pull complete 60e65a8e4030: Pull complete centos:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security. Digest: sha256:8072bc7c66c3d5b633c3fddfc2bf12d5b4c2623f7004d9eed6aae70e0e99fbd7 Status: Downloaded newer image for centos:latest $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos latest 60e65a8e4030 2 weeks ago 196.6 MB
イメージが準備出来たので コンテナを起動
$ docker run -it centos:latest [root@a7c6fef2e476 /]# whoami root [root@a7c6fef2e476 /]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) [root@a7c6fef2e476 /]# exit
scikit-learnインストール時のエラー対策2
以前,scikit-learnインストール時のエラー対策で
CentOS7にnumpy-1.10.1
+ scikit-learn-0.16.1
の環境を構築した時に以下のエラーが発生し,numpyを1.9.3にすることでエラーを回避した.
(VENV)$ pip isntall scikit-learn ... /usr/bin/ld: cannot find -lcblas collect2: error: ld returned 1 exit status /usr/bin/ld: cannot find -lcblas collect2: error: ld returned 1 exit status error: Command "g++ -pthread -shared build/temp.linux-x86_64-2.7/sklearn/svm/liblinear.o build/temp.linux-x86_64-2.7/sklearn/svm/src/liblinear/tron.o build/temp.linux-x86_64-2.7/sklearn/svm/src/liblinear/linear.o -L/usr/lib64/atlas -Lbuild/temp.linux-x86_64-2.7 -lcblas -lm -o build/lib.linux-x86_64-2.7/sklearn/svm/liblinear.so" failed with exit status 1
今回,numpy-1.10.4
+ scikit-learn-0.17
でも同様のエラーが発生した.
調べてみると,下のページが見つかった
cblasはatlasの中にあるそうなので
$ ln -s /usr/lib64/atlas/libsatlas.so /usr/lib64/atlas/libcblas.so
とすればいいらしいのでやってみると
(VENV)$ pip install scikit-learn Collecting scikit-learn Using cached scikit-learn-0.17.tar.gz Building wheels for collected packages: scikit-learn Running setup.py bdist_wheel for scikit-learn Stored in directory: /root/.cache/pip/wheels/f6/4e/d3/9f5a279531fddfc7fa3979adb24041323e4fb7421756261921 Successfully built scikit-learn Installing collected packages: scikit-learn Successfully installed scikit-learn-0.17
うまくいった.
UbuntuでIPを固定する
Ubuntu15.10 ServerでのIP固定方法
resolvconfのインストール
/etc/network/interfaces
内にDNSの設定もまとめられるようにする
$ sudo apt-get -y install resolvconf
設定ファイルの編集
$ sudo vim /etc/network/interfaces #iface eth0 inet dhcp iface eth0 inet static address ホストIP netmask ネットマスク gateway ゲートウェイ dns-nameservers DNSサーバ(スペース区切りで複数設定可)
変更の適用
$ sudo reboot
Raspberry Piを簡易DNSサーバにする
RaspbianをディスプレイなしでインストールするでRaspberryPi2 ModelBにインストールしたRaspbian Jessie Liteを使用した
DNSサーバのインストール
$ sudo apt-get -y install dnsmasq
上位DNSサーバの設定
DNSサーバの設置環境が
WAN
- 192.168.11.
- 192.168.19.
- RaspberryPi (192.168.19.104)
となっているので以下のように上位DNSを192.168.11.1
に設定する
$ sudo vim /etc/dhcpcd.conf ... interface eth0 static ip_address=192.168.19.104/24 static routers=192.168.19.1 static domain_name_servers=192.168.11.1
DNSレコードの編集
/etc/hosts
内にDNSレコードを追加する
$ sudo vim /etc/hosts 127.0.0.1 localhost 127.0.1.1 raspberrypi 192.168.19.2 HOST1 192.168.19.3 HOST2 ...
DNSサーバ起動
$ sudo service dnsmasq restart $ sudo sysv-rc-conf dnsmasq on
ルータの設定
ルータ(192.168.19.1
)がデフォルトで使用するDNSサーバを変更する
Aterm WG1800HP2の場合は
トップページ > 基本設定 > 接続先設定
で
と設定した
Raspbianをディスプレイなしでインストールする
Raspbianをダウンロード
Download Raspbian for Raspberry Piから
RASPBIAN JESSIE LITE (2015-11-21-raspbian-jessie-lite.img)をダウンロードする
Raspbianの書き込み
Raspbianの書き込みにはUbuntu15.10を使用する
dd
を使った方法ではRaspbianが起動できなかったので
ディスクイメージライターを使用して書き込んだ
IPアドレスを調べる
ルータのDHCP割り当て状態から
Raspberry PiのMACアドレス(B8:27:EB:??:??:??)に割り当てられているIPを調べる
ルータで調べられない場合は以下のスクリプトで調べる
readonly IP_NETWORK=192.168.19 for host in `seq 1 254`; do ping -c 1 -w 0.5 ${IP_NETWORK}.${host} > /dev/null arp -a ${IP_NETWORK}.${host} | grep ether done
SSHでログインする
- ユーザー名: pi
- パスワード: raspberry
でログインする
IP固定
vimのインストール
$ sudo apt-get -y install vim
/etc/dhcpcd.conf
の編集
$ sudo vim /etc/dhcpcd.conf interface eth0 static ip_address=192.168.19.104/24 static routers=192.168.19.1 static domain_name_servers=192.168.19.1
再起動
$ sudo reboot