Linux: Peppermint 9を古いPCに入れる

年初めなので埃をかぶっている旧PCのメンテをしてみました。もともとDebian 8が入っていましたが今はDebian 9になってますのでいずれ変える必要がありました。

先日Virtual BoxにPeppemint Linuxを入れてみてなかなか良さそうだったのでインストールしました。事実旧型になるほどハードルが上がりインストールは難しくなるのですが今回も例外ではありませんでした。USBは付いているのですがブートできないタイプなので一旦、インストールにコケると大変手間がかかる作業が待っています。

今回インストールした機種

Toshiba SS 2000 (RAM:400MB弱、HDD:20GB)

LufusやUNetbootinには色々オプションがありますがイメージを焼くときは必ずISOイメージそのままで焼きます。

この機種はUSBブートができませんがPlopというブートマネージャを入れるとUSBブートができるようになります。ところが今回インストール途中でハングアップして全て消えたので物理的にネット以外アクセス手段はなく、PXEBootをセットアップしました。

PXEBootはDebianのサイトにある手順で自分のネットワーク環境に合わせて変更すればほぼOKです。

DebianのPXEBootの仕組みをみてPlopをインストールしてカスタマイズします。次の2つのパッケージをtftpd-hpaサーバーのrootディレクトリ/srv/tftpにインストールします。

無事PXEBootができたらplpをタイプしてブートマネージャーを起動します。

最後にインストールが完了したらGrubにPlopを設定します。(ブートパーティションありの例)

/etc/grub.d/40_custom

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.

menuentry "Plop Boot Manager" {
set root=(hd0,msdos1)
linux16 /plpbt.bin
}

ポストインストレーション
デフォルトのグラフィックスがフレームバッファーなのでtridentのグラフィックスドライバーを入れます。aptでxserver-xorg-video-tridentパッケージをインストールしてつぎの設定ファイルをディレクトリに置きます。

/usr/share/X11/xorg.conf.d/99-trident.conf

Section "Device"
        Identifier	"gfxcard"
        Driver		"trident"
# Options under this line may (or may not) improve performace
# If you experience segmentation faults on attempting to log out of X, uncomment the
# following option.  If you do, comment or remove the "AccelMethod" option.
#       Option		"NoAccel" "True"
#       Option		"ShadowFB" "Enable"
#       Option		"NoPciBurst" "Enable"
#       Option		"FramebufferWC"
# If Xorg crashes on startup (hangs with black screen) you may try out
# the following two lines (by removing the "#" before the line):
#       Option		"NoDDC"
# The 1024 in UseTiming is for a Notebook with a native resolution of 1024x768 pixel.
# If you have a native resolution of 800x600 pixel you should use "UseTiming800" instead.
#       Option		"UseTiming1024"
# If Xorg still crashes at startup and the error 'Failed to load module "xaa"' is found
# in /var/log/Xorg.0.log, remove the "#" in the following line to use EXA acceleration:
#       Option         "AccelMethod" "EXA"
EndSection
Section "Screen"
        Identifier	"Screen 0"
        Device		"gfxcard"
        Monitor		"Monitor 0"
        DefaultDepth	16
EndSection
Section "Monitor"
        Identifier	"Monitor 0"
        Option		"DPMS" "Disable"
EndSection

参考:

軽量Linux

macOSはUNIX系ですがBSDなのでUbuntuなどのGNUコマンドとは微妙にコマンドオプションが異なります。Homebrewでこれらのコマンドセットを入れることもできるのですが、個人的にはあまり基本コマンドをいじりたくないのでVirtualBoxを使って軽量Linuxを入れてみました。いくつか候補があって、crunchbang、Bodhi Linux、Peppermint Linuxの3つをテストしてみました。軽量LinuxはOpenBoxのような軽量WMを使っているのでそれほど差はありません。収録してあるパッケージと独自のカスタム仕様がセールスポイントになります。

crunchbangはDebianをベースにしたミニマリストのディストリビューションで個人的には気に入っていたのですが、デフォルトでsambaが使えなかったりしてちょっと惜しい感じです。
Bodhiはさらに軽量化されているのですが、収録していあるブラウザがマイナーなMidoriを使っているので残念な感じです。ファイルマネージャーはPCmanFMでsambaを使うときはサイドパネルに現れないので、メニューからアクセスするのでちょっと使いづらい。一番使うターミナルもちょっと変わっていてシンプルなものを使いたい私にとっては選択肢から外れる感じです。
最後のPeppermint Linuxはかなり派手な感じだったのですが、バランス良いパッケージングでなかなかいい感じです。ちょっとカスタマイズすれば好みにあったものになりそうです。ちょっと触った感じでは設定関係も綺麗にまとめられていて特に問題なさそうだったのでHDDにインストールしてみました。MSオフィスはオンラインで使えるようになっています。ただし回線が細いとかなりストレスを感じますが軽量LinuxにわざわざLibreOfficeを入れるまでもなく、バランスよくまとめられています。

Peppermint linuxとmacOS

20GBのHDDと2GBのメモリでサクサク動きます。使い勝手も軽量Linuxにも関わらず普段使っているKDE5と遜色ありません。

私のデスクトップではまだビルトインのオーディオデバイスが使えないのですがなんらかの方法で出力することはできます。CoreELECまたはLibreELECなら簡単に接続できる上に曲の選択はできませんがリモコンで音量調整できます。alarmはBT経由でサムスンARM機のArchLinuxでBTスピーカーから出力もできます。

1行で作るパスワード

些細なことですが、パスワードを設定するときふと悩むことがあります。余計な心配をせずにセキュアなパスワード作りたいとき重宝する1行コマンドを集めて見ました。何と言っても定番はOpenSSLを使う方法です。ほかにポピュラーなのはurandomやdateコマンドを使う方法でしょう。

openssl rand -base64 24
openssl rand -base64 24 | sed 's/\///g'
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32; echo ''
date +%s | sha256sum | base64 | head -c 32 ; echo

専用のツール使う場合は細かい設定ができて魅力的です。

pwgen 32 1
gpg --gen-random --armor 1 24
apg -m 32 -x 1 -M SNCL -a 1 -n 1
makepasswd --minchars=32 --maxchars=32

Bittorrent: Transmissionのテスト

自宅のネット回線が良くないので大きなファイルをダウンロードすると途中で切断されることはよく起こります。そこでいったんVPSにファイルをダウンロードしてそこからTorrentで自宅までダウンロードしてみました。TorrentはP2P接続で回線か不安定でも安定してファイル交換できるシステムです。TorrentクライアントにはTransmissionを使いました。このアプリはWindows、Linux、macOSにインストールできます。VPS側にはTransmission-daemonを稼働させて、自宅PCにはmacOSにTransmissionを入れています。

apt install transmission-daemon

ファイル交換するためにはTrackerという追跡システムが必要になるのでopentrackerというアプリのソースをvpsにダウンロードしてコンパイルして稼働させます。ビルドは勝手がわからないとまごつくので次のスクリプトを使います。

操作環境はvpsのlxcコンテナのアドレスを10.1.1.2としてIKEv2のVPNでアクセスしています。

$ lsof -i
COMMAND     PID    USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
opentrack  2210    my01   3u   IPv4 4722611      0t0  TCP *:6969 (LISTEN)

Torrentファイルの作り方は次のようにします。

cd  /var/lib/transmission-daemon/downloads
transmission-create -o macOSUpdCombo10.13.6.dmg.torrent -c "download macos combo" -t udp://10.1.1.2:6969 macOSUpdCombo10.13.6.dmg

このtorrentファイルを自宅PCとVPSのtransmission-daemonにそれぞれ稼働させます。

transmission-daemonの使い方

  1. aptでtransmission-daemonをインストールします。
  2. transmission-daemonを停止します。
  3. /etc/transmission-daemon/settings.jsonを編集します。
  4. “rpc-whitelist”:””を自分のIPアドレスを入れます。
  5. “rpc-password”:はパスワードをダブルクォートで括って入れます。
  6. systemctlでtransmission-daemonを起動します。
  7. ブラウザでhttp://<IP>:9091にアクセスします。
  8. ID:transmission/PASS:設定したパスワードでログインします。
  9. trrentファイルをアップロードします。(リモート側と自分のPCとリンクします)

firewall:

$ netstat -nl
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:51413           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:6969            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:9091            0.0.0.0:*               LISTEN     
tcp6       0      0 :::51413                :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN
udp        0      0 0.0.0.0:51413           0.0.0.0:*                          
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 0.0.0.0:6969            0.0.0.0:*

双方のポート6969、51413、9091を開けます

ufw allow 9091
ufw allow 51413
ufw allow 6969

テスト

開始前
終了

High Sierraのセキュリティパッチのインストール、無事インストール完了!

shell scriptのトリビア

最近はスペースを含んだファイル名やディレクト名が多くてシェルスクリプトで頭を悩ますことが多くなりました。スペースが含まれたファイル名やディレクトリ名をシェルで実行するには2通りの方法があります。ひとつは’\’(バックスラッシュ)をスペースの前に付けることです。

例)This book.txt –> This\ book.txt

もう一つの方法はダブルクオートで囲むことです。

例  This book.txt –> “This book.txt”

シェルで使うにはどうするかというと、

a='apple orange and mango are fruits.txt'
touch "$a"

このように変数にダブルクオートで囲むと良いわけです。

 

仮想通貨取引のテストプログラム(update)

前回のテストプログラムでは1回分しかありませんでしたが、テストの性格上いくらでも追加できるのでプログラムを変更してみました。Pythonはオブジェクト指向のプログラムなのでclassを使うといい具合にプログラムを作成できます。今回、classは初めてなのでいろいろチュートリアルをみても基本例しか見当たらず、自己流でプログラムしています。これが正規のPythonの記述法なのかわかりませんが次の3点です。classから外部のdef関数の呼び出し、メソッドにself以外の引数割り当て、class内でほかのdef関数呼び出しです。

Bot埋め込み用:

import time
import os
import re
import sqlite3
from contextlib import closing, suppress

class testDB: 
    def __init__(self, dbname): 
        self.dbname = dbname 

    def db_create(self): 
        with closing(sqlite3.connect(self.dbname)) as conn: 
            c = conn.cursor() 
            create_table = '''CREATE TABLE IF NOT EXISTS transactions (id INT, pos VARCHAR(1), 
                              unixtime INT, price REAL, stop REAL, target REAL, flag VARCHAR(1))''' 
            c.execute(create_table) 
            conn.commit() 

    def db_insert(self, index_id, position, unixtime, btc_close, stop, target, flag = ''): 
        if position == 'short': 
            position_cap = 'S' 
        elif position == 'long': 
            position_cap = 'L' 
        with closing(sqlite3.connect(self.dbname)) as conn: 
            c = conn.cursor() 
            sql = 'insert into transactions (id, pos, unixtime, price, stop, target, flag) values (?,?,?,?,?,?,?)' 
            dat = (index_id, position_cap, unixtime, btc_close, stop, target, flag) 
            c.execute(sql, dat) 
            conn.commit()

    def db_read(self, st=''): 
        with closing(sqlite3.connect(self.dbname)) as conn: 
            c = conn.cursor() 
            select_sql = 'select * from transactions ' + st 
            c.execute(select_sql) 
            rows = c.fetchall() 
        return rows 

    def db_update(self, st=''): 
        with closing(sqlite3.connect(self.dbname)) as conn: 
            c = conn.cursor() 
            update_sql = 'update transactions ' + st 
            c.execute(update_sql) 
            conn.commit() 

    def profit_log(self, pos, position, btc_close, tag='', test_tag = ''):
        st = 'set flag = "Z", target = ' + str(btc_close) + ' where id = ' + str(key)
        testDB.db_update(self, st)

レポートプログラム:

#!/home/parvati/environments/test2_env/bin/python3

import os
import sys
#import subprocess
import re
import time
from datetime import datetime
import sqlite3
from contextlib import closing

dblist       = ['binance_test0.db' ,'binance_test1.db', 'binance_test2.db']
#dblist       = ['binance_test0.db' ,'binance_test1.db']
#dbname       = 'binance_zaif.db'
#start_hour   = 0
#start_day    = 1
#start_month  = 'jan'
#start_week   = 'sun'
timezone     = 'JST-9'          # timezone: Japan
unit         = 'daily'          # default
c_trade_cost = 0.075            # percent (total transaction costs; transaction fee, spread, swap and so on)
os.environ['TZ'] = timezone
time.tzset()
unixtime = int(time.time())
verbose = 'N'

if len(sys.argv) > 1:
    args = sys.argv[1:]
    num = int()
    #unit = str()
    flag = str()
    for n in args:
        if re.match( r'^(?!-).*$', n):
            matchObj = re.match( r'^(\d*)(\w*)', n, re.M|re.I)
        else:
            pass
        matchflag = re.match( r'^-(\w)$', n)
        matchflag2 = re.match( r'^--(\w*)$', n)
        if 'matchObj' in locals():
            if matchObj:
                if matchObj.group(1):
                    num  = int(matchObj.group(1))
                unit = matchObj.group(2)
                if re.match( r'daily$', unit, re.M|re.I):
                    unit = 'daily'
                elif re.match( r'weekly$', unit, re.M|re.I):
                    unit = 'weekly'
                elif re.match( r'monthly$', unit, re.M|re.I):
                    unit = 'monthly'
                else:
                    unit = 'daily'
        if 'matchflag' in locals():
            if matchflag:
                flag = matchflag.group(1)
                if flag == 'v':
                    verbose = 'Y'
                else:
                    print('ERROR: wrong argument!')
                    exit(1)
        if 'matchflag2' in locals():
            if matchflag2:
                flag = matchflag2.group(1)
                if flag == 'verbose':
                    verbose = 'Y'
                else:
                    print('ERROR: wrong argument!')
                    exit(1)
#                print('verbose = Y', unit)
#        elif re.match( r'd$|days?$', unit, re.M|re.I) and num > 0:
#            unit = 'd'
#        elif re.match( r'w$|weeks?$', unit, re.M|re.I) and num > 0:
#            unit = 'w'
#        elif re.match( r'm$|months?$', unit, re.M|re.I) and num > 0:
#            unit = 'm'
#        elif re.match( r'y$|years?$|yrs?$', unit, re.M|re.I) and num > 0:
#            unit = 'y'
#        elif re.match( r'yearly$', unit, re.M|re.I):
#            unit = 'yearly'
class ProfitReport:
    def __init__(self, dbname):
        self.dbname = dbname

    def db_read(self, args):
        with closing(sqlite3.connect(self.dbname)) as conn:
            c = conn.cursor()
            select_sql = 'select * from transactions where ' +  str(args)
            c.execute(select_sql)
            rows = c.fetchall()
        return rows

    def db_read_one(self, args):
        with closing(sqlite3.connect(self.dbname)) as conn:
            c = conn.cursor()
            select_sql = str(args)
            c.execute(select_sql)
            row = c.fetchone()
        return row

    # TABLE: id, pos, unixtime, price, stop, target, flag
    def get_data(self, t_start, t_end):
        profit = float()
        price_high = float()
        price_low = float()
        fee = float()
        st = 'unixtime > ' + str(t_start) + ' and unixtime < ' + str(t_end) + ' and flag = "Z"'
        rows       = ProfitReport.db_read(self, st)
        _a         = ProfitReport.db_read_one(self, 'select max(price) from transactions where ' + st)
        price_high = _a[0]
        _a         = ProfitReport.db_read_one(self, 'select min(price) from transactions where ' + st)
        price_low  = _a[0]
        _a         = ProfitReport.db_read_one(self, 'select count(*) from transactions where ' + st)
        count      = _a[0]
     #   print(_a)
        if verbose == 'Y':
            print('^')
            print('    ID    ' + ' '.join('{:>8}'.format(item) for item in ('Pos', 'Profit', 'Fee', 'Process')))
        for d in rows:
            _tmp = d[3] * c_trade_cost / 100
            fee = fee + _tmp
            if d[1] == 'L':
                profit = profit + (d[5] - d[3])
                if verbose == 'Y':
                    ts = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(d[0]))
                    print(str(ts) + ' '.join('{:>8}'.format(item) for item in ( d[1], round((d[5] - d[3]), 2), round(_tmp, 2), d[6])))
            elif d[1] == 'S':
                profit = profit + (d[3] - d[5])
                if verbose == 'Y':
                    ts = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(d[0]))
                    print(str(ts) + ' '.join('{:>8}'.format(item) for item in (d[1], round((d[3] - d[5]), 2), round(_tmp, 2), d[6])))
        if count == 0:
            profit, price_high, price_low, fee = 0, 0, 0, 0
        return profit, price_high, price_low, count, fee

    def calc(self, unit):
        if unit == 'daily':
            t_start = unixtime - (1 * 24 * 60 * 60)
            t_end = unixtime
        elif unit == 'weekly':
            t_start = unixtime - (1 * 24 * 60 * 60 * 7)
            t_end = unixtime
        elif unit == 'monthly':
            t_end = unixtime
            t_end_customized = time.localtime(t_end)
            t_m = time.strftime("%m", t_end_customized)
            t_y = time.strftime("%y", t_end_customized)
            t_y_leap = t_y % 4
            if re.match( r'^1$|^5$|^7$|^8$|^10$|^12$', str(t_m)):
                t_d = 30
            elif re.match( r'^2$|^4$|^6$|^9$|^11$', str(t_m)):
                t_d = 31
            elif re.match( r'^3$', t_m) and t_y_leap != 0:
                t_d = 28
            elif re.match( r'^3$', t_m) and t_y_leap == 0:
                t_d = 29
            t_start = unixtime - (1 * 24 * 60 * 60 * t_d)
        return t_start, t_end

    def report(self, unit):
        t_start, t_end = ProfitReport.calc(self, unit)
        profit, price1, price2, count, fee = ProfitReport.get_data(self, t_start, t_end)
        print('^')
        print(' DB name      :', self.dbname)
        print(' Action       :', unit, 'profit')
        print(' From         :', time.strftime('%X %x %Z', time.localtime(int(t_start))))
        print(' TO           :', time.strftime('%X %x %Z', time.localtime(int(t_end))))
        print(' Transactions :', '{:>5}'.format(count))
        print(' Price(high)  :', '{:>8.2f}'.format(price1))
        print(' Price(low)   :', '{:>8.2f}'.format(price2))
        print(' Income       :', '{:>8.2f}'.format(profit))
        print(' Fee          :', '{:>8.2f}'.format(fee))
        print(' Profit       :', '{:>8.2f}'.format(profit - fee))
        print('')

db = []
for n in range(0, len(dblist)):
    _a = ProfitReport(dblist[n])
    db.append(_a)
    db[n].report(unit)

2個のデーターベースを動かしてみましたが、今のアルゴリズムだと取引回数が増えると利益がでるが手数料等でマイナスになります。

テスト1回目
テスト2回目