RAIDをやめようと思ってスーパーブロックを削除しちゃった話(解決済み)

よく分かっていないまま記事を書いているところがあるので、間違っている部分があるかもしれません!その場合はどうぞご指摘下さい。

症状

2TBのHDD2台でRAID1を組んでいたが、書き込み速度が遅いのが気になってRAID解除しようとした。どっかのサイトに乗っていたのに従って、

sudo umount /dev/md0
sudo mdadm --misc --stop /dev/md0

をコマンドラインで実行。続いてあろうことかスーパーブロックの削除を行ってしまった。

※これをやるとHDDがマウントできなくなる
sudo mdadm --misc --zero-superblock /dev/sda1 /dev/sdb1

よく知らないままやったのが最大の間違い。このコマンドでHDDのパーティション区切り等の情報を保存している領域を0で上書きしてしまう。しかも両方のHDDに適用してしまった。

試行錯誤

スーパーブロックが壊れたり消してしまった場合でも、HDD内にバックアップが残っているらしいのでそこから復元を試みた。

#スーパーブロックが保存されている場所を表示
sudo mkfs.ext3 -n /dev/hda5
#スーパーブロックの場所を指定して読み込み出来るかチェック
sudo fsck.ext3 -b 32768 -B 4096 /dev/hda5

参考ページ https://www.atmarkit.co.jp/flinux/rensai/linuxtips/728fixpartition.html

結果、バックアップの場所は表示されるものの、どのバックアップを使っても復元は出来なかった。

解決

打つ手がないので数万円払ってデータリカバリサービスに頼もうかと思ってた頃、あっさりと解決した。

失われたデータを復元するのではなく、同じRAID構成でもう一度作ってしまおうという発想だ。

まず、動作中のRAIDデバイス(HDDが壊れてもRAIDデバイスが動かないながらも残っていることがある。)があれば停止。

sudo mdadm --stop /dev/md0
mdadm: stopped /dev/md0

続いて、動いていた頃と全く同じようにRAIDを作ってみる。おそらくRAIDデバイスを作るときにスーパーブロックが作成される。全く同じ条件で作ればスーパーブロックも同じ?…にはならなくとも使えるようにはなると思い、一か八かやってみた。

mdadm –createオプションで作る。作るデバイス名は/dev/md0で良いのかもしれないけど、わかりやすさのために一応別の名前にしている。

壊れたHDDを2台とも繋いだまま再作成してもいいけど、また失敗してデータを失わないように、一番最初に壊れた時点のHDDは取っておいて他の1台だけで作業した。HDD1台でRAIDを作るときは、欠けているデバイスの部分は「missing」を入れると存在しなくても作ってくれる。すごい。

sudo mdadm --create /dev/md1 --verbose --level=1 --raid-devices=2 /dev/sda missing
mdadm: /dev/sda appears to be part of a raid array:
       level=raid0 devices=0 ctime=Thu Jan  1 09:00:00 1970
mdadm: partition table exists on /dev/sda but will be lost or
       meaningless after creating array
mdadm: Note: this array has metadata at the start and
    may not be suitable as a boot device.  If you plan to
    store '/boot' on this device please ensure that
    your boot-loader understands md/v1.x metadata, or use
    --metadata=0.90
mdadm: size set to 1953383488K
mdadm: automatically enabling write-intent bitmap on large array
Continue creating array? y
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md1 started.

僕の場合は作成した時点で/dev/md1が勝手にマウントされた。手動マウントするなら、

sudo mount /dev/md1 /mnt

もし何も反応がない場合はダメ押しの再起動を試してみる。

sudo reboot

これで何とかなった。

ownCloud入れた(初期設定エラー)

今日は自前で作るクラウド、ownCloudをTinkerBoardに導入してみました。1TBの外付けハードディスクにデータを保存すれば容量無制限のDropBox的なやつが作れちゃいます。というわけでインストールではまった所を挙げておきます。

管理者アカウント作るところで「 このユーザ名は既に使用されています」と出る

owncloudフォルダコピー、mysqlデータベース作成、サーバ設定後、ブラウザからアクセスして初期設定画面が出るとこまではOKでしたが、各項目を正しく入力しても「このユーザ名は既に使用されています」と出て進めなくなりました。ちなみにデータベースにはSQLiteではなくMySQLを使用していましたが、原因はその前に何回か初期設定に失敗したことでデータベースにユーザだけ存在してしまったことでした。次のコマンドでデータベースを削除したら行けました。

sudo mysql -u root
mysql>drop database cp932db;
mysql>exit;

あとはデータベースの作成からやり直せばOKです。
参考サイト

外部ストレージにデータを置きたい

外付けHDDにデータを置きたかったので、owncloudフォルダをHDDに移して、パーミッションを変更しました。ところがnginxでドキュメントルートをowncloudフォルダがあるパスにしても、ブラウザでアクセスすると「File not found.」と表示されてしまいました。パスが/media/ppdr/xxxとかになると思いますが/mediaも/media/ppdrも/media/ppdr/xxxも所有者をwww-data(nginxの場合)にしないといけないみたいです。

sudo chown www-data:www-data /media
sudo chown www-data:www-data /media/ppdr
sudo chown www-data:www-data /media/ppdr/xxx

結構適当にやってしまったので間違っているかもしれません。ご存知の方がいらっしゃいましたらご教示ください。

WordPressを使おうと思っても80番ポートだけ外部からアクセスできない問題

症状

Nginxで80番でWordPress、8080番で適当なhtmlファイルにアクセスするように設定したら、

  • LAN内からローカルIPアドレス「192.168.0.5」→アクセスできる
  • LAN内から80番以外のポート「192.168.0.5:8080」→アクセスできる
  • LAN外からグローバルIPアドレスまたはドメイン名「ppdr.softether.net」→アクセスできない
  • LAN外から80番以外のポート「ppdr.softether.net:8080」→アクセスできる

チェックしたこと

ファイアウォールを確認

ファイアウォールが有効になっていて80番ポートをブロックしていないか確かめた。でもファイアウォール自体入っていなかった。

nginxが正しく80番ポートを受けているか

nginxが正しく機能しているか確認した。ポートを待ち受けているプロセスを見るには、

$ sudo netstat -ltupn
稼働中のインターネット接続 (サーバのみ)
Proto 受信-Q 送信-Q 内部アドレス 外部アドレス 状態 PID/Program name
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1188/nginx: master
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1188/nginx: master

いた。そもそもLAN内からはアクセスできるから待ち受けてはいる。

DDNSが正しく機能しているか

DNSのチェックサービスを利用してDNSと自宅のサーバーのグローバルIPが正しく結びついているか確認した。大丈夫だった。

ブラウザのキャッシュをクリア

LAN外からのアクセスにはスマホを使っていた。同じURLで何度か試しているので前回読み込んだ結果がキャッシュに保存されているのかもしれない。chromeの「︙」から「履歴」→「閲覧データを削除…」→「キャッシュされた画像とファイル」にチェックを入れてデータを消去。

これをやることで表示のされ方が変わった。もう一歩。

WordPressの設定

これ重要。

WordPressの初期設定はローカルでの接続で行っていたので、サイトのURLが「http://192.168.0.5」になっていた。WordPressの「設定」→「一般」から「 WordPress アドレス (URL) 」と「サイトアドレス(URL)」を「http://ppdr.softether.net」に変えた。

これで正しく表示できるようになった!!
よかったよかった。

PS4のコントローラの値をLinuxで表示

はじめに

プログラミングの勉強を始めた頃は、プログラム書いたは良いけどターミナルから値を入力するのダサすぎ…って思ってたものです。
電気回路の知識がちょっと増えるにつれて、押しボタンスイッチとかジョイスティックを押したのを検知してプログラムを起動できるようになりましたが、今回はUbuntuやラズパイ上で動いているプログラムにPS4のコントローラから数値入力を渡す方法を模索したいと思います。

「PS4 Linux」で検索するとPCゲームのコントローラにPS4コントローラを使う方法が出てきますが、今回は自分で作ったプログラムにコントローラの値を受け渡す方法です。

意外と情報が少ない

ROSを使っていれば、PS4コントローラでコントロールする方法は簡単に見つかります。pythonの「ds4drv」でコントローラとPCを接続して、ROSのjoy_nodeで値を読み込んでjoyトピックで出してくれるのでそれを使うだけです。

しかし、ROSを使ってないシステムでコントローラの値を使いたいときはどうすれば良いんじゃ…ってことで探してみました。

やり方

環境:
Ubuntu 16.04、ラズパイ(Jessie)

コントローラをPCと接続するところまでは「ds4drv」を使います。次のコマンドをターミナルに入力してインストール、起動します。

sudo pip install ds4drv

sudo ds4drv

起動したらPS4コントローラの「PSボタン」と「SHAREボタン」を青のランプがカチカチッと早い点滅をするまで同時に押します。成功すると「connected」的なのがターミナルに出ると思います。

続いて値の表示ですが、こちらのページにサンプルプログラムと解説がありましたがヘッダ等がありませんでしたので、色々調べた結果コンパイルできるようになったものを載せます。

#include <linux/joystick.h>
#include <linux/input.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#define BUTTON_DATA_MAX 100
#define STICK_DATA_MAX 100

int fd= open( "/dev/input/js0", O_RDONLY );

unsigned char  ButtonData[BUTTON_DATA_MAX];
signed int     StickData[STICK_DATA_MAX];

int main(int argc, char** argv){

	for(;;){
		  struct js_event  event;
		  if( read( fd, &event, sizeof(struct js_event) ) >= sizeof(struct js_event) ){
		      switch( event.type & 0x7f ){
		      case JS_EVENT_BUTTON:
		          if( event.number < BUTTON_DATA_MAX ){
		              ButtonData[ event.number ]= event.value != 0;
		              std::cout << event.value << std::endl;

		          }
		          break;
		      case JS_EVENT_AXIS:
		          if( event.number < STICK_DATA_MAX ){
		              StickData[ event.number ]= event.value;
		          }
		          break;
		      }
		  }
	}
	close( fd );
}

これをds4.cppに保存してg++でコンパイル

g++ ds4.cpp

すればいけるはずです。
ちなみにjoystick.hの場所は僕の環境では/usr/include/linux/joystick.hでした。

出力ファイルa.outができていると思うので実行すると、ボタンを押したタイミングで1が出力されるはずです。

ppdr@raspberrypi:~ $ ./a.out
0
0
0
0
1
0

あとは値を表示するなり他のプログラムにパイプで渡したり自由にどうぞ。

./a.out | ./my_program.exe

 

プログラムの内容は全然追えていませんが、event.numberがボタンの種類、event.valueがボタンの値を示すみたいです。この辺はどうなってるかまだ把握していないので、std::coutしてみてご確認ください。

この辺の情報をご存知の方は教えていただけると幸いですm(_ _)m

お疲れ様キーボードを作る!

動機

以前Twitterでこんな投稿を見つけました。

「お疲れ様でした」や「ありがとうございます」等の定型文が入力できるキーボードです。
めっちゃ便利そうなので自分で作ってみることにしました!

仕様

とりあえずの構想はこんな感じで行きます

  • キーを押したことをArduino Nanoで認識
  • Arduino NanoからUSB経由でパソコンにデータを送信
  • パソコン側でPythonプログラムがデータを受信
  • プログラムが文字を打ち込む

必要なものと環境

  • 300円デバイス、Arduino Nano
  • ボタンスイッチ
  • Python3.5(3.6以上だと動作しない可能性あり?)
  • Windows 10(ここは何でも構いません)

ボタンとArduino

まず、ボタンを押したことをArduinoで検知する部分を作ります。押すと導通するスイッチとArduinoをはんだ付けでつなぎます。

Arduinoとスイッチ4つを配線

スイッチを押すとD2~D4が0Vに落ちます。

基盤にはんだ付け

Arduino側プログラム

スイッチの0V落ちを検知して、パソコンにデータを送るプログラムを作成します。

const int pin1 = 2;
const int pin2 = 3;
const int pin3 = 4;
const int pin4 = 5;

void setup() {
  Serial.begin(9600);
  pinMode(pin1,INPUT_PULLUP);
  pinMode(pin2,INPUT_PULLUP);
  pinMode(pin3,INPUT_PULLUP);
  pinMode(pin4,INPUT_PULLUP);
}

void loop() {
  int x1,x2,x3,x4;
  x1 = digitalRead(pin1);
  x2 = digitalRead(pin2);
  x3 = digitalRead(pin3);
  x4 = digitalRead(pin4);
  
  if(x1==0) Serial.println(1);
  if(x2==0) Serial.println(2);
  if(x3==0) Serial.println(3);
  if(x4==0) Serial.println(4);
  if(x1*x2*x3*x4!=0) Serial.println(0);
  
  delay(10);
}

スイッチ1~4が押されていれば(電圧が0Vならば)その数字を、すべて押されていなければ(x1~x4がすべて5Vならば)0を送信します。
スイッチが押されていない時(端子に何も接続されていない時)の電圧は、通常は不安定にならないように5Vまたは0Vに接続しなければいけませんがArduinoでは内部的にプルアップまたはプルダウンを選べるようです。

pinMode(pin1,INPUT_PULLUP);

このプログラムだとボタンを押している間ものすごい速さでデータが送信され続けます。一見無駄なようですが、データを受ける側のタイミングが悪いと上手に受け取れなかったりするので、受ける側で対処することにします。

パソコン側Pythonプログラム

最後に、Arduinoからデータを受け取って文字を打ち込むプログラムを作ります。シリアル通信が簡単に書けるPythonを使っていきます。

#coding utf-8
#シリアル通信で受け取ったデータによって
#定型文をエディタにペーストする
import serial
import time
import pyautogui as pa
import pyperclip

def put_text(t):
	tmp = pyperclip.paste()	#今のクリップボードのデータを一時保存しておく
	pyperclip.copy(t)	#テキストをクリップボードに読み込む
	pa.hotkey("ctrl","v")	#”Ctrl+V”を入力
	pyperclip.copy(tmp)	#保存しておいたデータをクリップボードに戻す
	return

def main():
	#通信ポートおよびボーレートの設定
	ser = serial.Serial('COM4',9600)

	while True:
		#データを一行受信
		x = ser.readline()

		#受け取ったデータによって処理を実行
		if (x==b'1\r\n'):
			put_text("お疲れ様です")
		if (x==b'2\r\n'):
			put_text("ありがとうございます")

		#0が送られてくるまで受信のみ
		while (x!=b'0\r\n'):
			x = ser.readline()
	ser.close()

if __name__=="__main__":
	main()

通信ポートの設定の部分はArduinoが接続しているポートに合わせて変えて下さい。(windowsなら「COM◯」、Linuxなら「/dev/ttyUSB0」等)。
pyautoguiやpyperclipなどのライブラリも適宜pipで入れて下さい。僕はpyautoguiがインストール出来ずに割と悩みましたがPython3.5を使うことで解決しました。この辺の原因は謎です。

pip install pyserial
pip install pyautogui
pip install pyperclip

 

エラー処理も何もしていないプログラムなので、参考程度に使用して下さい。

テストしてみる

ArduinoKeyboard.pyを実行しておきます。その状態でスイッチを押すと…

ちゃんと打ててる!

出来ました!

2017/12/26追記:

別の環境で動かそうとしてみたところ、serialパッケージが無いと言われる。pipでインストールしたけど…

pip install serial

エラーが…
serialじゃなくてpyserialらしい

pip uninstall serial
pip install pyserial

これで動いた

 

 

しりとリツイート

しりとリツイートって何?

数多のツイートから無作為にピックアップしてはじめの文字と終わりの文字でしりとりをするツイッターbotです。

現在の機能

  • 毎日午後10時に鍵なしツイートの中からピックアップ
  • ある程度探してしりとりが繋がらなかったらその回は何もしない
  • 「ば」→「は」のような感じで、割とゆるめなルール
  • 様々な文字種に対応(ひらがな、カタカナ、漢字)
  • 末尾のURL、「!」や「?」は無視
  • 名前やプロフィールに「bot」「Bot」「BOT」が入ってる場合は除外

コード紹介

twitterの認証とかはpythonがめちゃ便利なので、pythonで全部書いてしまいました。色々と関数化したら長くなってしまいましたが最後のmain関数が本体です。continueすると以降の処理は飛ばしてfor文の最初まで戻るらしいです。使いようによっては便利ですね。

#coding:utf-8

from requests_oauthlib import OAuth1Session
import json, urllib2
from datetime import datetime
import os
import sys
import time
import re
import TwitterKEYS #twitterのキーを保存しておくファイル
from subprocess import Popen, PIPE

#カタカナ→ひらがなに変換する関数
def henkan(text):
	hiragana=[unichr(i) for i in range(12353,12436)]
	katakana=[unichr(i) for i in range(12449,12532)]
	kana=""
	
	if u'ア' <= text <= u'ワ':
		for text in list(text):
			for i in range(83):
				if text == katakana[i]:
					kana+=hiragana[i]
		return kana
	else:
		return text

#小文字、濁音を変換する関数
def change(text):
	if len(text) == 1:
		n = ord(text)
		#ぁ〜ぉ→あ〜お
		if 0x3041 <= n <= 0x304A and n % 2 == 1:
			text = unichr( n + 1 )
		#が〜ぢ→か〜ち
		elif 0x304C <= n <= 0x3062 and n % 2 == 0:
			text = unichr( n - 1 )
		#っ、づ→つ
		elif 0x3063<= n <= 0x3065:
			text = unichr( 0x3064)
		#で〜ど→て〜と
		elif 0x3066 <= n <= 0x3069 and n % 2 == 1:
			text = unichr( n - 1 )
		#ば、ぱ〜ぼ、ぽ→は〜ほ
		elif 0x306F <= n <= 0x307D and n % 3 != 0:
			text = unichr( n - (n%3) )
		#ゃ、ゅ、ょ→や、ゆ、よ
		elif 0x3083 <= n <= 0x3088 and n % 2 == 1:
			text = unichr( n + 1 )
	else:
		print "change error."
	return text

#漢字の読み仮名をとってくる関数→「mecab」で実現
def get_yomigana(text):
	p1 = Popen(['echo', text], stdout = PIPE)
	p2 = Popen(['mecab', '-Oyomi'], stdin = p1.stdout, stdout = PIPE)
	p1.stdout.close()
	output = p2.communicate()[0]
	return re.sub(r'\n', '', output)

#末尾に英数字があったら削除する関数
def skip(text):
	skip_list = u"/: .abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#」…。、??!!〜))ー"
	while skip_list.find(text[-1:])>=0:
		text = text[:-1]
	return text

#テキストをツイートする関数
def tweet(text):
	#ツイート投稿URL
	URL_tweet = 'https://api.twitter.com/1.1/statuses/update.json'
	
	twitter = OAuth1Session(TwitterKEYS.CK,TwitterKEYS.CS,TwitterKEYS.AT,TwitterKEYS.AS)
	
	params = {"status": text}
	req = twitter.post(URL_tweet,params = params)

#テキストの最初の文字をひらがなで返す
def check_first_letter(text):
	#最初の文字をひらがな、カタカナ、先頭4文字の読み仮名の順にチェック
	#最初の文字をひらがなで出す
	first_letter = text[0:1]
	if u'あ' <= first_letter <= u'わ':
		print first_letter.encode('utf-8')
	else:
		first_letter = henkan(first_letter)
		if u'あ' <= first_letter <= u'わ':
			print first_letter.encode('utf-8')
		else:
			yomi = get_yomigana(text[0:4]).decode('utf-8')
			first_letter = henkan(yomi[0:1])
			if u'あ' <= first_letter<= u'わ':
				print first_letter.encode('utf-8')
			else:
				print "cannot define first letter."
				first_letter = u'ん'
	return first_letter

#テキストの最後の文字をひらがなで返す
def check_last_letter(text):
	last_letter = text[-1:]
	last_letter = henkan(last_letter)
	last_letter = change(last_letter)
	return last_letter

#ここからメイン関数
def main():
	
	#はじめの文字をファイルから読み出す
	logfile_path = "/home/ppdr/chain.txt"
	f = open(logfile_path,"r")
	start = f.read().decode('utf-8')[0:1]
	f.close()
	
	print start.encode('utf-8')
	
	#はじめの文字で検索
	req_count = 100	#取得するツイート数
	params = {"q" : start + ' -rt','lang':'ja','count':req_count}	#検索時のパラメータ?
	twitter = OAuth1Session(TwitterKEYS.CK,TwitterKEYS.CS,TwitterKEYS.AT,TwitterKEYS.AS)	#twitteキーを使って認証
	req = twitter.get("https://api.twitter.com/1.1/search/tweets.json",params = params)	#検索結果をjsonで取得
	
	if req.status_code == 200:
		js = json.loads(req.text)
		for i in range(0, req_count):
			txt = js['statuses'][i]['text']
			
			#最初の文字が一致しない場合、除外
			if not(check_first_letter(txt) == start):
				continue
			
			#末尾の英数字を削除
			txt = skip(txt)
			print "skipped:    "+txt.encode('utf-8')
			
			#最後の文字がつぎにつながらない場合、除外
			last_letter = check_last_letter(txt)
			if not(u'あ' <= last_letter <= u'わ'):
				continue
			
			#最後の文字が前回と同じ場合、除外
			if (last_letter == start):
				continue
			
			#ユーザーネーム、プロフィール、スクリーンネームのいずれかに「bot」が入っていたら除外
			bot_check_text = js['statuses'][i]['user']['name']+js['statuses'][i]['user']['description']+js['statuses'][i]['user']['screen_name']
			if ('bot' in bot_check_text) or ('Bot' in bot_check_text) or ('BOT' in bot_check_text):
				continue
			
			#条件確定
			#リツイート
			txt_id = js['statuses'][i]['id']
			req = twitter.post('https://api.twitter.com/1.1/statuses/retweet/'+str(txt_id)+'.json')
			
			if req.status_code == 200:
				print "post success!"
				
				#リツイートが成功したらつぎの文字をファイルに書き出す
				f = open(logfile_path,"w")
				f.write(last_letter.encode('utf-8'))
				f.close()
				
				#つぎの文字をツイート
				txt = u"リツイートしりとり!\n\nつぎの文字は・・・\n『" + txt[-1:] + u"』!!"
				time.sleep(1)
				tweet(txt)
				break
			else:
				print "post filure"
				break
	
	else:
		print "error!"
		print req.status_code
	
if __name__ == "__main__":
	main()