diff --git a/README.md b/README.md index 077a462..434c1dc 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,141 @@ ----== Scripta ==--- -The turnkey solution for litecoin mining with raspberry pi and fpga/asic boards +Inital information compiled from lots of good stuff around Scripta at litecointalk +[fourm posts](https://litecointalk.org/index.php?topic=9908.msg143787#msg143787) + +1. Started with Scripta 1.1 [image](http://www.lateralfactory.com/download.php?file=scripta-1_1.tgz) + * source files in image do not match current Scripta [repo](https://github.com/scriptamining/scripta.git) + * rsync github files to image to get starting code base (all uses of "minepeon" replaced with "scripta") + * origianl MinePeon [project](http://minepeon.com/index.php/Main_Page) has lots of good PI setup and helpful info + +2. Update raspberry to newest kernel (3.10.33+) and added 'slub_debug=FP' to bootline.conf + * this should fix the wierd USB debug error logging issues. + +3. Set ssh port to 22. Should probably turn off root ssh access and change password. + ssh root@10.0.1.28 + root password: scripta + web password: scripta + +4. Change locales to en_US + +5. Add wifi support (wlan0) + * set ssid and psk in `network` block at `/etc/wpa_supplicant/wpa_supplicant.conf` + +6. Build GridSeed GS3355 specific version of cgminer with mulit-frequency support from [repo](https://github.com/girnyau/cgminer-gc3355) + * modified cgminer-gc3355 to report Frequency and Serial in API calls: [repo](https://github.com/mox235/cgminer-gc3355) + +7. Edit `/opt/scripta/startup/miner-start.sh` to use cgminer-gc3355 + +8. Remove Scripta LTC donation address. Disable MinePeon BTC hash time donation option. Need to determine the right thing to do since _most_ of the web interface stuff is directly from the [MinePeonUI project](https://github.com/MineForeman/zArchive-MinePeonWebUI.git) + * other Scripta rebranded PI images at [Crypto Pros](http://www.cryptopros.com/2014/03/gridseed-dual-miner-first-look-amazing.html) + * and [Hash Master](https://hash-master.com/blog/using-your-raspberry-pi-as-a-gridseed-mining-controller/) + * different PI web-based controller for Gridseed at [Hashra](https://github.com/HASHRA) + +9. Fix pool URL JSON encoding. Add back miner config name/values settings from MinePeonUI. All cgminer settings can be changed or added from miner form. + * priority used to reorder pool list + * ability to set per GSD frequency based on Serial # from freq list: + ``` + static const int opt_frequency[] = { + 700, 706, 713, 719, 725, 731, 738, 744, + 750, 756, 763, 769, 775, 781, 788, 794, + 800, 813, 825, 838, 850, 863, 875, 888, + 900, 913, 925, 938, 950, 963, 975, 988, + 1000, 1013, 1025, 1038, 1050, 1063, 1075, 1088, + 1100, 1113, 1125, 1138, 1150, 1163, 1175, 1188, + 1200, 1213, 1225, 1238, 1250, 1263, 1275, 1288, + 1300, 1313, 1325, 1338, 1350, 1363, 1375, 1388, + 1400, + -1 + }; + ``` +10. Modify Status table + * show KHs instead of MHs + * replace Name with GSD Serial Number + * replace Temperature with Frequency + * remove percentages for DiffAccept and DiffReject + +11. Open Issues + * maybe reported [hashrate](http://cryptomining-blog.com/1760-what-is-the-actual-hashrate-you-get-from-your-gridseed-asic/) is not quite accurate + * something weird with system time display, timezone, day-light savings + * graphs should auto-refresh when /status page loads + +mega link [scripta-20140330.img](https://mega.co.nz/#!D5RiSZTR!wcDqC3yOeUrYC6tqYM7Lh5YbRjVpdtQhg29CagL4ZsI) + +--- + +scripta-20140401 + + * updated PI firmware to maybe fix usb slub crash + * fix cgminer to always start at boot `/etc/rc.local` + * gzip image + +mega link [scripta-20140401.img.gz](https://mega.co.nz/#!Tx42mJab!XMpNsU6cfS23GAuli3C_BgwrdJ15sFLqEF7QNgrYTN4) + +--- + +scripta-20140408 + + * install [watchdog](http://linux.die.net/man/8/watchdog) service + * load `bcm2708_wdog` kernel module + * modify `/opt/watchdog.conf` to reboot PI if bad things happen. See below. + * add UI option for cgminer specific watchdog script `/opt/scripta/bin/wdog.py`. See below. + * add/fix UI option for manual reboot + * option for email notification on PI automatic reboot. *YOUR EMAIL PASSWORD IS STORED IN PLAIN TEXT SO PLAN ACCORDINGLY* + +Once the watchdog is enabled, it is possible to get in a reboot loop. If this happens, disable the system watchdog by quick edit of `/etc/watchdog.conf` after boot and comment out `watchdog-device = /dev/watchdog`. The cgminer API based watchdog script can be disabled by using the UI checkbox or commenting out `test-binary = /opt/scripta/bin/wdog.py` in the same config file. You will have about 30 seconds each loop to try and unscrew things. + +The system watchdog daemon will check the following every 30 seconds: + - sytem load less than 24/18/12 + - more than 1 page of RAM available + - syslog still alive + - cgminer specific watchdog script `/opt/scripta/bin/wdog.py` that uses the following logic: + + ``` + if (UI manual reboot) + { + force PI reboot + } + if (UI auto reboot) + { + if (sytem up for more than 60 seconds) + { + if (cgminer process not running) + { + if (UI enable) send email + force PI reboot + } + if (ASIC device count less UI device count) + { + if (UI enable) send email + force PI reboot + } + for (each ASIC device) + { + if (device hashrate less than UI value) + { + if (UI enable) send email + force PI reboot + } + } + } + } + OK (yay!!!) + ``` + - watchdog details logged to syslog + +mega link [scripta-20140408.img.gz](https://mega.co.nz/#!ah4XkCpL!A-b_10rNj1GvfQN36waTzxCRCHB_8UltIA4pFgaXIkw) + +--- + +scripta-20140423 + + * Install wicd and wicd-curses to help with WiFi network configuration. Use `wicd-curses` to setup your WiFi. + * Fixed double emails from "cgminer process not running" reboot. + * Update openssl (heartbleed) + * Upgrade/downgrade rpi firmware to use 3.10.36+ kernel. Maybe more stable USB than 20140408 version using "next" branch (??) + * Replace miner with bfgminer for stability. Update browser options to display status. No longer able to set/display individual GSD clock frequencies. + +Miner bfgminer 3.99.0 seems to be more stable than wierd cgminer 3.7.2 branch. Ongoing gridseed support scheduled to be merged into main bfgminer in next (4.0.0) main version release. + +mega link [scripta-20140423.img.gz](https://mega.co.nz/#!PwB0jIzK!ltpfKSBv_IOc1gsyPog3eotnprKFOBoOoEkgiPN3-GY) ----=== INSTALL INTRUCTIONS ===--- - - - ----=== The easy way ===--- - -1) Download the full image here http://www.lateralfactory.com/download.php?file=scripta-1_1.tgz - -2) Burn it on a ssd in your favourite way - -3) Log in as root from a console (pw is "scripta") - -4) Remember to change root password with passwd - -5) Enjoy - - - ----=== The way of the turtle ===--- - -Start from a fresh raspbian wheezy (tested with 2014-01-07) Download here http://downloads.raspberrypi.org/raspbian_latest - -$>raspi-config ( if needed "Expand Filesystem" and reboot ) - -$>sudo apt-get update - -$>sudo apt-get install lighttpd - -$>sudo apt-get install php5-common php5-cgi php5 (Pay attention to packet's order) - -$>sudo lighty-enable-mod fastcgi-php - -$>sudo /etc/init.d/lighttpd force-reload - ----= Add pi user to www-data group =--- - -$>sudousermod -a -G www-data pi - -$>sudo apt-get install php5-rrd libexpect-php5 php-auth-sasl php-mail php-net-smtp php-net-socket - - ----= Needed to enable https =--- - -$>sudo mkdir /etc/lighttpd/certs - -$>sudo su - -$>cd /etc/lighttpd/certs - -$>openssl req -new -x509 -keyout lighttpd.pem -out lighttpd.pem -days 365 -nodes - -$>chmod 400 lighttpd.pem - -$>/etc/init.d/lighttpd force-reload - - ----= edit /etc/lighttpd/lighttpd.conf =--- - -$>pico /etc/lighttpd/lighttpd.conf - ----= add the following lines at the end =--- - -$SERVER["socket"] == ":443" { - ssl.engine = "enable" - ssl.pemfile = "/etc/lighttpd/certs/lighttpd.pem" -} - ----= libs for cgminer =--- - -$>sudo apt-get install libjansson4 libusb-1.0-0 ntpdate screen - ----= install scripta package =--- - -$>cd / - -$>tar -xf scripta_1-1.tgz - ----= point your browser on raspberry ip address, enjoy! =--- diff --git a/etc/rc.local b/etc/rc.local index c880c94..aa43ef1 100644 --- a/etc/rc.local +++ b/etc/rc.local @@ -11,10 +11,11 @@ # # By default this script does nothing. +/opt/scripta/startup/miner-start.sh +sleep 10 + # Print the IP address _IP=$(hostname -I) || true -if [ "$_IP" ]; then - echo " " echo " _________ .__ __ " echo " / _____/ ___________|__|______/ |______ " @@ -22,12 +23,8 @@ echo " \_____ \_/ ___\_ __ \ \____ \ __\__ \ " echo " / \ \___| | \/ | |_> > | / __ \_ " echo "/_______ /\___ >__| |__| __/|__| (____ / " echo " \/ \/ |__| \/ " - printf "Connect to http://%s to manage Scripta.\n" "$_IP" printf "Happy mining!\n" -sudo /opt/scripta/startup/miner-start.sh & - -fi - exit 0 + diff --git a/opt/scripta/bin/cgminer-gc3355 b/opt/scripta/bin/cgminer-gc3355 new file mode 100755 index 0000000..022851d Binary files /dev/null and b/opt/scripta/bin/cgminer-gc3355 differ diff --git a/opt/scripta/bin/cgminer-monitor.py b/opt/scripta/bin/cgminer-monitor.py new file mode 100644 index 0000000..2029a66 --- /dev/null +++ b/opt/scripta/bin/cgminer-monitor.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# Romain Dura | romain@shazbits.com +# https://github.com/shazbits/cgminer-monitor +# BTC 1Kcn1Hs76pbnBpBQHwdsDmr3CZYcoCAwjj +# + +import socket +import sys +import time +import smtplib +import json +import os +import threading +import SimpleHTTPServer +import SocketServer +import urllib2 + + +# +# Config +# + +cgminer_host = 'localhost' +cgminer_port = 4028 +email_smtp_server = 'smtp.gmail.com:587' +email_login = 'mylogin' +email_password = 'mypassword' +email_from = 'myemail@example.com' +email_to = 'myemail@example.com' +email_subject = 'Miner warning detected' +monitor_interval = 15 +monitor_wait_after_email = 60 +monitor_http_interface = '0.0.0.0' +monitor_http_port = 84 +monitor_restart_cgminer_if_sick = True +monitor_send_email_alerts = True +monitor_max_temperature = 85 +monitor_min_mhs_scrypt = 0.5 +monitor_min_mhs_sha256 = 500 +monitor_enable_pools = False + +# MMCFE pools (www.wemineltc.com, dgc.mining-foreman.org, megacoin.miningpool.co, etc.) +# Replace the URLs and/or API keys by your own, add as many pools as you like +pools = [ + { + 'url': 'http://www.digicoinpool.com/api?api_key=1234567890', + 'cur': 'DGC' + }, + { + 'url': 'http://www.wemineltc.com/api?api_key=1234567890', + 'cur': 'LTC' + }, +] + + +# +# Shared between monitor and http server +# + +shared_output = '' +shared_output_lock = threading.Lock() + + +# +# cgminer RPC +# + +class CgminerClient: + def __init__(self, host, port): + self.host = host + self.port = port + + def command(self, command, parameter): + # sockets are one time use. open one for each command + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + sock.connect((self.host, self.port)) + if parameter: + self._send(sock, json.dumps({"command": command, "parameter": parameter})) + else: + self._send(sock, json.dumps({"command": command})) + received = self._receive(sock) + except Exception as e: + print e + sock.close() + return None + + sock.shutdown(socket.SHUT_RDWR) + sock.close() + + # the null byte makes json decoding unhappy + try: + decoded = json.loads(received.replace('\x00', '')) + return decoded + except: + pass # restart makes it fail, but it's ok + + def _send(self, sock, msg): + totalsent = 0 + while totalsent < len(msg): + sent = sock.send(msg[totalsent:]) + if sent == 0: + raise RuntimeError("socket connection broken") + totalsent = totalsent + sent + + def _receive(self, sock, size=65500): + msg = '' + while True: + chunk = sock.recv(size) + if chunk == '': + # end of message + break + msg = msg + chunk + return msg + + +# +# Utils +# + +def SendEmail(from_addr, to_addr_list, cc_addr_list, + subject, message, login, password, + smtpserver=email_smtp_server): + header = 'From: %s\n' % from_addr + header += 'To: %s\n' % ','.join(to_addr_list) + header += 'Cc: %s\n' % ','.join(cc_addr_list) + header += 'Subject: %s\n\n' % subject + + server = smtplib.SMTP(smtpserver) + server.starttls() + server.login(login, password) + server.sendmail(from_addr, to_addr_list, header + message) + server.quit() + + +# +# Monitor +# + +def StartMonitor(client): + os.system('cls') + while (True): + output = '' + + must_send_email = False + must_restart = False + + result = client.command('coin', None) + coin = '' + if result: + coin = result['COIN'][0]['Hash Method'] + output = 'Coin : %s\n' % coin + + result = client.command('pools', None) + if result: + output += 'Pool URL : %s\n' % (result['POOLS'][0]['Stratum URL']) + warning = ' <----- /!\\' if result['POOLS'][0]['Status'] != 'Alive' else '' + must_send_email = True if warning != '' else must_send_email + output += 'Pool : %s%s\n' % (result['POOLS'][0]['Status'], warning) + + # Put this in a loop for multi-gpu support + result = client.command('gpu', '0') + if result: + gpu_result = result['GPU'][0] + warning = ' <----- /!\\' if gpu_result['Status'] != 'Alive' else '' + must_restart = True if warning != '' else False + must_send_email = True if warning != '' else must_send_email + output += 'GPU 0 : %s%s\n' % (gpu_result['Status'], warning) + + min_mhs = monitor_min_mhs_scrypt if coin == 'scrypt' else monitor_min_mhs_sha256 + warning = ' <----- /!\\' if gpu_result['MHS 1s'] < min_mhs else '' + must_send_email = True if warning != '' else must_send_email + output += 'MHS 1s/av: %s/%s%s\n' % (gpu_result['MHS 1s'], gpu_result['MHS av'], warning) + + warning = ' <----- /!\\' if gpu_result['Temperature'] > monitor_max_temperature else '' + must_send_email = True if warning != '' else must_send_email + output += 'Temp : %s%s\n' % (gpu_result['Temperature'], warning) + output += 'Intensity: %s\n' % gpu_result['Intensity'] + + result = client.command('summary', None) + if result: + if result['SUMMARY'][0]['Hardware Errors'] > 0: + must_send_email = True + output += 'HW err : %s%s\n' % (result['SUMMARY'][0]['Hardware Errors'], ' <----- /!\\') + + result = client.command('stats', None) + if result: + uptime = result['STATS'][0]['Elapsed'] + output += 'Uptime : %02d:%02d:%02d\n' % (uptime / 3600, (uptime / 60) % 60, uptime % 60) + print output + + global shared_output + global shared_output_lock + shared_output_lock.acquire() + shared_output = output + shared_output_lock.release() + + if must_restart and monitor_restart_cgminer_if_sick: + print 'Restarting' + result = client.command('restart', None) + + if must_send_email and monitor_send_email_alerts and uptime > 10: + SendEmail(from_addr=email_from, to_addr_list=[email_to], cc_addr_list=[], + subject=email_subject, + message=output, + login=email_login, + password=email_password) + time.sleep(monitor_wait_after_email) + + # Sleep by increments of 1 second to catch the keyboard interrupt + for i in range(monitor_interval): + time.sleep(1) + + os.system('cls') + + +# +# HTTP server request handler +# + +class CGMinerRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + + if self.path == '/favicon.ico': + return + + self.send_header("Content-type", "text/html") + self.end_headers() + + global shared_output + global shared_output_lock + shared_output_lock.acquire() + html_output = shared_output[:-1] # one too many \n + shared_output_lock.release() + + # Get balance from pools + pools_output = '' + if monitor_enable_pools: + td_div = '' + for pool in pools: + try: + response = urllib2.urlopen(pool['url']) + data = json.load(response) + pools_output += '\n\n' + td_div + '\n' + 'pool' + '' + td_div + pool['cur'] + ' %.6f' % (float(data['confirmed_rewards'])) + except urllib2.HTTPError as e: + pools_output += '\n\n' + td_div + '\n' + 'pool' + '' + td_div + pool['cur'] + ' Error: ' + str(e.code) + except urllib2.URLError as e: + pools_output += '\n\n' + td_div + '\n' + 'pool' + '' + td_div + pool['cur'] + ' Error: ' + e.reason + except: + pools_output += '\n\n' + td_div + '\n' + 'pool' + '' + td_div + pool['cur'] + ' Error: unsupported pool?' + + # Format results from the monitor + td_div = '' + html_output = ('\n\n' + td_div + '\n').join(html_output.replace(': ', '' + td_div).split('\n')) + html_output += pools_output + html = """ + + + + cgminer monitor + + + + +
+ """ + html_output + """ +
+ + + """ + self.wfile.write(html) + + +# +# usage: cgminer-monitor.py [command] [parameter] +# No arguments: monitor + http server mode. Press CTRL+C to stop. +# Arguments: send the command with optional parameter and exit. +# + +if __name__ == "__main__": + command = sys.argv[1] if len(sys.argv) > 1 else None + parameter = sys.argv[2] if len(sys.argv) > 2 else None + + client = CgminerClient(cgminer_host, cgminer_port) + + if command: + # An argument was specified, ask cgminer and exit + result = client.command(command, parameter) + print result if result else 'Cannot get valid response from cgminer' + else: + # No argument, start the monitor and the http server + try: + server = SocketServer.TCPServer((monitor_http_interface, monitor_http_port), CGMinerRequestHandler) + threading.Thread(target=server.serve_forever).start() + StartMonitor(client) + except KeyboardInterrupt: + server.shutdown() + diff --git a/opt/scripta/bin/wdog.py b/opt/scripta/bin/wdog.py new file mode 100644 index 0000000..9650eb6 --- /dev/null +++ b/opt/scripta/bin/wdog.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# +# Scripta watchdog test for cgminer status +# +# parts used from cgminer-monitor.py +# Romain Dura | romain@shazbits.com +# https://github.com/shazbits/cgminer-monitor +# BTC 1Kcn1Hs76pbnBpBQHwdsDmr3CZYcoCAwjj +# + +import socket +import sys +import time +import smtplib +import json +import os +import threading +import SimpleHTTPServer +import SocketServer +import urllib2 +import datetime, time +import pprint + +# +# Config +# + +cgminer_host = 'localhost' +cgminer_port = 4028 +monitor_interval = 15 +monitor_wait_after_email = 10 + +shared_output = '' +shared_output_lock = threading.Lock() + +# +# cgminer RPC +# + +class CgminerClient: + def __init__(self, host, port): + self.host = host + self.port = port + + def command(self, command, parameter): + # sockets are one time use. open one for each command + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + sock.connect((self.host, self.port)) + if parameter: + self._send(sock, json.dumps({"command": command, "parameter": parameter})) + else: + self._send(sock, json.dumps({"command": command})) + received = self._receive(sock) + except Exception as e: + print e + sock.close() + return None + + sock.shutdown(socket.SHUT_RDWR) + sock.close() + + # the null byte makes json decoding unhappy + try: + decoded = json.loads(received.replace('\x00', '')) + return decoded + except: + pass # restart makes it fail, but it's ok + + def _send(self, sock, msg): + totalsent = 0 + while totalsent < len(msg): + sent = sock.send(msg[totalsent:]) + if sent == 0: + raise RuntimeError("socket connection broken") + totalsent = totalsent + sent + + def _receive(self, sock, size=65500): + msg = '' + while True: + chunk = sock.recv(size) + if chunk == '': + # end of message + break + msg = msg + chunk + return msg + + +# +# Utils +# + +def SendEmail(from_addr, to_addr_list, cc_addr_list, + subject, message, login, password, + smtpserver): + header = 'From: %s\n' % from_addr + header += 'To: %s\n' % ','.join(to_addr_list) + header += 'Cc: %s\n' % ','.join(cc_addr_list) + header += 'Subject: %s\n\n' % subject + + server = smtplib.SMTP(smtpserver) + server.starttls() + server.login(login, password) + server.sendmail(from_addr, to_addr_list, header + message) + server.quit() + print 'send email: ' + to_addr_list[0] + + +if __name__ == "__main__": + + now = (datetime.datetime.now()-datetime.datetime(1970,1,1)).total_seconds() + + if os.path.exists('/tmp/wdog.ts'): + ts_file=open('/tmp/wdog.ts','r') + dt = now - float(ts_file.readline()) + ts_file.close(); + else: + ts_file=open('/tmp/wdog.ts','w') + ts_file.write(str(now)); + ts_file.close; + dt = 0.0; + + if os.path.exists('/tmp/reboot.ts'): + print 'OK - wait for reboot command' + sys.exit(0) + + conf_file=open('/opt/scripta/etc/scripta.conf') + conf = json.load(conf_file) + pprint.pprint(conf) + conf_file.close() + + if conf['rebootEnable']: + conf['rebootEnable'] = False + with open('/opt/scripta/etc/scripta.conf', 'w') as outfile: + json.dump(conf, outfile) + print 'OK - reboot command' + sys.exit(1) + + if not conf['recoverEnable']: + print 'OK - scripta watchdog disabled' + sys.exit(0) + + if dt < 60.0: + print 'OK - wait for cgminer startup ' + str(dt) + ' sec' + sys.exit(0) + + print 'expected device count: ' + str(conf['miningExpDev']) + print 'expected min hashrate: ' + str(conf['miningExpHash']) + + pid_file=open('/var/run/bfgminer.pid','r') + if not pid_file: + print 'ERROR - bfgminer.pid not found' + else: + pid = int(pid_file.readline()) + pid_file.close() + ps = '/proc/' + str(pid) + '/' + if os.path.exists(ps): + print 'OK - bfgminer process running' + else: + output = 'ERROR - bfgminer process not running (pid ' + ps + ')' + time.sleep(3) + if not os.path.exists(ps): + output = 'ERROR - bfgminer process not running (pid ' + ps + ')' + if conf['alertEnable']: + SendEmail( + from_addr='scripta@hotmail.com', + to_addr_list=[conf['alertEmailTo']], + cc_addr_list=[], + subject='Scripta Reboot [' + conf['alertDevice'] + ']', + message=output, + login=conf['alertSmtpUser'], + password=conf['alertSmtpPwd'], + smtpserver=conf['alertSmtp'] + ':' + str(conf['alertSmtpPort'])) + time.sleep(monitor_wait_after_email) + ts_file=open('/tmp/reboot.ts','w') + ts_file.write(str(now)); + ts_file.close; + sys.exit(1) # reboot + + client = CgminerClient(cgminer_host, cgminer_port) + + result = client.command('pgacount', None) + if result: + dev = result['PGAS'][0]['Count'] + if conf['miningExpDev'] > dev: + output = 'ERROR - device count: ' + str(dev) + '\n\n' + pprint.pformat(result) + print output + if conf['alertEnable']: + SendEmail( + from_addr='scripta@hotmail.com', + to_addr_list=[conf['alertEmailTo']], + cc_addr_list=[], + subject='Scripta Reboot [' + conf['alertDevice'] + ']', + message=output, + login=conf['alertSmtpUser'], + password=conf['alertSmtpPwd'], + smtpserver=conf['alertSmtp'] + ':' + str(conf['alertSmtpPort'])) + time.sleep(monitor_wait_after_email) + ts_file=open('/tmp/reboot.ts','w') + ts_file.write(str(now)); + ts_file.close; + sys.exit(1) # reboot + else: + print 'OK - device count: ' + str(dev) + else: + print 'OK - cgminer not ready' + sys.exit(0); + + result = client.command('devs', None) + if result: + for d in result['DEVS']: + if conf['miningExpHash'] > d['MHS av']: + output = 'ERROR - ' + str(d['Name']) + str(d['ID']) + ' hashrate: ' + str(d['MHS av']) + '\n\n' + pprint.pformat(result) + print output + if conf['alertEnable']: + SendEmail( + from_addr='scripta@hotmail.com', + to_addr_list=[conf['alertEmailTo']], + cc_addr_list=[], + subject='Scripta Reboot [' + conf['alertDevice'] + ']', + message=output, + login=conf['alertSmtpUser'], + password=conf['alertSmtpPwd'], + smtpserver=conf['alertSmtp'] + ':' + str(conf['alertSmtpPort'])) + time.sleep(monitor_wait_after_email) + ts_file=open('/tmp/reboot.ts','w') + ts_file.write(str(now)); + ts_file.close; + sys.exit(1) # reboot + else: + print 'OK - ' + str(d['ID']) + ' hashrate: ' + str(d['MHS av']) + + sys.exit(0) \ No newline at end of file diff --git a/opt/scripta/etc/miner.conf b/opt/scripta/etc/miner.conf index e0e6b65..db016c2 100644 --- a/opt/scripta/etc/miner.conf +++ b/opt/scripta/etc/miner.conf @@ -1,19 +1,42 @@ { "api-listen": true, "api-port": "4028", + "api-allow": "W:127.0.0.1,192.168.100.0/24", "expiry": "120", "hotplug": "5", "log": "5", "no-pool-disable": true, - "queue": "1", + "no-show-processors": true, + "no-show-procs": true, + "no-submit-stale": true, + "queue": "90", "scan-time": "30", "scrypt": true, "shares": "0", + "scan": [ + "gridseed:all" + ], + "set-device": [ + "gridseed:clock=825" + ], "pools": [ { - "url": "stratum tcp:\/\/global.wemineltc.com:3335", - "user": "drfranz.gs", - "pass": "gs" + "url": "stratum+tcp://us.ltcrabbit.com:3334", + "user": "mox235.0", + "pass": "0", + "prio": "0" + }, + { + "url": "stratum+tcp://stratum.scryptguild.com:3333", + "user": "mox235_0", + "pass": "0", + "prio": "1" + }, + { + "url": "stratum+tcp://usa.wemineltc.com:3336", + "user": "mox235.0", + "pass": "0", + "prio": "2" } ] -} \ No newline at end of file +} diff --git a/opt/scripta/etc/miner.options.json b/opt/scripta/etc/miner.options.json index a49f1bb..8bd199d 100644 --- a/opt/scripta/etc/miner.options.json +++ b/opt/scripta/etc/miner.options.json @@ -8,39 +8,65 @@ "value": "4028" }, { - "key": "expiry", - "value": "120" - }, - { - "key": "hotplug", - "value": "5" - }, - { - "key": "log", - "value": "5" - }, - { - "key": "no-pool-disable", - "value": true - }, - { - "key": "queue", - "value": "1" - }, - { - "key": "scan-time", - "value": "30" - }, - { - "key": "scan-time", - "value": "30" - }, -{ - "key": "scrypt", - "value": true - }, -{ - "key": "shares", - "value": "0" - } + "key": "api-allow", + "value": "W:127.0.0.1,192.168.100.0/24" + }, + { + "key": "expiry", + "value": "120" + }, + { + "key": "hotplug", + "value": "5" + }, + { + "key": "log", + "value": "5" + }, + { + "key": "no-pool-disable", + "value": true + }, + { + "key": "no-show-processors", + "value": true + }, + { + "key": "no-show-procs", + "value": true + }, + { + "key": "no-submit-stale", + "value": true + }, + { + "key": "queue", + "value": "90" + }, + { + "key": "scan-time", + "value": "30" + }, + { + "key": "scrypt", + "value": true + }, + { + "key": "shares", + "value": "0" + }, + { + "key": "scan", + "value": + [ + "gridseed:all" + ] + }, + { + "key": "set-device", + "value": + [ + "gridseed:clock=825" + ] + } ] diff --git a/opt/scripta/etc/miner.pools.json b/opt/scripta/etc/miner.pools.json index 946e384..62e9035 100644 --- a/opt/scripta/etc/miner.pools.json +++ b/opt/scripta/etc/miner.pools.json @@ -1,7 +1,18 @@ [ { - "url": "stratum tcp:\/\/global.wemineltc.com:3335", - "user": "drfranz.gs", - "pass": "gs" + "url": "stratum tcp:\/\/usa.wemineltc.com:3336", + "user": "mox235.1", + "pass": "\"1\"" + }, + { + "url": "stratum tcp:\/\/united.wemineltc.com:3335", + "user": "mox235.1", + "pass": "\"1\"" + }, + { + "url": "stratum tcp:\/\/stratum.scryptguild.com:3333", + "user": "mox235_gridseed", + "pass": "\"1\"" } -] \ No newline at end of file +] + diff --git a/opt/scripta/http/rrd/rrd b/opt/scripta/http/rrd/rrd deleted file mode 100644 index 7a90797..0000000 --- a/opt/scripta/http/rrd/rrd +++ /dev/null @@ -1 +0,0 @@ -rrd \ No newline at end of file diff --git a/opt/scripta/startup/.miner-start.sh.swp b/opt/scripta/startup/.miner-start.sh.swp deleted file mode 100644 index a173e3b..0000000 Binary files a/opt/scripta/startup/.miner-start.sh.swp and /dev/null differ diff --git a/opt/scripta/startup/miner-start.sh b/opt/scripta/startup/miner-start.sh index bc53286..a9977fa 100644 --- a/opt/scripta/startup/miner-start.sh +++ b/opt/scripta/startup/miner-start.sh @@ -1,8 +1,5 @@ #!/bin/bash -sudo /usr/sbin/ntpdate -u pool.ntp.org -sudo /usr/bin/screen -dmS cgminer /opt/scripta/bin/cgminer -c /opt/scripta/etc/miner.conf - -#sudo /opt/scripta/bin/cgminer -c /opt/scripta/etc/miner.conf >> /dev/null & -#echo $! > /opt/scripta/var/cgminer.lock - - +/usr/sbin/ntpdate -u pool.ntp.org +/usr/bin/screen -dmS cgminer /opt/scripta/bin/cgminer-gc3355 -c /opt/scripta/etc/miner.conf +sleep 1 +echo `pidof bfgminer` > /opt/scripta/var/bfgminer.pid \ No newline at end of file diff --git a/var/www/f_settings.php b/var/www/f_settings.php index d942aee..258c9cd 100644 --- a/var/www/f_settings.php +++ b/var/www/f_settings.php @@ -6,6 +6,8 @@ die(); } +include('inc/ChromePhp.php'); + /* f_settings syncs settings in different files and always returns new state returns settings and ['date'] @@ -38,8 +40,9 @@ foreach ($newdata as $key => $value) { $r['data'][$key]=$value; } - file_put_contents($configScripta, json_encode($r['data'], JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK)); + file_put_contents($configScripta, json_encode($r['data'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); $r['info'][]=array('type' => 'success', 'text' => 'Configuration saved'); + if ($r['data']['rebootEnable']) $r['info'][]=array('type' => 'success', 'text' => 'Reboot Now'); } // Load current settings else{ @@ -56,10 +59,32 @@ elseif (!empty($_REQUEST['pools'])) { $newdata = json_decode($_REQUEST['pools'], true); $r['data'] = json_decode(@file_get_contents($configPools), true); + + foreach ($r['data'] as $id => $p) + { + $r['data'][$id]['url'] = str_replace('stratum tcp','stratum+tcp',$p['url']); + } + $m=0; + foreach ($newdata as $id => $p) + { + $newdata[$id]['url'] = str_replace('stratum tcp','stratum+tcp',$p['url']); + if($p['prio'] > $m) $m=$p['prio']; + } + + // reorder pool list based on priority + if($m>0){ + $pl=array(); + for ($pp = 0; $pp <= $m; $pp++) { + foreach ($newdata as $id => $p){ + if($p['prio'] == $pp) $pl[]=$newdata[$id]; + } + } + $newdata = $pl; + } // Overwrite current with new pools if(!empty($newdata)&&is_array($newdata)){ - file_put_contents($configPools, json_encode($newdata, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK)); + file_put_contents($configPools, json_encode($newdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); minerConfigGenerate(); $r['data']=$newdata; $r['info'][]=array('type' => 'success', 'text' => 'Pools config saved'); @@ -82,7 +107,7 @@ // Overwrite current with new config if(!empty($newdata)&&is_array($newdata)){ - file_put_contents($configOptns, json_encode($newdata, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK)); + file_put_contents($configOptns, json_encode($newdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); minerConfigGenerate(); $r['data']=$newdata; $r['info'][]=array('type' => 'success', 'text' => 'Miner options saved'); @@ -117,10 +142,15 @@ function minerConfigGenerate(){ // Angular objects ==> miner // {key:k,value:v} ==> {k:v} foreach ($options as $o) { - $miner[$o['key']]=$o['value']; + if ($o['key']=='scan' || $o['key']=='set-device'){ + $miner[$o['key']]=array($o['value']); + } + else{ + $miner[$o['key']]=$o['value']; + } } $miner['pools']= json_decode(@file_get_contents($configPools), true); - file_put_contents($configMiner, json_encode($miner, JSON_PRETTY_PRINT)); + file_put_contents($configMiner, json_encode($miner, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } ?> diff --git a/var/www/f_status.php b/var/www/f_status.php index 30eace5..f6b6942 100644 --- a/var/www/f_status.php +++ b/var/www/f_status.php @@ -12,6 +12,7 @@ header('Content-type: application/json'); include('inc/cgminer.inc.php'); +include('inc/ChromePhp.php'); // Miner data //$r['summary'] = cgminer('summary', '')['SUMMARY']; @@ -53,6 +54,8 @@ $HardwareErrors = 0; $Utility = 0; +// ChromePhp::log($r['status']); + if(!empty($r['status']['devs'])){ foreach ($r['status']['devs'] as $id => $dev) { $devices += $dev['MHS5s']>0?1:0; // Only count hashing devices @@ -66,18 +69,10 @@ } } - -$ret = explode(' ',$MHS5s); -$KHS5s = ($ret[0]/1024). " Kh/s"; -$ret = explode(' ',$MHSav); -$KHSav = ($ret[0]/1024). " Kh/s"; - $r['status']['dtot']=array( 'devices'=>$devices, 'MHS5s'=>$MHS5s, 'MHSav'=>$MHSav, - 'KHS5s'=>$KHS5s, - 'KHSav'=>$KHSav, 'Accepted'=>$Accepted, 'Rejected'=>$Rejected, 'HardwareErrors'=>$HardwareErrors, diff --git a/var/www/inc/ChromePhp.php b/var/www/inc/ChromePhp.php new file mode 100644 index 0000000..577b1ce --- /dev/null +++ b/var/www/inc/ChromePhp.php @@ -0,0 +1,446 @@ + + */ +class ChromePhp +{ + /** + * @var string + */ + const VERSION = '4.1.0'; + + /** + * @var string + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * @var string + */ + const BACKTRACE_LEVEL = 'backtrace_level'; + + /** + * @var string + */ + const LOG = 'log'; + + /** + * @var string + */ + const WARN = 'warn'; + + /** + * @var string + */ + const ERROR = 'error'; + + /** + * @var string + */ + const GROUP = 'group'; + + /** + * @var string + */ + const INFO = 'info'; + + /** + * @var string + */ + const GROUP_END = 'groupEnd'; + + /** + * @var string + */ + const GROUP_COLLAPSED = 'groupCollapsed'; + + /** + * @var string + */ + const TABLE = 'table'; + + /** + * @var string + */ + protected $_php_version; + + /** + * @var int + */ + protected $_timestamp; + + /** + * @var array + */ + protected $_json = array( + 'version' => self::VERSION, + 'columns' => array('log', 'backtrace', 'type'), + 'rows' => array() + ); + + /** + * @var array + */ + protected $_backtraces = array(); + + /** + * @var bool + */ + protected $_error_triggered = false; + + /** + * @var array + */ + protected $_settings = array( + self::BACKTRACE_LEVEL => 1 + ); + + /** + * @var ChromePhp + */ + protected static $_instance; + + /** + * Prevent recursion when working with objects referring to each other + * + * @var array + */ + protected $_processed = array(); + + /** + * constructor + */ + private function __construct() + { + $this->_php_version = phpversion(); + $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time(); + $this->_json['request_uri'] = $_SERVER['REQUEST_URI']; + } + + /** + * gets instance of this class + * + * @return ChromePhp + */ + public static function getInstance() + { + if (self::$_instance === null) { + self::$_instance = new self(); + } + return self::$_instance; + } + + /** + * logs a variable to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function log() + { + $args = func_get_args(); + return self::_log('', $args); + } + + /** + * logs a warning to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function warn() + { + $args = func_get_args(); + return self::_log(self::WARN, $args); + } + + /** + * logs an error to the console + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function error() + { + $args = func_get_args(); + return self::_log(self::ERROR, $args); + } + + /** + * sends a group log + * + * @param string value + */ + public static function group() + { + $args = func_get_args(); + return self::_log(self::GROUP, $args); + } + + /** + * sends an info log + * + * @param mixed $data,... unlimited OPTIONAL number of additional logs [...] + * @return void + */ + public static function info() + { + $args = func_get_args(); + return self::_log(self::INFO, $args); + } + + /** + * sends a collapsed group log + * + * @param string value + */ + public static function groupCollapsed() + { + $args = func_get_args(); + return self::_log(self::GROUP_COLLAPSED, $args); + } + + /** + * ends a group log + * + * @param string value + */ + public static function groupEnd() + { + $args = func_get_args(); + return self::_log(self::GROUP_END, $args); + } + + /** + * sends a table log + * + * @param string value + */ + public static function table() + { + $args = func_get_args(); + return self::_log(self::TABLE, $args); + } + + /** + * internal logging call + * + * @param string $type + * @return void + */ + protected static function _log($type, array $args) + { + // nothing passed in, don't do anything + if (count($args) == 0 && $type != self::GROUP_END) { + return; + } + + $logger = self::getInstance(); + + $logger->_processed = array(); + + $logs = array(); + foreach ($args as $arg) { + $logs[] = $logger->_convert($arg); + } + + $backtrace = debug_backtrace(false); + $level = $logger->getSetting(self::BACKTRACE_LEVEL); + + $backtrace_message = 'unknown'; + if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) { + $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line']; + } + + $logger->_addRow($logs, $backtrace_message, $type); + } + + /** + * converts an object to a better format for logging + * + * @param Object + * @return array + */ + protected function _convert($object) + { + // if this isn't an object then just return it + if (!is_object($object)) { + return $object; + } + + //Mark this object as processed so we don't convert it twice and it + //Also avoid recursion when objects refer to each other + $this->_processed[] = $object; + + $object_as_array = array(); + + // first add the class name + $object_as_array['___class_name'] = get_class($object); + + // loop through object vars + $object_vars = get_object_vars($object); + foreach ($object_vars as $key => $value) { + + // same instance as parent object + if ($value === $object || in_array($value, $this->_processed, true)) { + $value = 'recursion - parent object [' . get_class($value) . ']'; + } + $object_as_array[$key] = $this->_convert($value); + } + + $reflection = new ReflectionClass($object); + + // loop through the properties and add those + foreach ($reflection->getProperties() as $property) { + + // if one of these properties was already added above then ignore it + if (array_key_exists($property->getName(), $object_vars)) { + continue; + } + $type = $this->_getPropertyKey($property); + + if ($this->_php_version >= 5.3) { + $property->setAccessible(true); + } + + try { + $value = $property->getValue($object); + } catch (ReflectionException $e) { + $value = 'only PHP 5.3 can access private/protected properties'; + } + + // same instance as parent object + if ($value === $object || in_array($value, $this->_processed, true)) { + $value = 'recursion - parent object [' . get_class($value) . ']'; + } + + $object_as_array[$type] = $this->_convert($value); + } + return $object_as_array; + } + + /** + * takes a reflection property and returns a nicely formatted key of the property name + * + * @param ReflectionProperty + * @return string + */ + protected function _getPropertyKey(ReflectionProperty $property) + { + $static = $property->isStatic() ? ' static' : ''; + if ($property->isPublic()) { + return 'public' . $static . ' ' . $property->getName(); + } + + if ($property->isProtected()) { + return 'protected' . $static . ' ' . $property->getName(); + } + + if ($property->isPrivate()) { + return 'private' . $static . ' ' . $property->getName(); + } + } + + /** + * adds a value to the data array + * + * @var mixed + * @return void + */ + protected function _addRow(array $logs, $backtrace, $type) + { + // if this is logged on the same line for example in a loop, set it to null to save space + if (in_array($backtrace, $this->_backtraces)) { + $backtrace = null; + } + + // for group, groupEnd, and groupCollapsed + // take out the backtrace since it is not useful + if ($type == self::GROUP || $type == self::GROUP_END || $type == self::GROUP_COLLAPSED) { + $backtrace = null; + } + + if ($backtrace !== null) { + $this->_backtraces[] = $backtrace; + } + + $row = array($logs, $backtrace, $type); + + $this->_json['rows'][] = $row; + $this->_writeHeader($this->_json); + } + + protected function _writeHeader($data) + { + header(self::HEADER_NAME . ': ' . $this->_encode($data)); + } + + /** + * encodes the data to be sent along with the request + * + * @param array $data + * @return string + */ + protected function _encode($data) + { + return base64_encode(utf8_encode(json_encode($data))); + } + + /** + * adds a setting + * + * @param string key + * @param mixed value + * @return void + */ + public function addSetting($key, $value) + { + $this->_settings[$key] = $value; + } + + /** + * add ability to set multiple settings in one call + * + * @param array $settings + * @return void + */ + public function addSettings(array $settings) + { + foreach ($settings as $key => $value) { + $this->addSetting($key, $value); + } + } + + /** + * gets a setting + * + * @param string key + * @return mixed + */ + public function getSetting($key) + { + if (!isset($this->_settings[$key])) { + return null; + } + return $this->_settings[$key]; + } +} diff --git a/var/www/index.php b/var/www/index.php index 491db8b..3c1d437 100644 --- a/var/www/index.php +++ b/var/www/index.php @@ -22,7 +22,7 @@ --> - + @@ -85,11 +85,11 @@  -  Temp {{status.pi.temp}} °C  -  - Load {{100*status.pi.load|number:0}} + Load {{status.pi.load|number:2}} Scripta, by Lateral Factory under GPLv3 License

-LTC Donations welcome : Lcb3cy5nPnh3pQWPCpa55Zg8ShZj5kUHYC + diff --git a/var/www/login.php b/var/www/login.php index 8f83799..4810992 100644 --- a/var/www/login.php +++ b/var/www/login.php @@ -73,7 +73,7 @@

Scripta, by Lateral Factory under GPLv3 License

-LTC Donations welcome : Lcb3cy5nPnh3pQWPCpa55Zg8ShZj5kUHYC + diff --git a/var/www/miner.php b/var/www/miner.php index c8217d2..1e66b6c 100644 --- a/var/www/miner.php +++ b/var/www/miner.php @@ -1040,7 +1040,7 @@ function fmt($section, $name, $value, $when, $alldata) if ($ret === '') $ret = $b; - + return array( $ret, $class diff --git a/var/www/partials/backup.html b/var/www/partials/backup.html index e4b3b50..0552217 100644 --- a/var/www/partials/backup.html +++ b/var/www/partials/backup.html @@ -1,7 +1,43 @@ -

Backup

+

Backup settings

+
+ +
+
+ {{backupFolder}} + +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ {{thisFolder}} + +
+ +
+
+
+

@@ -11,16 +47,16 @@

Backup

Backed up items will be confirmed by a green label. Problematic items get a red label.
- Click backup again and Scripta will attempt to backup the not yet backed up items again. + Click backup again and MinePeon will attempt to backup the not yet backed up items again.

- - + +
-

Restore

+

Restore settings from backup

@@ -31,20 +67,20 @@

Restore

There seems to be no files in this backup, this can happen if permission was denied.
- +

Reload list will scan the backup folder for saved backups.
- Restore will copy the items in the currently selected backup to the active Scripta folder. Restart the miner to let it use the restored settings.
+ Restore will copy the items in the currently selected backup to the active MinePeon folder. Restart the miner to let it use the restored settings.
Export ... will make a zipfile of the items in the currently selected backup and serve it as a download.
Import file... lets you upload a zip file and will add it to the list of backups. If it does not show up, try to reload the list.

- +
@@ -55,4 +91,4 @@

Restore

- + \ No newline at end of file diff --git a/var/www/partials/miner.html b/var/www/partials/miner.html index 40c36a4..9e4bdf3 100644 --- a/var/www/partials/miner.html +++ b/var/www/partials/miner.html @@ -12,16 +12,21 @@

Available pools

Click to add pool to pool's list

- Coinotron -GiveMeCoins (EU) GiveMeCoins (EU)Litecoinpool - London (UK) -Litecoinpool - New York (USA) -Liteguardian (EU) -Liteguardian (USA) -WeMineLTC + Coinotron + GiveMeCoins (EU) + GiveMeCoins (EU) + Litecoinpool - London (UK) + Litecoinpool - New York (USA) + Liteguardian (EU) + Liteguardian (USA) + WeMineLTC + WeMineLTC (USA) + ScryptGuild + LTCRabbit
-
+

Pools

-

The settings below serve as direct input to the miner. One little error can and will let the miner crash.

- - + + +
-
+
+
+ +
- +
@@ -71,5 +79,31 @@

Pools

+ +

Options more info on GitHub (gc3355)

+ +
+
+ + +
+
+
+ +
+
+
+ +
+ +
+
+
+
+ + + +
+ Scr|pta
- +
@@ -23,7 +23,7 @@

Scr|pta

- +
@@ -39,7 +39,7 @@

Scr|pta

x
- +

{{settings.date}}

@@ -51,7 +51,7 @@

Scr|pta

- +
@@ -83,10 +83,13 @@

Mining

+
-

E-mail alerts

+

E-mail Alerts

@@ -173,20 +176,20 @@

E-mail alerts

- +
- +
- +

Please choose your own SMTP server ip address.

- +

Please choose your own SMTP server port.

diff --git a/var/www/partials/status.html b/var/www/partials/status.html index 63ac7d6..f8c1af5 100644 --- a/var/www/partials/status.html +++ b/var/www/partials/status.html @@ -45,7 +45,7 @@

Devices

Name ID - Temp + Freq Hashrate 5s Hashrate av Accepted @@ -59,11 +59,9 @@

Devices

- {{d.Temperature}} °C + {{d.MHS5s|mhs}}h/s {{d.MHSav|mhs}}h/s - @@ -93,7 +91,7 @@

Pools

URL User Status -
Pr
+
Prio
GW
Acc
Rej
@@ -118,8 +116,10 @@

Pools

- - + + +