Wipe branch
This commit is contained in:
commit
553754cd77
|
@ -0,0 +1,162 @@
|
|||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
BIT License
|
||||
|
||||
Copyright (c) 2023 Bit.Corp
|
||||
|
||||
Permission is deny, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
MAKE BACKUPS FOR LICENSE SERVER!!!
|
||||
Enable LED when WARN or CRIT
|
||||
Switch debug remotely
|
|
@ -0,0 +1,27 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
class ASICs(Enum):
|
||||
# Avalon 10-13
|
||||
Avalon1066 = auto()
|
||||
Avalon1126 = auto()
|
||||
|
||||
# Antminer S
|
||||
AntminerS19 = auto()
|
||||
AntminerS19J = auto()
|
||||
AntminerS19Pro = auto()
|
||||
AntminerS19JPro = auto()
|
||||
|
||||
# Antminer L
|
||||
AntminerL3plus = auto()
|
||||
AntminerL7 = auto()
|
||||
|
||||
# Antminer T
|
||||
AntminerT17 = auto()
|
||||
AntminerT17plus = auto()
|
||||
|
||||
# WhatsMiner
|
||||
WhatsMinerM20s = auto()
|
||||
WhatsMinerM21s = auto()
|
||||
WhatsMinerM30 = auto()
|
||||
WhatsMinerM31 = auto()
|
||||
WhatsMinerM50 = auto()
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
pyarmor-7 pack --clean -e "--onefile " main.py
|
||||
#pyarmor-7 pack --clean -e "--onefile " main.py asics.py checker.py colors.py config.py license.py macros.py telegrambot.py updater.py webserver.py
|
|
@ -0,0 +1,334 @@
|
|||
from threading import Thread
|
||||
from mysql.connector import connect, Error
|
||||
from time import sleep, time
|
||||
from asics import ASICs
|
||||
from macros import *
|
||||
import os, re, json, socket, requests
|
||||
|
||||
|
||||
class CThread(Thread):
|
||||
conn = None
|
||||
|
||||
def __init__(self, event):
|
||||
super(CThread, self).__init__()
|
||||
self.event = event
|
||||
|
||||
|
||||
def getreg(self, reg, str):
|
||||
res = re.findall(reg, str)
|
||||
if res: return res[0][1]
|
||||
else: return False
|
||||
|
||||
|
||||
def getstat_AntminerL3(self, log):
|
||||
status = 'ok'
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def getstat_Avalon1XXX(self, log, type):
|
||||
status = 'ok'
|
||||
|
||||
rej = float(self.getreg('("Device Rejected%":([0-9.]+),)', log))
|
||||
ghs = int(float(self.getreg('(GHSmm\[([0-9.:,]+)\])', log)))
|
||||
avg = int(float(self.getreg('(GHSavg\[([0-9.: ]+)\])', log)))
|
||||
# brd = self.getreg('(MGHS\[([0-9.:, ]+)\])', log)
|
||||
tmp = int(self.getreg('(TMax\[([0-9]+)\])', log))
|
||||
|
||||
chash = CONF.get('asics', ASICs(type).name, 'crit', 'hash')
|
||||
whash = CONF.get('asics', ASICs(type).name, 'warn', 'hash')
|
||||
|
||||
ctemp = CONF.get('asics', ASICs(type).name, 'crit', 'temp')
|
||||
wtemp = CONF.get('asics', ASICs(type).name, 'warn', 'temp')
|
||||
|
||||
crej = CONF.get('asics', ASICs(type).name, 'crit', 'rej')
|
||||
wrej = CONF.get('asics', ASICs(type).name, 'warn', 'rej')
|
||||
|
||||
if rej > crej or ghs < chash or avg < chash or tmp > ctemp: status = 'crit'
|
||||
elif rej > wrej or ghs < whash or avg < whash or tmp > wtemp: status = 'warn'
|
||||
|
||||
DEBUG(f"{rej} | {ghs} | {avg} | {brd} | {tmp}")
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def getstat_AntminerS19(self, log):
|
||||
status = 'ok'
|
||||
|
||||
rej = float(self.getreg('("Device Rejected%": ([0-9.]+),)', log))
|
||||
ghs = int(float(self.getreg('("GHS 5s": ([0-9.]+),)', log)))
|
||||
avg = int(float(self.getreg('("GHS av": ([0-9.]+),)', log)))
|
||||
tmp = int(self.getreg('("TMax": ([0-9]+),)', log))
|
||||
|
||||
chash = CONF.get('asics', ASICs(type).name, 'crit', 'hash')
|
||||
whash = CONF.get('asics', ASICs(type).name, 'warn', 'hash')
|
||||
|
||||
ctemp = CONF.get('asics', ASICs(type).name, 'crit', 'temp')
|
||||
wtemp = CONF.get('asics', ASICs(type).name, 'warn', 'temp')
|
||||
|
||||
crej = CONF.get('asics', ASICs(type).name, 'crit', 'rej')
|
||||
wrej = CONF.get('asics', ASICs(type).name, 'warn', 'rej')
|
||||
|
||||
if rej > crej or ghs < chash or avg < chash or tmp > ctemp: status = 'crit'
|
||||
elif rej > wrej or ghs < whash or avg < whash or tmp > wtemp: status = 'warn'
|
||||
|
||||
DEBUG(f"{rej} | {ghs} | {avg} | {brd} | {tmp}")
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def gettype(self, ip):
|
||||
info = self.rtcp(ip, 4028, '{"command": "version"}')
|
||||
|
||||
if not info:
|
||||
return False
|
||||
|
||||
if CONF.get('debug') and info:
|
||||
info1 = self.rtcp(ip, 4028, '{"command": "stats"}')
|
||||
info2 = self.rtcp(ip, 4028, '{"command": "version"}')
|
||||
info3 = self.rtcp(ip, 4028, '{"command": "summary"}')
|
||||
info4 = self.rtcp(ip, 4028, '{"command": "pools"}')
|
||||
info5 = self.rtcp(ip, 4028, '{"command": "devdetails"}')
|
||||
info6 = self.rtcp(ip, 4028, '{"command": "get_version"}')
|
||||
info7 = self.rtcp(ip, 4028, '{"command": "status"}')
|
||||
info8 = self.rtcp(ip, 4028, '{"command": "get_miner_info"}')
|
||||
info9 = self.rtcp(ip, 4028, '{"command": "devs"}')
|
||||
info10 = self.rtcp(ip, 4028, '{"command": "notify"}')
|
||||
info11 = self.rtcp(ip, 4028, '{"command": "coin"}')
|
||||
info12 = self.rtcp(ip, 4028, '{"command": "edevs"}')
|
||||
info13 = self.rtcp(ip, 4028, '{"command": "estats"}')
|
||||
|
||||
DEBUG(f"[{ip}] {info1}")
|
||||
DEBUG(f"[{ip}] {info2}")
|
||||
DEBUG(f"[{ip}] {info3}")
|
||||
DEBUG(f"[{ip}] {info4}")
|
||||
DEBUG(f"[{ip}] {info5}")
|
||||
DEBUG(f"[{ip}] {info6}")
|
||||
DEBUG(f"[{ip}] {info7}")
|
||||
DEBUG(f"[{ip}] {info8}")
|
||||
DEBUG(f"[{ip}] {info9}")
|
||||
DEBUG(f"[{ip}] {info10}")
|
||||
DEBUG(f"[{ip}] {info11}")
|
||||
DEBUG(f"[{ip}] {info12}")
|
||||
DEBUG(f"[{ip}] {info13}")
|
||||
|
||||
info += self.rtcp(ip, 4028, '{"command": "devdetails"}')
|
||||
|
||||
# Bitmain
|
||||
if "Antminer" in info:
|
||||
if "S19J Pro" in info:
|
||||
return ASICs.AntminerS19JPro.value
|
||||
if "S19J" in info:
|
||||
return ASICs.AntminerS19J.value
|
||||
if "S19 Pro" in info:
|
||||
return ASICs.AntminerS19Pro.value
|
||||
if "S19" in info:
|
||||
return ASICs.AntminerS19.value
|
||||
|
||||
if "L3+" in info:
|
||||
return ASICs.AntminerL3plus.value
|
||||
if "L7" in info:
|
||||
return ASICs.AntminerL3plus.value
|
||||
|
||||
if "T17+" in info:
|
||||
return ASICs.AntminerT17plus.value
|
||||
if "T17" in info:
|
||||
return ASICs.AntminerT17.value
|
||||
|
||||
# Avalon
|
||||
if "Avalon" in info:
|
||||
if "1066" in info:
|
||||
return ASICs.Avalon1066.value
|
||||
if "1126" in info:
|
||||
return ASICs.Avalon1126.value
|
||||
|
||||
# WhatsMiner
|
||||
if "bitmicro" in info:
|
||||
if "M20s":
|
||||
return ASICs.WhatsMinerM20s.value
|
||||
if "M21s":
|
||||
return ASICs.WhatsMinerM21s.value
|
||||
if "M30":
|
||||
return ASICs.WhatsMinerM30.value
|
||||
if "M31":
|
||||
return ASICs.WhatsMinerM31.value
|
||||
if "M50":
|
||||
return ASICs.WhatsMinerM50.value
|
||||
|
||||
# Innosilicon
|
||||
if False:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def rtcp(self, ip, port, cmd):
|
||||
cmd = cmd.encode('utf-8')
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
try:
|
||||
s.connect((ip, port))
|
||||
s.sendall(cmd)
|
||||
tdata = []
|
||||
while 1:
|
||||
data = s.recv(1024*32)
|
||||
if not data: break
|
||||
tdata.append(data)
|
||||
|
||||
data = b''.join(tdata).decode("utf-8")
|
||||
|
||||
return data
|
||||
except OSError as e:
|
||||
# ERR(e)
|
||||
return False
|
||||
|
||||
|
||||
def check_AntminerL3(self, ip):
|
||||
# f'http://{ip}/cgi-bin/get_miner_status.cgi'
|
||||
# f'http://{ip}/cgi-bin/get_miner_conf.cgi'
|
||||
summ = self.rtcp(ip, 4028, "summary")
|
||||
stat = self.rtcp(ip, 4028, "estats")
|
||||
info = self.rtcp(ip, 4028, "version")
|
||||
|
||||
if summ and stat:
|
||||
mac = json.loads(requests.get(
|
||||
f'http://{ip}/cgi-bin/get_system_info.cgi',
|
||||
auth=requests.auth.HTTPDigestAuth('root', 'root')).text)['macaddr'].lower()
|
||||
|
||||
log = f"{info} ||| {summ} ||| {stat}"
|
||||
|
||||
return log, mac
|
||||
|
||||
return False, False
|
||||
|
||||
|
||||
def check_AntminerS19(self, ip):
|
||||
# f'http://{ip}/cgi-bin/get_miner_status.cgi'
|
||||
# f'http://{ip}/cgi-bin/get_miner_conf.cgi'
|
||||
summ = self.rtcp(ip, 4028, "summary")
|
||||
stat = self.rtcp(ip, 4028, "stats")
|
||||
info = self.rtcp(ip, 4028, "version")
|
||||
|
||||
if summ and stat:
|
||||
mac = json.loads(requests.get(
|
||||
f'http://{ip}/cgi-bin/get_network_info.cgi',
|
||||
auth=requests.auth.HTTPDigestAuth('root', 'root')).text)['macaddr'].lower()
|
||||
|
||||
log = f"{info} ||| {summ} ||| {stat}"
|
||||
|
||||
return log, mac
|
||||
|
||||
return False, False
|
||||
|
||||
|
||||
def check_Avalon1XXX(self, ip):
|
||||
summ = self.rtcp(ip, 4028, "summary")
|
||||
stat = self.rtcp(ip, 4028, "estats")
|
||||
info = self.rtcp(ip, 4028, "version")
|
||||
|
||||
if summ and stat:
|
||||
mac = self.getreg(r'(MAC=([A-z0-9]+))', info)
|
||||
if mac: mac = ':'.join(mac[i:i+2] for i in range(0,12,2))
|
||||
else: mac = ':'
|
||||
|
||||
log = f"{info} ||| {summ} ||| {stat}"
|
||||
|
||||
return log, mac
|
||||
|
||||
return False, False
|
||||
|
||||
|
||||
def check(self, ip):
|
||||
try:
|
||||
type = self.gettype(ip)
|
||||
|
||||
if not type:
|
||||
return (False, False, False, False, False), False
|
||||
|
||||
if type in [ASICs.AntminerL3plus.value]:
|
||||
log, mac = self.check_AntminerL3(ip)
|
||||
status = self.getstat_AntminerL3(log, type)
|
||||
elif type in [ASICs.Avalon1066.value, ASICs.Avalon1126.value]:
|
||||
log, mac = self.check_Avalon1XXX(ip)
|
||||
status = self.getstat_Avalon1XXX(log, type)
|
||||
elif type in [ASICs.AntminerS19.value, ASICs.AntminerS19J.value, ASICs.AntminerS19Pro.value, ASICs.AntminerS19JPro.value]:
|
||||
log, mac = self.check_AntminerS19(ip)
|
||||
status = self.getstat_AntminerS19(log, type)
|
||||
|
||||
return (ip, mac, type, int(time()), log), status
|
||||
except Exception as e:
|
||||
WARN('Minor error in <lambda>Thread: ' + str(e))
|
||||
return (False, False, False, False, False), False
|
||||
|
||||
|
||||
def checkall(self):
|
||||
ips = []
|
||||
|
||||
with self.conn.cursor(dictionary=True) as c:
|
||||
c.execute("SELECT * FROM `ips` WHERE `location` = %s", (CONF.get('location'),))
|
||||
|
||||
for ip in c.fetchall():
|
||||
ip = ip['ip']
|
||||
if ip.isalpha() or not ip.find('-'):
|
||||
ips.append(ip)
|
||||
else:
|
||||
i_p = ip.split('.')
|
||||
i_p_new = []
|
||||
for i in i_p:
|
||||
i_p_new.append(i.split('-'))
|
||||
i_p_new[-1][0] = int(i_p_new[-1][0])
|
||||
i_p_new[-1][-1] = int(i_p_new[-1][-1])
|
||||
|
||||
for i1 in range(i_p_new[0][0], i_p_new[0][-1] + 1):
|
||||
for i2 in range(i_p_new[1][0], i_p_new[1][-1] + 1):
|
||||
for i3 in range(i_p_new[2][0], i_p_new[2][-1] + 1):
|
||||
for i4 in range(i_p_new[3][0], i_p_new[3][-1] + 1):
|
||||
ips.append(f"{i1}.{i2}.{i3}.{i4}")
|
||||
|
||||
Tca = []
|
||||
ca = []
|
||||
for ip in ips:
|
||||
T = Thread(target=lambda x: ca.append(self.check(x)), args=(ip,))
|
||||
Tca.append(T)
|
||||
for i in range(len(Tca)):
|
||||
Tca[i].start()
|
||||
for i in range(len(Tca)):
|
||||
Tca[i].join()
|
||||
|
||||
with self.conn.cursor() as c:
|
||||
c.execute(f"DELETE FROM `laststate` WHERE `location` = %s", (CONF.get('location'),))
|
||||
|
||||
for i in ca:
|
||||
i, status = i
|
||||
if i[4]:
|
||||
c.execute(f"INSERT INTO `asiclogs` (`ip`, `mac`, `type`, `time`, `log`) VALUES (%s, %s, %s, %s, %s)", i)
|
||||
|
||||
if i[1] == ':':
|
||||
continue
|
||||
c.execute(
|
||||
f"INSERT INTO `laststate` (`ip`, `mac`, `type`, `location`, `status`, `time`) VALUES (%s, %s, %s, %s, %s, %s)",
|
||||
(i[0], i[1], i[2], CONF.get('location'), status, int(time())))
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
SUCC("Checker started!")
|
||||
sleep(5)
|
||||
|
||||
self.conn = connect(
|
||||
host=CONF.get('db', 'host'),
|
||||
user=CONF.get('db', 'user'),
|
||||
password=CONF.get('db', 'password'),
|
||||
database=CONF.get('db', 'name'))
|
||||
|
||||
while 1:
|
||||
if self.event.is_set():
|
||||
SUCC("Checker stopped!")
|
||||
break
|
||||
|
||||
self.checkall()
|
||||
sleep(10)
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
watch -n 0.1 'ps -o vsize,command,pcpu ax | sort -b -k3 -r | grep -E -i -w "[0-9]+ python"'
|
|
@ -0,0 +1,43 @@
|
|||
from threading import Thread
|
||||
from mysql.connector import connect, Error
|
||||
from time import sleep, time
|
||||
from macros import *
|
||||
import os
|
||||
|
||||
|
||||
class NThread(Thread):
|
||||
conn = None
|
||||
|
||||
def __init__(self, event):
|
||||
super(NThread, self).__init__()
|
||||
self.event = event
|
||||
|
||||
|
||||
def clean(self):
|
||||
with self.conn.cursor() as c:
|
||||
c.execute(f"DELETE FROM `asiclogs` WHERE `time` < UNIX_TIMESTAMP() - %s", (CONF.get('db-log-days'),))
|
||||
c.execute(f"DELETE FROM `laststate` WHERE `time` < UNIX_TIMESTAMP() - 30")
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
SUCC("Cleaner started!")
|
||||
|
||||
self.conn = connect(
|
||||
host=CONF.get('db', 'host'),
|
||||
user=CONF.get('db', 'user'),
|
||||
password=CONF.get('db', 'password'),
|
||||
database=CONF.get('db', 'name'))
|
||||
|
||||
while 1:
|
||||
if self.event.is_set():
|
||||
SUCC('Cleaner stopped!')
|
||||
break
|
||||
|
||||
self.clean()
|
||||
sleep(5)
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
|
@ -0,0 +1,20 @@
|
|||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
|
||||
BLACK = "\033[30m"
|
||||
RED = "\033[31m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
BLUE = "\033[34m"
|
||||
MAGENTA = "\033[35m"
|
||||
CYAN = "\033[36m"
|
||||
WHITE = "\033[37m"
|
||||
|
||||
BGBLACK = "\033[40m"
|
||||
BGRED = "\033[41m"
|
||||
BGGREEN = "\033[42m"
|
||||
BGYELLOW = "\033[43m"
|
||||
BGBLUE = "\033[44m"
|
||||
BGMAGENTA = "\033[45m"
|
||||
BGCYAN = "\033[46m"
|
||||
BGWHITE = "\033[47m"
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"license": "00001111-2222-3333-4444-555566667777",
|
||||
"location": "Underground",
|
||||
"logging": true,
|
||||
"db-log-days": 7,
|
||||
"debug": true,
|
||||
"updates": false,
|
||||
"port": 9070,
|
||||
"telegram": {
|
||||
"enable": true,
|
||||
"channel": -1001871940722,
|
||||
"token": "5953600362:AAELgZr0Ldstf0omK43zKrAzGXOuMGowcf8",
|
||||
"normal-notify": true,
|
||||
"warn-notify": false,
|
||||
"crit-notify": true
|
||||
},
|
||||
"db": {
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "0",
|
||||
"name": "myasics"
|
||||
},
|
||||
"asics": {
|
||||
"L3plus": {
|
||||
"crit": {
|
||||
"hash": 40000,
|
||||
"temp": 90,
|
||||
"rejects": 2.5
|
||||
},
|
||||
"warn": {
|
||||
"hash": 45000,
|
||||
"temp": 80,
|
||||
"rejects": 1.5
|
||||
}
|
||||
},
|
||||
"S19": {
|
||||
"crit": {
|
||||
"hash": 80000,
|
||||
"temp": 90,
|
||||
"rejects": 2.5
|
||||
},
|
||||
"warn": {
|
||||
"hash": 85000,
|
||||
"temp": 80,
|
||||
"rejects": 1.5
|
||||
}
|
||||
},
|
||||
"Avalon1066": {
|
||||
"crit": {
|
||||
"hash": 42000,
|
||||
"temp": 90,
|
||||
"rejects": 2.5
|
||||
},
|
||||
"warn": {
|
||||
"hash": 46000,
|
||||
"temp": 80,
|
||||
"rejects": 1.5
|
||||
}
|
||||
},
|
||||
"Avalon1126": {
|
||||
"crit": {
|
||||
"hash": 64000,
|
||||
"temp": 90,
|
||||
"rejects": 2.5
|
||||
},
|
||||
"warn": {
|
||||
"hash": 66000,
|
||||
"temp": 80,
|
||||
"rejects": 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
APPNAME='Bit.ASICmon'
|
||||
VERSION='v0.1.1a'
|
||||
DEBUGGING=True
|
|
@ -0,0 +1,12 @@
|
|||
version: '3.1'
|
||||
|
||||
services:
|
||||
|
||||
db:
|
||||
image: mysql
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 0
|
||||
ports:
|
||||
- 3306:3306
|
|
@ -0,0 +1,223 @@
|
|||
const sprintf = (...[string, ...args]) => {
|
||||
return string.replace(/{(\d+)}/g, (match, number) => args[number] ?? match);
|
||||
}
|
||||
|
||||
const getJSON = async url => {
|
||||
const resp = await fetch(url)
|
||||
const json = await resp.json()
|
||||
return json;
|
||||
}
|
||||
|
||||
const createPopup = async id => {
|
||||
const asics = await getJSON('/api/asictypes')
|
||||
|
||||
asicoptions = ''
|
||||
asics.forEach(elem => {
|
||||
asicoptions += sprintf('<option value="{0}">{1}</option>', elem['key'], elem['value'])
|
||||
})
|
||||
|
||||
document.querySelector('body').innerHTML += sprintf(popup, id, asicoptions, 'content')
|
||||
}
|
||||
|
||||
const deletePopup = id => {
|
||||
document.querySelector('#popup-' + id).remove()
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
let inner = await getStats()
|
||||
document.querySelector('.grid').innerHTML = inner
|
||||
}
|
||||
|
||||
const datafn = async () => {
|
||||
// return await getJSON('/api/webinit')
|
||||
}
|
||||
|
||||
const data = datafn()
|
||||
|
||||
console.log(data)
|
||||
|
||||
const cols = 40
|
||||
const rows = 40
|
||||
|
||||
|
||||
const css =
|
||||
`<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap');
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
border: 1px solid #fff;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.green {
|
||||
background: #0b0 !important;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
background: #ff0 !important;
|
||||
}
|
||||
|
||||
.red {
|
||||
background: #f11 !important;
|
||||
}
|
||||
|
||||
.darkred {
|
||||
background: #300 !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 60px;
|
||||
background: #222;
|
||||
text-align: center;
|
||||
margin: 0 0 1px 0;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
color: #bbb;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${cols}, 30px);
|
||||
grid-template-rows: repeat(${rows}, 30px);
|
||||
grid-gap: 1px;
|
||||
margin: auto;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
background: #111;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.cell:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cell:hover::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
background: #ffffff33;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.popup-bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #00000033;
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
.popup-window {
|
||||
position: relative;
|
||||
height: max-content;
|
||||
max-height: 90vh;
|
||||
width: max-content;
|
||||
max-width: 90vw;
|
||||
padding: 40px;
|
||||
background: #222;
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
const header =
|
||||
'<div class="header">'
|
||||
+ '<div class="header-info">'
|
||||
+ 'Всего устройств: {0}, Предупреждений, {1}, Ошибки: {2}, В сети: {3}, Не в сети: {4}'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
|
||||
|
||||
const cell =
|
||||
'<div class="cell {1}" id="cell-{0}" onclick="createPopup({0})">'
|
||||
+ '{2}'
|
||||
+ '</div>'
|
||||
|
||||
const grid =
|
||||
'<div class="grid">'
|
||||
+ '{0}'
|
||||
+ '</div>'
|
||||
|
||||
const popup =
|
||||
'<div class="popup" id="popup-{0}">'
|
||||
+ '<div class="popup-bg" onclick="deletePopup({0})"></div>'
|
||||
+ '<div class="popup-window">'
|
||||
+ '<div class="row">'
|
||||
+ '<select>'
|
||||
+ '<option value="none">Свободно</option>'
|
||||
+ '{1}'
|
||||
+ '</select>'
|
||||
+ '<input type="text" placeholder="Адрес...">'
|
||||
+ '</div>'
|
||||
+ '{2}'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
|
||||
const getStats = async () => {
|
||||
let cells = ''
|
||||
let info = await getJSON('/curstatus.json')
|
||||
for(let i = 1; i <= cols * rows; i++) {
|
||||
if(info.asics[i]?.status == "ok")
|
||||
cells += sprintf(cell, i, 'green', info.asics[i]?.hashrate)
|
||||
else if(info.asics[i]?.status == "warn")
|
||||
cells += sprintf(cell, i, 'yellow', info.asics[i]?.hashrate)
|
||||
else if(info.asics[i]?.status == "crit")
|
||||
cells += sprintf(cell, i, 'red', info.asics[i]?.hashrate)
|
||||
else if(info.asics[i]?.status == "off")
|
||||
cells += sprintf(cell, i, 'darkred', '')
|
||||
else
|
||||
cells += sprintf(cell, i, '', '')
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
document.querySelector('body').innerHTML += css
|
||||
document.querySelector('body').innerHTML += sprintf(header)
|
||||
|
||||
let inner = sprintf(grid, await getStats())
|
||||
document.querySelector('body').innerHTML += inner
|
||||
|
||||
setInterval(async () => {
|
||||
await update()
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
|
||||
run()
|
|
@ -0,0 +1,76 @@
|
|||
from threading import Thread
|
||||
from time import sleep, time
|
||||
from macros import *
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Random import get_random_bytes
|
||||
from hashlib import sha256
|
||||
from base64 import b64decode
|
||||
from datetime import datetime
|
||||
from urllib import request
|
||||
import os, json, requests
|
||||
|
||||
|
||||
class LThread(Thread):
|
||||
_FIRST = True
|
||||
|
||||
|
||||
def __init__(self, event):
|
||||
super(LThread, self).__init__()
|
||||
self.event = event
|
||||
|
||||
|
||||
def check(self):
|
||||
url = 'https://license.bitheaven.ru/api/v1/license.check'
|
||||
post = {
|
||||
'key': CONF.get('license'),
|
||||
'loc': CONF.get('location'),
|
||||
'time': int(time()),
|
||||
'data': get_random_bytes(16)
|
||||
}
|
||||
curtime = post['time']
|
||||
|
||||
try:
|
||||
req = requests.post(url, data=post).json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return False
|
||||
|
||||
if req['error']:
|
||||
ERR(f"Server error! {req['msg']}")
|
||||
return True
|
||||
|
||||
key = sha256(post['loc'].encode('utf-8')).hexdigest()[0:32].encode("utf-8")
|
||||
iv = post['data']
|
||||
aes = AES.new(key, AES.MODE_OFB, iv=iv)
|
||||
msg = aes.decrypt(b64decode(req['data'])).decode('unicode_escape').strip()
|
||||
|
||||
until = datetime.fromtimestamp(req['until']).strftime('%d.%m.%Y')
|
||||
|
||||
if self._FIRST:
|
||||
INFO(f'License paid until: {until}')
|
||||
self._FIRST = False
|
||||
|
||||
return f"{CONF.get('license')}{curtime}" == msg
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while 1:
|
||||
if self.event.is_set():
|
||||
break
|
||||
|
||||
cur = self.check()
|
||||
|
||||
if not cur:
|
||||
try:
|
||||
request.urlopen('http://1.1.1.1', timeout=2)
|
||||
CRIT("License wrong or expired!")
|
||||
WARN("Get it on https://bhev.ru/glfbam")
|
||||
sleep(60)
|
||||
except:
|
||||
ERR("No access to network!")
|
||||
# os._exit(1)
|
||||
|
||||
sleep(5)
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
Binary file not shown.
|
@ -0,0 +1,97 @@
|
|||
from datetime import datetime
|
||||
from colors import *
|
||||
from platform import system
|
||||
import inspect
|
||||
import json, os, config
|
||||
|
||||
|
||||
__DEBUGGING_ALERT = False
|
||||
|
||||
MUSTDIE = system().lower() == 'windows'
|
||||
LINUX = system().lower() == 'linux'
|
||||
MACOS = system().lower() == 'darwin'
|
||||
|
||||
|
||||
class CONF():
|
||||
def __init__(self):
|
||||
CONF.config = json.load(open('config.json'))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get(*params):
|
||||
conf = CONF.config
|
||||
|
||||
for param in params:
|
||||
if param in conf.keys():
|
||||
conf = conf[param]
|
||||
|
||||
if conf:
|
||||
return conf
|
||||
|
||||
return False
|
||||
|
||||
CONF()
|
||||
|
||||
|
||||
def __GET_TIME():
|
||||
return datetime.now().strftime("%H:%M:%S.%f")
|
||||
|
||||
|
||||
def __GET_DATE():
|
||||
return datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def __ADD_TO_LOG(str):
|
||||
if CONF.get('logging'):
|
||||
if not os.path.isdir('log'):
|
||||
os.mkdir('log')
|
||||
log = open(f'log/{__GET_DATE()}.log', 'a')
|
||||
log.write(str + '\n')
|
||||
log.close()
|
||||
|
||||
|
||||
def __PRINT_LOG(str, log):
|
||||
if CONF.get('debug') and config.DEBUGGING:
|
||||
st = inspect.stack()[2]
|
||||
caller = st.filename.split('/')[-1].split('.')[0]
|
||||
callerline = st.lineno
|
||||
str = f'[{__GET_TIME()}] [{caller}:{callerline}] {str}'
|
||||
log = f'[{caller}:{callerline}] {log}'
|
||||
else:
|
||||
str = f'[{__GET_TIME()}] {str}'
|
||||
|
||||
print(str)
|
||||
__ADD_TO_LOG(log)
|
||||
|
||||
|
||||
def INFO(s = ''):
|
||||
__PRINT_LOG(f"[INFO] {str(s)}{RESET}", str(s))
|
||||
|
||||
|
||||
def DEBUG(s = ''):
|
||||
global __DEBUGGING_ALERT
|
||||
|
||||
if not CONF.get('debug'):
|
||||
return None
|
||||
|
||||
if config.DEBUGGING:
|
||||
__PRINT_LOG(f"{MAGENTA}[DEBUG]{RESET} {BGBLUE}{YELLOW}{BOLD}{str(s)}{RESET}", str(s))
|
||||
elif not __DEBUGGING_ALERT:
|
||||
WARN('DEBUGGING DISABLED BY OWNER!')
|
||||
__DEBUGGING_ALERT = True
|
||||
|
||||
|
||||
def SUCC(s = ''):
|
||||
__PRINT_LOG(f"{GREEN}[SUCCESS]{RESET} {GREEN}{str(s)}{RESET}", str(s))
|
||||
|
||||
|
||||
def WARN(s = ''):
|
||||
__PRINT_LOG(f"{YELLOW}[WARN]{RESET} {YELLOW}{str(s)}{RESET}", str(s))
|
||||
|
||||
|
||||
def ERR(s = ''):
|
||||
__PRINT_LOG(f"{RED}[ERROR]{RESET} {RED}{str(s)}{RESET}", str(s))
|
||||
|
||||
|
||||
def CRIT(s = ''):
|
||||
__PRINT_LOG(f"{RED}{BOLD}[CRITICAL]{RESET} {BGRED}{WHITE} {str(s)} {RESET}", str(s))
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from updater import UThread
|
||||
from license import LThread
|
||||
from checker import CThread
|
||||
from cleaner import NThread
|
||||
from telegrambot import TGThread
|
||||
from webserver import WSThread
|
||||
from time import sleep
|
||||
from macros import *
|
||||
from colors import *
|
||||
from threading import Event
|
||||
import config, sys, os, asyncio
|
||||
|
||||
|
||||
INFO('╔' + '═'*(len(config.APPNAME) + len(config.VERSION) + 3) + '╗')
|
||||
INFO(f"║ {CYAN}{BOLD}{config.APPNAME}{RESET} {config.VERSION} ║")
|
||||
INFO('╚' + '═'*(len(config.APPNAME) + len(config.VERSION) + 3) + '╝')
|
||||
|
||||
if CONF.get('debug'):
|
||||
INFO('DEBUG TEST START ' + '*' * 20)
|
||||
DEBUG(f"{RESET}{BLACK}*{RED}*{GREEN}*{YELLOW}*{BLUE}*{MAGENTA}*{CYAN}*{WHITE}*")
|
||||
DEBUG(f"{RESET}{BOLD}{BLACK}*{RED}*{GREEN}*{YELLOW}*{BLUE}*{MAGENTA}*{CYAN}*{WHITE}*")
|
||||
DEBUG(f"{RESET}{BGBLACK}*{BGRED}*{BGGREEN}*{BGYELLOW}*{BGBLUE}*{BGMAGENTA}*{BGCYAN}*{BGWHITE}*")
|
||||
INFO('Test message')
|
||||
DEBUG('Test message')
|
||||
SUCC('Test message')
|
||||
WARN('Test message')
|
||||
ERR('Test message')
|
||||
CRIT('Test message')
|
||||
INFO('DEBUG TEST END ' + '*' * 22 + RESET)
|
||||
|
||||
|
||||
eupdate = Event()
|
||||
estop = Event()
|
||||
|
||||
lt = LThread(eupdate)
|
||||
lt.daemon = True
|
||||
lt.start()
|
||||
|
||||
wt = WSThread(eupdate)
|
||||
wt.daemon = True
|
||||
wt.start()
|
||||
|
||||
ct = CThread(eupdate)
|
||||
ct.daemon = True
|
||||
ct.start()
|
||||
|
||||
nt = NThread(eupdate)
|
||||
nt.daemon = True
|
||||
nt.start()
|
||||
|
||||
tt = TGThread(eupdate, estop)
|
||||
tt.daemon = True
|
||||
tt.start()
|
||||
|
||||
ut = UThread()
|
||||
ut.daemon = True
|
||||
ut.start()
|
||||
|
||||
updated = False
|
||||
|
||||
try:
|
||||
while 1:
|
||||
if lt.is_alive() and wt.is_alive() and ct.is_alive() and tt.is_alive() and ut.is_alive() and nt.is_alive():
|
||||
sleep(1)
|
||||
elif lt.is_alive() and wt.is_alive() and ct.is_alive() and tt.is_alive() and nt.is_alive():
|
||||
eupdate.set()
|
||||
if lt.is_alive(): lt.join()
|
||||
if wt.is_alive(): wt.join()
|
||||
if ct.is_alive(): ct.join()
|
||||
if nt.is_alive(): nt.join()
|
||||
if tt.is_alive(): tt.join()
|
||||
updated = True
|
||||
SUCC('Program updated, reboot!')
|
||||
break
|
||||
else:
|
||||
estop.set()
|
||||
sleep(1)
|
||||
CRIT('Program broken.')
|
||||
os._exit(3)
|
||||
except KeyboardInterrupt:
|
||||
sys.stdout.flush()
|
||||
print('\r', end='')
|
||||
estop.set()
|
||||
sleep(1)
|
||||
CRIT('STOP')
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
estop.set()
|
||||
sleep(1)
|
||||
os._exit(1)
|
||||
|
||||
if updated:
|
||||
os.execl(sys.argv[0], *sys.argv)
|
|
@ -0,0 +1,4 @@
|
|||
flask
|
||||
python-telegram-bot
|
||||
pyinstaller
|
||||
pyarmor<8.0.0
|
|
@ -0,0 +1,125 @@
|
|||
from threading import Thread
|
||||
from telegram import Bot, error
|
||||
from asics import ASICs
|
||||
from time import sleep
|
||||
from mysql.connector import connect, Error
|
||||
from macros import *
|
||||
import os, json, asyncio
|
||||
|
||||
|
||||
class TGThread(Thread):
|
||||
conn = None
|
||||
bot = None
|
||||
asics = {}
|
||||
|
||||
|
||||
def __init__(self, event, event2):
|
||||
super(TGThread, self).__init__()
|
||||
self.event = event
|
||||
self.event2 = event2
|
||||
|
||||
|
||||
async def sendmsg(self, msg):
|
||||
return await self.bot.send_message(CONF.get('telegram', 'channel'), msg, 'MarkdownV2')
|
||||
|
||||
|
||||
async def check(self):
|
||||
msg = []
|
||||
|
||||
for i in self.asics.keys():
|
||||
self.asics[i]['tooff'] -= 1
|
||||
|
||||
with self.conn.cursor(dictionary=True) as c:
|
||||
c.execute(f"SELECT * FROM `laststate`")
|
||||
|
||||
for i in c.fetchall():
|
||||
if not i['mac'] in self.asics.keys():
|
||||
self.asics[i['mac']] = {}
|
||||
self.asics[i['mac']]['alert'] = 0
|
||||
|
||||
msg.append(f"🟢 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` online\!")
|
||||
|
||||
self.asics[i['mac']]['tooff'] = 3
|
||||
self.asics[i['mac']]['status'] = i['status']
|
||||
|
||||
if i['status'] == 'crit':
|
||||
if CONF.get('telegram', 'crit-notify') and self.asics[i['mac']]['alert'] < 2:
|
||||
msg.append(f"🔴 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` crit status\!")
|
||||
self.asics[i['mac']]['alert'] = 2
|
||||
elif i['status'] == 'warn':
|
||||
if CONF.get('telegram', 'warn-notify') and self.asics[i['mac']]['alert'] < 1:
|
||||
msg.append(f"🟡 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` warn status\!")
|
||||
self.asics[i['mac']]['alert'] = 1
|
||||
elif i['status'] == 'ok':
|
||||
if CONF.get('telegram', 'normal-notify') and self.asics[i['mac']]['alert'] != 0:
|
||||
msg.append(f"🟢 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` normal status\!")
|
||||
self.asics[i['mac']]['alert'] = 0
|
||||
|
||||
if self.asics[i['mac']]['tooff'] <= 0:
|
||||
msg.append(f"🔴 {i['location']} \| ASIC \"*{ASICs(i['type']).name}*\" `{i['ip']}` offline\!")
|
||||
del self.asics[i['mac']]
|
||||
|
||||
if len(msg) > 0:
|
||||
await self.sendmsg('\n'.join(msg))
|
||||
|
||||
|
||||
async def runbot(self):
|
||||
try:
|
||||
res = await self.sendmsg('🟢 *Notify server online\!*')
|
||||
INFO(f"Telegram channel ID: {res['chat']['id']}")
|
||||
|
||||
while 1:
|
||||
if self.event.is_set():
|
||||
SUCC('Telegram stopped!')
|
||||
await self.sendmsg('🟡 Updating software\! Recommend check all running instances.')
|
||||
break
|
||||
|
||||
await self.check()
|
||||
await asyncio.sleep(15)
|
||||
|
||||
except error.Forbidden as e:
|
||||
WARN(str(e))
|
||||
except error.TimedOut as e:
|
||||
WARN(str(e))
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
# os._exit(1)
|
||||
|
||||
|
||||
async def stopalert(self):
|
||||
while 1:
|
||||
if self.event2.is_set():
|
||||
await self.sendmsg('🔴 *Notify server offline\!*')
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
async def main(self):
|
||||
await asyncio.gather(self.stopalert(), self.runbot())
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
if CONF.get('telegram', 'enable'):
|
||||
SUCC('Telegram started!')
|
||||
|
||||
self.conn = connect(
|
||||
host=CONF.get('db', 'host'),
|
||||
user=CONF.get('db', 'user'),
|
||||
password=CONF.get('db', 'password'),
|
||||
database=CONF.get('db', 'name'))
|
||||
self.conn.autocommit = True
|
||||
|
||||
self.bot = Bot(CONF.get('telegram', 'token'))
|
||||
|
||||
asyncio.run(self.main())
|
||||
else:
|
||||
while 1:
|
||||
if self.event.is_set():
|
||||
SUCC('Telegram stopped!')
|
||||
break
|
||||
|
||||
sleep(5)
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
|
@ -0,0 +1,54 @@
|
|||
from threading import Thread
|
||||
from macros import *
|
||||
from config import *
|
||||
from time import sleep
|
||||
import urllib, os, requests
|
||||
|
||||
class UThread(Thread):
|
||||
path = os.path.dirname(__file__)
|
||||
|
||||
def __init__(self):
|
||||
super(UThread, self).__init__()
|
||||
|
||||
|
||||
def run(self):
|
||||
global VERSION
|
||||
|
||||
try:
|
||||
SUCC('Updater started!')
|
||||
while 1:
|
||||
if not CONF.get('updates'):
|
||||
sleep(1)
|
||||
continue
|
||||
|
||||
version = requests.get('https://mirror.bitheaven.ru/main/versions/Bit.ASICmon-a').text
|
||||
|
||||
if version == VERSION:
|
||||
sleep(300)
|
||||
continue
|
||||
|
||||
INFO('Update found! Downloading...')
|
||||
if LINUX:
|
||||
with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_linux") as upd:
|
||||
with open(self.path, "wb+") as f:
|
||||
INFO('Installing update...')
|
||||
f.write(upd.read())
|
||||
INFO('Stopping process...')
|
||||
break
|
||||
elif MACOS:
|
||||
with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_macos") as upd:
|
||||
with open(self.path, "wb+") as f:
|
||||
INFO('Installing update...')
|
||||
f.write(upd.read())
|
||||
INFO('Stopping process...')
|
||||
break
|
||||
elif MUSTDIE:
|
||||
with urllib.request.urlopen("https://mirror.bitheaven.ru/main/archive/Bit.ASICmon-a_mustdie.exe") as upd:
|
||||
with open(self.path, "wb+") as f:
|
||||
INFO('Installing update...')
|
||||
f.write(upd.read())
|
||||
INFO('Stopping process...')
|
||||
break
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
|
@ -0,0 +1,107 @@
|
|||
from flask import Flask, send_file, request
|
||||
from time import sleep
|
||||
from werkzeug.serving import make_server
|
||||
from threading import Thread
|
||||
from mysql.connector import connect, Error
|
||||
from macros import *
|
||||
import os, json, config, requests, logging
|
||||
|
||||
|
||||
class WSThread(Thread):
|
||||
app = Flask(__name__)
|
||||
conn = None
|
||||
|
||||
def __init__(self, event):
|
||||
super(WSThread, self).__init__()
|
||||
self.event = event
|
||||
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.disabled = True
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/index')
|
||||
def index():
|
||||
return '<meta charset="UTF-8"><script defer src="/index.js"></script>'
|
||||
|
||||
|
||||
@app.route('/index.js')
|
||||
def indexjs():
|
||||
return send_file('index.js')
|
||||
|
||||
|
||||
@app.route('/curstatus.json')
|
||||
def curstatus():
|
||||
j = {}
|
||||
j['info'] = {}
|
||||
j['info']['asics'] = {}
|
||||
|
||||
return json.dumps(j), 200, {'ContentType':'application/json'}
|
||||
|
||||
|
||||
def dbinit(self):
|
||||
with self.conn.cursor() as c:
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS `ips` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`ip` VARCHAR(64) NOT NULL,
|
||||
`location` VARCHAR(64) NOT NULL
|
||||
)
|
||||
""")
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS `laststate` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`ip` VARCHAR(64) NOT NULL,
|
||||
`mac` VARCHAR(32) NOT NULL,
|
||||
`type` INT(11) NOT NULL,
|
||||
`location` VARCHAR(64) NOT NULL,
|
||||
`status` VARCHAR(32) NOT NULL,
|
||||
`time` INT(11) NOT NULL
|
||||
)
|
||||
""")
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS `asiclogs` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`ip` VARCHAR(64) NOT NULL,
|
||||
`mac` VARCHAR(64) NOT NULL,
|
||||
`type` VARCHAR(64) NOT NULL,
|
||||
`time` INT(11) NOT NULL,
|
||||
`log` TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
def runweb(self):
|
||||
self.server = make_server('0.0.0.0', CONF.get('port'), self.app)
|
||||
self.server.serve_forever()
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
with connect(host=CONF.get('db', 'host'), user=CONF.get('db', 'user'), password=CONF.get('db', 'password')) as conn:
|
||||
with conn.cursor() as c:
|
||||
c.execute(f"CREATE DATABASE IF NOT EXISTS {CONF.get('db', 'name')}")
|
||||
|
||||
self.conn = connect(
|
||||
host=CONF.get('db', 'host'),
|
||||
user=CONF.get('db', 'user'),
|
||||
password=CONF.get('db', 'password'),
|
||||
database=CONF.get('db', 'name'))
|
||||
self.dbinit()
|
||||
|
||||
web = Thread(target=self.runweb)
|
||||
web.daemon = True
|
||||
web.start()
|
||||
|
||||
SUCC(f"Web interface started at port {CONF.get('port')}!")
|
||||
|
||||
while not self.event.is_set():
|
||||
sleep(1)
|
||||
|
||||
SUCC(f"Web server stopped!")
|
||||
self.server.shutdown()
|
||||
except Exception as e:
|
||||
CRIT(str(e))
|
||||
os._exit(1)
|
Loading…
Reference in New Issue