Release v0.4.6:

- Lightning-rod Web interface:
  - Registration improved
  - Configuration management added
  - Factory reset and restore added
- Iotronic login: added information provided by LR to Iotronic (version and MAC)
- NGINX redirects updated
- WSTUN: <defunct> processes cleaning added

Change-Id: Ifaa18cc5a591a40a3ba13ca9074e24a5a2cddf5a
This commit is contained in:
Nicola Peditto 2019-02-08 18:42:42 +01:00
parent f351ff5caf
commit 562147263e
21 changed files with 1547 additions and 92 deletions

View File

@ -80,3 +80,13 @@ Execution:
systemctl start lightning-rod.service
tail -f /var/log/iotronic/lightning-rod.log
Troubleshooting:
~~~~~~~~~~~~~~~~
- **cbor error:** "Connection failed: RuntimeError: could not create serializer for "cbor"
It is a dependency of Autobahn package
**Solution:**
pip3 install cbor

View File

@ -11,7 +11,7 @@ Requirements
::
apt install python3 python3-setuptools python3-pip gdb lsof
apt install python3 python3-setuptools python3-pip gdb lsof libssl-dev
* NodeJS
@ -81,3 +81,13 @@ Execution:
systemctl start lightning-rod.service
tail -f /var/log/iotronic/lightning-rod.log
Troubleshooting:
~~~~~~~~~~~~~~~~
- **cbor error:** "Connection failed: RuntimeError: could not create serializer for "cbor"
It is a dependency of Autobahn package
**Solution:**
pip3 install cbor

View File

@ -17,7 +17,6 @@ __author__ = "Nicola Peditto <n.peditto@gmail.com>"
from datetime import datetime
import json
import os
from oslo_config import cfg
from oslo_log import log as logging
@ -131,7 +130,8 @@ class Board(object):
except Exception as err:
LOG.error("Wrong code: " + str(err))
os._exit(1)
self.status = "first_boot"
# os._exit(1)
def getWampAgent(self, config):
'''This method gets and sets the WAMP Board attributes from the conf file.
@ -153,7 +153,8 @@ class Board(object):
"WAMP Agent configuration is wrong... "
"please check settings.json WAMP configuration... Bye!"
)
os._exit(1)
# os._exit(1)
self.status = "first_boot"
# self.agent_url = str(self.wamp_config['url'])
LOG.info(' - agent: ' + str(self.agent))

View File

@ -0,0 +1,171 @@
# Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
__author__ = "Nicola Peditto <n.peditto@gmail.com>"
from ctypes import byref
from ctypes import c_char
from ctypes import c_char_p
from ctypes import c_int
from ctypes import c_uint
from ctypes import c_void_p
from ctypes import cast
from ctypes import CDLL
from ctypes import CFUNCTYPE
from ctypes import POINTER
from ctypes import sizeof
from ctypes import Structure
from ctypes.util import find_library
import sys
# LIBC imports
libc = CDLL(find_library("c"))
libpam = CDLL(find_library("pam"))
# CALLOC imports
calloc = libc.calloc
calloc.restype = c_void_p
calloc.argtypes = [c_uint, c_uint]
# STRUP imports
strdup = libc.strdup
strdup.argstypes = [c_char_p]
strdup.restype = POINTER(c_char)
# PAM CONSTANTS
PAM_PROMPT_ECHO_OFF = 1
PAM_PROMPT_ECHO_ON = 2
PAM_ERROR_MSG = 3
PAM_TEXT_INFO = 4
PAM_REINITIALIZE_CRED = 0x0008 # libpam requirement
class PamHandle(Structure):
"""pam_handle_t wrapper"""
_fields_ = [("handle", c_void_p)]
def __init__(self):
Structure.__init__(self)
self.handle = 0
class PamMsg(Structure):
"""pam_message structure wrapper"""
_fields_ = [("msg_style", c_int), ("msg", c_char_p), ]
def __repr__(self):
return "<PamMsg %i '%s'>" % (self.msg_style, self.msg)
class PamResp(Structure):
"""pam_response structure wrapper"""
_fields_ = [("resp", c_char_p), ("resp_retcode", c_int), ]
def __repr__(self):
return "<PamResp %i '%s'>" % (self.resp_retcode, self.resp)
conv_func = CFUNCTYPE(
c_int,
c_int,
POINTER(POINTER(PamMsg)),
POINTER(POINTER(PamResp)),
c_void_p
)
class PamWrapper(Structure):
"""pam_conv structure wrapper"""
_fields_ = [("conv", conv_func), ("appdata_ptr", c_void_p)]
pamLib_start = libpam.pam_start
pamLib_start.restype = c_int
pamLib_start.argtypes = [
c_char_p,
c_char_p,
POINTER(PamWrapper),
POINTER(PamHandle)
]
pamLib_authenticate = libpam.pam_authenticate
pamLib_authenticate.restype = c_int
pamLib_authenticate.argtypes = [PamHandle, c_int]
pamLib_setcred = libpam.pam_setcred
pamLib_setcred.restype = c_int
pamLib_setcred.argtypes = [PamHandle, c_int]
pamLib_end = libpam.pam_end
pamLib_end.restype = c_int
pamLib_end.argtypes = [PamHandle, c_int]
def pamAuthentication(
username, password, service='login', encoding='utf-8', resetcred=True):
auth_success = None
if sys.version_info >= (3,):
if isinstance(username, str):
username = username.encode(encoding)
if isinstance(password, str):
password = password.encode(encoding)
if isinstance(service, str):
service = service.encode(encoding)
else:
if isinstance(username, unicode):
username = username.encode(encoding)
if isinstance(password, unicode):
password = password.encode(encoding)
if isinstance(service, unicode):
service = service.encode(encoding)
@conv_func
def prompt_cov_msg(n_messages, messages, p_response, app_data):
"""
Conversation function that responds to any
prompt where the echo is off with the supplied password
"""
addr = calloc(n_messages, sizeof(PamResp))
p_response[0] = cast(addr, POINTER(PamResp))
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
pw_copy = strdup(password)
p_response.contents[i].resp = cast(pw_copy, c_char_p)
p_response.contents[i].resp_retcode = 0
return 0
handle = PamHandle()
conv = PamWrapper(prompt_cov_msg, 0)
pamRes = pamLib_start(service, username, byref(conv), byref(handle))
if pamRes != 0:
print("Error calling PAM module: " + str(pamRes))
return False
pamRes = pamLib_authenticate(handle, 0)
if pamRes == 0:
auth_success = True
else:
auth_success = False
# Re-initialize credentials (e.g. for Kerberos users)
if auth_success and resetcred:
pamRes = pamLib_setcred(handle, PAM_REINITIALIZE_CRED)
pamLib_end(handle, pamRes)
return auth_success

View File

@ -23,8 +23,11 @@ LOG = logging.getLogger(__name__)
import os
import pkg_resources
import psutil
import site
import subprocess
import sys
import threading
import time
def LR_restart():
@ -36,6 +39,16 @@ def LR_restart():
LOG.error("Lightning-rod restarting error" + str(err))
def LR_restart_delayed(seconds):
def delayLRrestarting():
time.sleep(seconds)
python = sys.executable
os.execl(python, python, *sys.argv)
threading.Thread(target=delayLRrestarting).start()
def checkIotronicConf(lr_CONF):
try:
@ -84,3 +97,69 @@ def get_version(package):
package = package.lower()
return next((p.version for p in pkg_resources.working_set if
p.project_name.lower() == package), "No version")
def get_socket_info(wport):
lr_mac = "N/A"
try:
for socks in psutil.Process().connections():
if len(socks.raddr) != 0:
if (socks.raddr.port == wport):
lr_net_iface = socks
print("WAMP SOCKET: " + str(lr_net_iface))
dct = psutil.net_if_addrs()
for key in dct.keys():
if isinstance(dct[key], dict) == False:
iface = key
for elem in dct[key]:
ip_addr = elem.address
if ip_addr == str(
lr_net_iface.laddr.ip):
for snicaddr in dct[iface]:
if snicaddr.family == 17:
lr_mac = snicaddr.address
print(" - Selected NIC: ", iface,
ip_addr,
lr_mac)
return [iface, ip_addr, lr_mac]
except Exception as e:
LOG.warning("Error getting socket info " + str(e))
lr_mac = "N/A"
return lr_mac
return lr_mac
def backupConf():
try:
os.system(
'cp /etc/iotronic/settings.json /etc/iotronic/settings.json.bkp'
)
except Exception as e:
LOG.warning("Error restoring configuration " + str(e))
def restoreConf():
try:
result = os.system(
'cp /etc/iotronic/settings.json.bkp /etc/iotronic/settings.json'
)
except Exception as e:
LOG.warning("Error restoring configuration " + str(e))
result = str(e)
return result
def restoreFactoryConf():
try:
py_dist_pack = site.getsitepackages()[0]
os.system(
'cp ' + py_dist_pack + '/iotronic_lightningrod/'
+ 'templates/settings.example.json '
+ '/etc/iotronic/settings.json'
)
except Exception as e:
LOG.warning("Error restoring configuration " + str(e))

View File

@ -44,6 +44,7 @@ from iotronic_lightningrod.Board import FIRST_BOOT
from iotronic_lightningrod.common.exception import timeoutALIVE
from iotronic_lightningrod.common.exception import timeoutRPC
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.common.utils import get_socket_info
from iotronic_lightningrod.common.utils import get_version
import iotronic_lightningrod.wampmessage as WM
@ -76,8 +77,16 @@ CONF.register_opts(lr_opts)
global SESSION
SESSION = None
global lr_mac
lr_mac = None
global wport
wport = None
global board
board = None
reconnection = False
RPC = {}
RPC_devices = {}
@ -144,6 +153,11 @@ class LightningRod(object):
singleModuleLoader("rest", session=None)
if(board.status == "first_boot"):
os.system("pkill -f 'node /usr/bin/wstun'")
LOG.debug("OLD tunnels cleaned!")
print("OLD tunnels cleaned!")
LOG.info("LR FIRST BOOT: waiting for first configuration...")
while (board.status == "first_boot"):
@ -184,11 +198,15 @@ class WampManager(object):
def start(self):
LOG.info(" - starting Lightning-rod WAMP server...")
try:
if(board.status != "url_wamp_error"):
global loop
loop = asyncio.get_event_loop()
component.start(loop)
loop.run_forever()
global loop
loop = asyncio.get_event_loop()
component.start(loop)
loop.run_forever()
except Exception as err:
LOG.error(" - Error starting asyncio-component: " + str(err))
def stop(self):
LOG.info("Stopping WAMP agent server...")
@ -199,7 +217,10 @@ class WampManager(object):
def iotronic_status(board_status):
if board_status != "first_boot":
if (board_status != "first_boot") \
and (board_status != "already-registered") \
and (board_status != "url_wamp_error"):
# WS ALIVE
try:
alive = asyncio.run_coroutine_threadsafe(
@ -294,7 +315,12 @@ async def IotronicLogin(board, session, details):
res = await session.call(
rpc,
uuid=board.uuid,
session=details.session
session=details.session,
info={
"lr_version": str(get_version("iotronic-lightningrod")),
"mac_addr": str(lr_mac)
}
)
w_msg = WM.deserialize(res)
@ -360,7 +386,10 @@ def wampConnect(wamp_conf):
if wurl_list[0] == "wss":
is_wss = True
whost = wurl_list[1].replace('/', '')
global wport
wport = int(wurl_list[2].replace('/', ''))
if is_wss and CONF.skip_cert_verify:
@ -404,7 +433,14 @@ def wampConnect(wamp_conf):
"""
print("WAMP SOCKET: " + str(psutil.Process().connections()[0]))
global wport
global lr_mac
sock_bundle = get_socket_info(wport)
if sock_bundle == "N/A":
lr_mac = sock_bundle
else:
lr_mac = sock_bundle[2]
global connected
connected = True
@ -441,6 +477,15 @@ def wampConnect(wamp_conf):
print(" - Session ID: " + str(board.session_id))
LOG.info(" - Board status: " + str(board.status))
if sock_bundle == "N/A":
LOG.info(" - Socket info:" + str(sock_bundle))
else:
LOG.info(" - Socket info: %s %s %s",
str(sock_bundle[0]),
str(sock_bundle[1]),
str(sock_bundle[2])
)
if reconnection is False:
if board.uuid is None:
@ -494,9 +539,11 @@ def wampConnect(wamp_conf):
utils.LR_restart()
else:
LOG.error("Registration denied by Iotronic: "
LOG.error("Registration denied by Iotronic - " +
"board already registered: "
+ str(w_msg.message))
Bye()
board.status = "already-registered"
# Bye()
except exception.ApplicationError as e:
LOG.error("IoTronic registration error: " + str(e))
@ -571,7 +618,13 @@ def wampConnect(wamp_conf):
res = await session.call(
rpc,
uuid=board.uuid,
session=details.session
session=details.session,
info={
"lr_version": str(
get_version("iotronic-lightningrod")),
"mac_addr": str(lr_mac)
}
)
w_msg = WM.deserialize(res)
@ -725,9 +778,14 @@ def wampConnect(wamp_conf):
else:
LOG.error("Reconnection wrong status!")
except IndexError as err:
LOG.error(" - Error parsing WAMP url: " + str(err))
LOG.error(" --> port or address not specified")
board.status = "url_wamp_error"
except Exception as err:
LOG.error(" - WAMP connection error: " + str(err))
Bye()
# Bye()
def moduleWampRegister(session, meth_list):

View File

@ -207,10 +207,13 @@ class ProxyManager(Proxy.Proxy):
nginx_path = "/etc/nginx/conf.d/"
nginx_board_conf_file = nginx_path + "/" + board_dns + ".conf"
nginx_board_conf_file = nginx_path + "/lr_" + board_dns + ".conf"
nginx_board_conf = '''server {{
listen 50000;
server_name {0};
location / {{
proxy_pass http://127.0.0.1:1474;
}}
}}
'''.format(board_dns)
@ -233,6 +236,7 @@ class ProxyManager(Proxy.Proxy):
"--email " + owner_email
LOG.debug(command)
certbot_result = call(command, shell=True)
LOG.info("CERTBOT RESULT: " + str(certbot_result))
@ -255,7 +259,7 @@ class ProxyManager(Proxy.Proxy):
nginx_path = "/etc/nginx/conf.d"
service_path = nginx_path + "/" + service_dns + ".conf"
service_path = nginx_path + "/lr_" + service_dns + ".conf"
string = '''server {{
listen 50000;
server_name {0};
@ -293,6 +297,7 @@ class ProxyManager(Proxy.Proxy):
"--tls-sni-01-port 60000 " \
"--domain " + str(dns_list)
"""
command = "/usr/bin/certbot " \
"-n " \
"--redirect " \
@ -301,6 +306,7 @@ class ProxyManager(Proxy.Proxy):
"--cert-name " + str(board_dns) + " " \
"--tls-sni-01-port 60000 " \
"--domain " + str(dns_list)
"""
LOG.debug(command)
certbot_result = call(command, shell=True)
@ -335,7 +341,7 @@ class ProxyManager(Proxy.Proxy):
try:
nginx_path = "/etc/nginx/conf.d"
service_path = nginx_path + "/" + service_dns + ".conf"
service_path = nginx_path + "/lr_" + service_dns + ".conf"
if os.path.exists(service_path):

View File

@ -15,6 +15,10 @@
__author__ = "Nicola Peditto <n.peditto@gmail.com>"
from iotronic_lightningrod.common.pam import pamAuthentication
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.common.utils import get_version
from iotronic_lightningrod.lightningrod import board
from iotronic_lightningrod.lightningrod import iotronic_status
from iotronic_lightningrod.modules import device_manager
@ -27,8 +31,11 @@ from flask import Flask
from flask import redirect
from flask import render_template
from flask import request
from flask import send_file
from flask import session as f_session
from flask import url_for
import getpass
import os
import subprocess
import threading
@ -65,73 +72,488 @@ class RestManager(Module.Module):
static_url_path="/static"
)
app.secret_key = os.urandom(24).hex() # to use flask session
UPLOAD_FOLDER = '/tmp'
ALLOWED_EXTENSIONS = set(['tar.gz', 'gz'])
ALLOWED_STTINGS_EXTENSIONS = set(['json'])
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/')
def home():
return render_template('home.html')
if 'username' in f_session:
return render_template('home.html')
else:
return render_template('login.html')
def redirect_dest(fallback):
dest = request.args.get('next')
try:
dest_url = url_for(dest)
except Exception:
return redirect(fallback)
return redirect(dest_url)
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if pamAuthentication(
str(request.form['username']),
str(request.form['password'])
):
f_session['username'] = request.form['username']
return redirect_dest(fallback="/")
else:
error = 'Invalid Credentials. Please try again.'
if 'username' in f_session:
return render_template('home.html')
else:
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
# remove the username from the session if it's there
f_session.pop('username', None)
return redirect("/login", code=302)
@app.route('/status')
def status():
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
if ('username' in f_session):
f_session['status'] = str(board.status)
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
else:
wstun_status = "Offline"
service_list = service_manager.services_list()
if service_list == "":
service_list = "no services exposed!"
info = {
'board_id': board.uuid,
'board_name': board.name,
'wagent': board.agent,
'session_id': board.session_id,
'timestamp': str(
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')),
'wstun_status': wstun_status,
'board_reg_status': str(board.status),
'iotronic_status': str(iotronic_status(board.status)),
'service_list': str(service_list),
'lr_version': str(get_version("iotronic-lightningrod"))
}
return render_template('status.html', **info)
else:
wstun_status = "Offline"
return redirect(url_for('login', next=request.endpoint))
service_list = service_manager.services_list()
if service_list == "":
service_list = "no services exposed!"
info = {
'board_id': board.uuid,
'board_name': board.name,
'wagent': board.agent,
'session_id': board.session_id,
'timestamp': str(
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')),
'wstun_status': wstun_status,
'board_reg_status': str(board.status),
'iotronic_status': str(iotronic_status(board.status)),
'service_list': str(service_list)
}
return render_template('status.html', **info)
@app.route('/system')
def system():
if 'username' in f_session:
info = {
'board_status': board.status
}
return render_template('system.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/network')
def network():
info = {
'ifconfig': device_manager.getIfconfig().replace('\n', '<br>')
}
return render_template('network.html', **info)
if 'username' in f_session:
info = {
'ifconfig': device_manager.getIfconfig().replace(
'\n', '<br>'
)
}
return render_template('network.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
def lr_config(ragent, code):
bashCommand = "lr_configure %s %s " % (code, ragent)
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
# print(output)
return
def lr_install():
bashCommand = "lr_install"
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
return
def identity_backup():
bashCommand = "device_bkp_rest backup --path /tmp "\
+ "| grep filename: |awk '{print $4}'"
process = subprocess.Popen(bashCommand,
stdout=subprocess.PIPE, shell=True)
output, error = process.communicate()
return output.decode('ascii').strip()
def identity_restore(filepath):
bashCommand = "device_bkp_rest restore " + filepath + "| tail -n 1"
process = subprocess.Popen(bashCommand,
stdout=subprocess.PIPE, shell=True)
output, error = process.communicate()
return output.decode('ascii').strip()
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def allowed_settings(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() \
in ALLOWED_STTINGS_EXTENSIONS
@app.route('/restore', methods=['GET', 'POST'])
def upload_file():
if 'username' in f_session:
f_session['status'] = str(board.status)
if request.form.get('dev_rst_btn') == 'Device restore':
if 'rst_file' not in request.files:
error = 'Identity restore result: No file uploaded!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
file = request.files['rst_file']
if file.filename == '':
error = 'Identity restore result: No filename!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template('config.html', **info,
error=error)
else:
filename = file.filename
print("Identity file uploaded: " + str(filename))
if file and allowed_file(file.filename):
bpath = os.path.join(
app.config['UPLOAD_FOLDER'],
filename
)
file.save(bpath)
out_res = identity_restore(bpath)
print("--> restore result: " + str(out_res))
# restart LR
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
error = 'Identity restore result: ' \
+ 'file extention not allowed!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
return redirect("/config", code=302)
else:
return redirect("/", code=302)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/backup', methods=['GET'])
def backup_download():
if 'username' in f_session:
print("Identity file downloading: ")
filename = identity_backup()
print("--> backup created:" + str(filename))
path = str(filename)
if path is None:
print("Error path None")
try:
print("--> backup file sent.")
return send_file(path, as_attachment=True)
except Exception as e:
print(e)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/factory', methods=['GET'])
def factory_reset():
if 'username' in f_session:
print("Lightning-rod factory reset: ")
f_session['status'] = str(board.status)
# delete nginx conf.d files
os.system("rm /etc/nginx/conf.d/lr_*")
print("--> NGINX settings deleted.")
# delete letsencrypt
os.system("rm -r /etc/letsencrypt")
print("--> LetsEncrypt settings deleted.")
# delete var-iotronic
os.system("rm -r /var/lib/iotronic")
print("--> Iotronic data deleted.")
# delete etc-iotronic
os.system("rm -r /etc/iotronic")
print("--> Iotronic settings deleted.")
# exec lr_install
lr_install()
# restart LR
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/config', methods=['GET', 'POST'])
def config():
if request.method == 'POST':
if ('username' in f_session) or str(board.status) == "first_boot":
ragent = request.form['urlwagent']
code = request.form['code']
lr_config(ragent, code)
return redirect("/status", code=302)
f_session['status'] = str(board.status)
if request.method == 'POST':
if request.form.get('reg_btn') == 'CONFIGURE':
ragent = request.form['urlwagent']
code = request.form['code']
lr_config(ragent, code)
return redirect("/status", code=302)
elif request.form.get('rst_btn') == 'RESTORE':
utils.restoreConf()
print("Restored")
f_session['status'] = "restarting"
return redirect("/", code=302)
elif request.form.get('fct_btn'):
utils.restoreFactoryConf()
print("Refactored")
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
elif request.form.get('rst_settings_btn'):
print("Settings restoring from uploaded backup...")
if len(request.files) != 0:
if 'rst_settings_file' in request.files:
file = request.files['rst_settings_file']
if file.filename == '':
error = 'Settings restore result: ' \
+ 'No filename!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
filename = file.filename
print(" - file uploaded: " + str(filename))
if file and allowed_settings(filename):
bpath = os.path.join(
app.config['UPLOAD_FOLDER'],
filename
)
file.save(bpath)
try:
os.system(
'cp '
+ bpath
+ ' /etc/iotronic/'
+ 'settings.json'
)
except Exception as e:
LOG.warning(
"Error restoring " +
"configuration " + str(e))
print(" - done!")
if board.status == "first_boot":
# start LR
print(" - LR starting "
+ "in 5 seconds...")
f_session['status'] = "starting"
else:
# restart LR
print(" - LR restarting "
+ "in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
error = 'Wrong file extention: ' \
+ str(filename)
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
error = 'input form error!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template('config.html', **info,
error=error)
else:
error = "no settings file specified!"
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
return redirect("/config", code=302)
else:
print("Error POST request")
return redirect("/status", code=302)
else:
if board.status == "first_boot":
urlwagent = request.args.get('urlwagent') or ""
code = request.args.get('code') or ""
info = {
'urlwagent': urlwagent,
'code': code,
'board_status': board.status
}
return render_template('config.html', **info)
else:
if request.args.get('bkp_btn'):
# utils.backupConf()
print("Settings file downloading: ")
path = "/etc/iotronic/settings.json"
if path is None:
print("Error path None")
return redirect("/config", code=500)
try:
fn_download = "settings_" + str(
datetime.now().strftime(
'%Y-%m-%dT%H:%M:%S.%f')) + ".json"
print("--> backup settings file sent.")
return send_file(
path,
as_attachment=True,
attachment_filename=fn_download
)
except Exception as e:
print(e)
return redirect("/config", code=500)
elif request.args.get('rst_btn'):
utils.restoreConf()
print("Restored")
return redirect("/config", code=302)
elif request.args.get('fct_btn'):
utils.restoreFactoryConf()
print("Refactored")
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
elif request.args.get('lr_restart_btn'):
print("LR restarting in 5 seconds...")
f_session['status'] = "restarting"
utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
info = {
'board_status': board.status
}
return render_template('config.html', **info)
else:
if board.status == "first_boot":
urlwagent = request.args.get('urlwagent') or ""
code = request.args.get('code') or ""
info = {
'urlwagent': urlwagent,
'code': code
}
return render_template('config.html', **info)
else:
return redirect("/status", code=302)
return redirect(url_for('login', next=request.endpoint))
app.run(host='0.0.0.0', port=1474, debug=False, use_reloader=False)

View File

@ -68,7 +68,7 @@ s_conf_FILE = CONF.lightningrod_home + "/services.json"
ws_server_alive = 0
global WS_MON_LIST
WS_MON_LIST = {}
global wstun_ip
@ -103,6 +103,19 @@ class ServiceManager(Module.Module):
self.wstun_url = "ws://" + self.wstun_ip + ":" + self.wstun_port
def finalize(self):
# Clean process table and remove zombies
for _ in range(get_zombies()):
try:
os.waitpid(-1, os.WNOHANG)
except Exception as exc:
print(" - [finalize] Error cleaning" +
" wstun zombie process: " + str(exc))
message = "WSTUN zombie processes cleaned."
LOG.debug(message)
print(message)
LOG.info("Cloud service tunnels to initialization:")
# Load services.json configuration file
@ -195,8 +208,22 @@ class ServiceManager(Module.Module):
)
try:
os.kill(service_pid, signal.SIGINT)
print("OLD WSTUN KILLED: " + str(wp))
try:
os.waitpid(-1, os.WNOHANG)
print(" - OLD wstun zombie "
"process cleaned.")
except Exception as exc:
print(
" - [finalize] " +
"Error cleaning old " +
"wstun zombie process: " +
str(exc)
)
LOG.info(
" --> service '" + service_name
+ "' with PID " + str(service_pid)
@ -334,6 +361,7 @@ class ServiceManager(Module.Module):
(p.status() == psutil.STATUS_ZOMBIE)):
print(" - process: " + str(p))
zombie_list.append(p.pid)
except Exception as e:
LOG.error(
" --> PSUTIL [_zombie_hunter]: " +
@ -360,6 +388,12 @@ class ServiceManager(Module.Module):
for s_uuid in s_conf['services']:
# Reload services.json file in order to check
# again if the PID was updated in the mean time
# by another zombie-hunter instance, before starting
# another instance of wstun
s_conf = self._loadServicesConf()
service_pid = s_conf['services'][s_uuid]['pid']
if service_pid in zombie_list:
@ -371,6 +405,7 @@ class ServiceManager(Module.Module):
wstun_found = True
print(s_conf['services'][s_uuid])
service_public_port = \
s_conf['services'][s_uuid]['public_port']
service_local_port = \
@ -380,6 +415,14 @@ class ServiceManager(Module.Module):
try:
# Clean Zombie wstun process
try:
os.waitpid(-1, os.WNOHANG)
print(" - WSTUN zombie process cleaned.")
except Exception as exc:
print(" - [hunter] Error cleaning wstun " +
"zombie process: " + str(exc))
wstun = self._startWstun(
service_public_port,
service_local_port,
@ -387,6 +430,7 @@ class ServiceManager(Module.Module):
)
if wstun != None:
service_pid = wstun.pid
# UPDATE services.json file
@ -408,8 +452,14 @@ class ServiceManager(Module.Module):
+ " restored on port " \
+ str(service_public_port) \
+ " on " + self.wstun_ip
LOG.info(" - " + message
+ " with PID " + str(service_pid))
else:
message = "No need to spawn new tunnel for " \
+ str(service_local_port) + " port"
LOG.debug(message)
print(message)
except Exception:
pass
@ -422,9 +472,18 @@ class ServiceManager(Module.Module):
# LOG.debug("[WSTUN-RESTORE] --> " + str(message))
else:
print("WSTUN kill event:")
message = "Tunnel killed by LR"
print("\nWSTUN kill event:")
message = "Tunnel killed by LR."
print(" - " + str(message))
# Clean zombie processes (no wstun)
try:
os.waitpid(-1, os.WNOHANG)
print(" - Generic zombie process cleaned.")
except Exception as exc:
print(" - [hunter] Error cleaning "
"generic zombie process: " + str(exc))
# LOG.debug("[WSTUN-RESTORE] --> " + str(message))
# lightningrod.zombie_alert = True
@ -604,6 +663,27 @@ class ServiceManager(Module.Module):
local_port)
try:
for p in psutil.process_iter():
if len(p.cmdline()) != 0:
if ((p.name() == "node") and
(str(local_port) in p.cmdline()[2])):
old_tun = p.cmdline()[2]
if old_tun == opt_reverse:
message = "[_startWstun] Tunnel for port " \
+ str(local_port) \
+ " already established!"
print(message)
LOG.warning(message)
return None
except Exception as e:
LOG.error(
" --> PSUTIL [_startWstun]: " +
"error getting wstun processes info: " + str(e)
)
try:
wstun = subprocess.Popen(
[CONF.services.wstun_bin, opt_reverse, self.wstun_url],
stdout=subprocess.PIPE
@ -654,6 +734,26 @@ class ServiceManager(Module.Module):
opt_reverse = "-r" + str(public_port) + ":127.0.0.1:" + str(
local_port)
try:
for p in psutil.process_iter():
if len(p.cmdline()) != 0:
if ((p.name() == "node") and
(str(local_port) in p.cmdline()[2])):
old_tun = p.cmdline()[2]
if old_tun == opt_reverse:
message = "[_startWstunOnBoot] Tunnel for port " \
+ str(local_port) \
+ " already established!"
print(message)
LOG.warning(message)
return None
except Exception as e:
LOG.error(
" --> PSUTIL [_startWstunOnBoot]: " +
"error getting wstun processes info: " + str(e)
)
try:
wstun = subprocess.Popen(
[CONF.services.wstun_bin, opt_reverse, self.wstun_url],
@ -1051,6 +1151,13 @@ class ServiceManager(Module.Module):
return w_msg.serialize()
def get_zombies():
# NOTE: don't use Popen() here
output = os.popen(r"ps aux | grep ' Z' | grep -v grep").read()
nzombies = len(output.splitlines())
return nzombies
def services_list():
try:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,82 @@
body {
color: #999;
background: #f5f5f5;
font-family: 'Varela Round', sans-serif;
}
.form-control {
box-shadow: none;
border-color: #ddd;
}
.form-control:focus {
border-color: #4aba70;
}
.login-form {
width: 350px;
margin: 0 auto;
padding: 30px 0;
}
.login-form form {
color: #434343;
border-radius: 1px;
margin-bottom: 15px;
background: #fff;
border: 1px solid #f3f3f3;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
padding: 30px;
}
.login-form h4 {
text-align: center;
font-size: 22px;
margin-bottom: 20px;
}
.login-form .avatar {
color: #fff;
margin: 0 auto 30px;
text-align: center;
width: 100px;
height: 100px;
border-radius: 50%;
z-index: 9;
background: #4aba70;
padding: 15px;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1);
}
.login-form .avatar i {
font-size: 62px;
}
.login-form .form-group {
margin-bottom: 20px;
}
.login-form .form-control, .login-form .btn {
min-height: 40px;
border-radius: 2px;
transition: all 0.5s;
}
.login-form .close {
position: absolute;
top: 15px;
right: 15px;
}
.login-form .btn {
background: #4aba70;
border: none;
line-height: normal;
}
.login-form .btn:hover, .login-form .btn:focus {
background: #42ae68;
}
.login-form .checkbox-inline {
float: left;
}
.login-form input[type="checkbox"] {
margin-top: 2px;
}
.login-form .forgot-link {
float: right;
}
.login-form .small {
font-size: 13px;
}
.login-form a {
color: #4aba70;
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,15 @@
<!doctype html>
<title>{% block title %}{% endblock %} - LR</title>
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container">
@ -10,8 +18,10 @@
<li class="nav-item">
<a class="navbar-brand" href="/">Home</a>
<a class="navbar-brand" href="/status">Status</a>
<a class="navbar-brand" href="/system">System</a>
<a class="navbar-brand" href="/network">Network</a>
<a class="navbar-brand" href="/config">Configuration</a>
<a class="navbar-brand" href="/logout">Logout</a>
</li>
</ul>
</nav>

View File

@ -1,19 +1,324 @@
{% extends 'base.html' %}
{% block header %}
<h3>{% block title %}First configuration{% endblock %}</h3>
<h3>
{% block title %}
{% if session['status'] == 'first_boot' %}
First configuration
{% else %}
Configuration
{% endif %}
{% endblock %}
</h3>
<script type=text/javascript>
$(function(){
$('.rst_settings_in').on('change',function(){
var fileName = $(this).val().replace(/\\/g, '/').replace(/.*\//, '');
$('.rst_settings_lb').html(fileName);
});
});
$(function(){
$('.dev_rst_in').on('change',function(){
var fileName = $(this).val().replace(/\\/g, '/').replace(/.*\//, '');
$('.dev_rst_lb').html(fileName);
});
});
/*
$(function(){
document.getElementById('settingsInputDiv').style.display ='none';
});
$(function(){
$('.optRemote').click(function() {
document.getElementById('settingsInputDiv').style.display = 'block';
});
});
$(function(){
$('.optLocal').click(function() {
document.getElementById('settingsInputDiv').style.display = 'none';
});
});
*/
</script>
{% endblock %}
{% block content %}
<div class="form-group">
<form method="post">
<!-- -->
<center>
<br>
<h5> Lightning-rod status: {{ board_status }}</h5>
</center>
{% if error %}
<br>
<h4><center>
<i class="material-icons" style="font-size:48px;color:red">warning</i>
<p class="error"><strong>Error:</strong> {{ error }}
</center></h4>
{% endif %}
<br><br>
{% if session['status'] == 'first_boot' %}
<div class="form-group">
<form method="post">
<div class="form-group">
<label for="urlwagent">Registration Agent: </label>
<input class="form-control" name="urlwagent" id="urlwagent" value="{{ urlwagent }}" required></div>
<div class="form-group"></div>
<label for="code">Registration Code:</label>
<input class="form-control" name="code" id="code" value="{{ code }}" required></div>
<input class="btn btn-success" type="submit" value="CONFIGURE">
</form>
<label for="urlwagent">Registration Agent URL:</label>
<input class="form-control" name="urlwagent" id="urlwagent" value="{{ urlwagent }}" required placeholder="ws(s)://<WAGENT-ADDRESS>:<WAGENT-PORT>">
</div>
<div class="form-group">
<label for="code">Registration Code:</label>
<input class="form-control" name="code" id="code" value="{{ code }}" required>
</div>
<input class="btn btn-success" type="submit" value="CONFIGURE" name="reg_btn">
</form>
</div>
<br><br>
<div class="d-flex">
<hr class="my-auto flex-grow-1">
<div class="px-4"> <h5> Settings management </h5></div>
<hr class="my-auto flex-grow-1">
</div>
<br>
<table class="table table-hover">
<div class="form-group" align="center">
<form method="POST" action="/config">
<tr>
<td style="width:30%"> <input class="btn btn-warning btn-block" type="submit" value="Factory settings" name="fct_btn"> </td>
<td> Reload settings.json factory template. </td>
</tr>
</form>
</div>
<form method="POST" action="/config" enctype=multipart/form-data>
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Restore settings" name="rst_settings_btn"> </td>
<td>
<div class="input-group">
<div class="custom-file" id="settingsInputDivFirst">
<input type="file" class="custom-file-input rst_settings_in" id="rst_settings_in_first" aria-describedby="inputGroupFileAddon01" name="rst_settings_file">
<label class="custom-file-label rst_settings_lb" id="rst_settings_lb_first" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" for="rst_settings_in_first">Select settings file...</label>
</div>
<!--
&nbsp &nbsp
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary active optLocal">
<input type="radio" name="options" id="option1" autocomplete="off" value="local" checked> Local
</label>
<label class="btn btn-secondary optRemote">
<input type="radio" name="options" id="option2" autocomplete="off" value="remote"> Remote
</label>
</div>
-->
</div>
</td>
</tr>
</form>
<form method="POST" action="/restore" enctype=multipart/form-data>
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Device restore" name="dev_rst_btn"> </td>
<td>
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input dev_rst_in" id="dev_rst_in_first" aria-describedby="inputGroupFileAddon01" name="rst_file">
<label class="custom-file-label dev_rst_lb" id="dev_rst_lb_first" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" for="dev_rst_in_first">Select backup file...</label>
</div>
</div>
</td>
</tr>
</form>
</table>
{% else %}
<br>
<div class="d-flex">
<hr class="my-auto flex-grow-1">
<div class="px-4"> <h5> Settings management </h5></div>
<hr class="my-auto flex-grow-1">
</div>
<br>
<div class="form-group" align="center">
<table class="table table-hover">
<form method="GET" action="/config">
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Backup settings" name="bkp_btn" id="bkp_btn"> </td>
<td> Backup settings.json file. </td>
</tr>
</form>
<form method="POST" action="/config" enctype=multipart/form-data>
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Restore settings" name="rst_settings_btn"> </td>
<td>
<div class="input-group">
<div class="custom-file" id="settingsInputDiv">
<input type="file" class="custom-file-input rst_settings_in" id="rst_settings_in" aria-describedby="inputGroupFileAddon01" name="rst_settings_file">
<label class="custom-file-label rst_settings_lb" id="rst_settings_lb" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" for="rst_settings_in">Select settings file...</label>
</div>
<!--
&nbsp &nbsp
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary active optLocal" id="optLocal">
<input type="radio" name="options" autocomplete="off" value="local" checked> Local
</label>
<label class="btn btn-secondary optRemote" id="optRemote">
<input type="radio" name="options" autocomplete="off" value="remote"> Remote
</label>
</div>
-->
</div>
</td>
</tr>
</form>
<form method="get" action="/config">
<tr>
<td style="width:30%"> <input class="btn btn-danger btn-block" type="submit" value="Factory settings" name="fct_btn"> </td>
<td> Load settings.json template. </td>
</tr>
</form>
</table>
</div>
<br><br>
<div class="d-flex">
<hr class="my-auto flex-grow-1">
<div class="px-4"> <h5> Identity management </h5></div>
<hr class="my-auto flex-grow-1">
</div>
<br>
<div class="form-group" align="center">
<table class="table table-hover">
<form method="GET" action="/backup">
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Device backup" name="dev_bkp_btn"> </td>
<td> Backup device identity. </td>
</tr>
</form>
<form method="POST" action="/restore" enctype=multipart/form-data>
<tr>
<td style="width:30%"> <input class="btn btn-success btn-block" type="submit" value="Device restore" name="dev_rst_btn"> </td>
<td>
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input dev_rst_in" id="dev_rst_in" aria-describedby="inputGroupFileAddon01" name="rst_file">
<label class="custom-file-label dev_rst_lb" id="dev_rst_lb" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" for="dev_rst_in">Select backup file...</label>
</div>
</div>
</td>
</tr>
</form>
<form method="GET" action="/factory">
<tr>
<td style="width:30%"> <input class="btn btn-danger btn-block" type="submit" value="Factory reset" name="dev_fct_btn"> </td>
<td> Reset device to factory setup: wipe all data!!! </td>
</tr>
</form>
</table>
</div>
{% endif %}
{% endblock %}

View File

@ -4,6 +4,38 @@
<h1>{% block title %}Welcome in Lightning-rod{% endblock %}</h1>
{% endblock %}
{% block content %}
{% endblock %}
{% if session['status'] == 'restarting' %}
<head>
<meta http-equiv="refresh" content="10;url=/status" />
</head>
<br>
<div align="center">
Waiting for restarting in 10 seconds...
</div>
{% elif session['status'] == 'starting' %}
<head>
<meta http-equiv="refresh" content="10;url=/status" />
</head>
<br>
<div align="center">
Waiting for starting in 10 seconds...
</div>
{% else %}
<div align="center"> </div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,48 @@
<!doctype html>
<title>LR login</title>
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
</head>
{% block content %}
<div class="login-form">
<form action="{{ url_for('login', next=request.args.get('next')) }}" method="post">
<div class="avatar"><i class="material-icons">&#xE7FF;</i></div>
<h4 class="modal-title">Login to Lightning-rod</h4>
<div class="form-group">
<input type="text" class="form-control" name="username" placeholder="Username" required="required" value="{{request.form.username }}">
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" placeholder="Password" required="required" value="{{request.form.password }}">
</div>
<input type="submit" class="btn btn-primary btn-block btn-lg" value="Login">
</form>
{% if error %}
<p class="error"><strong>Error:</strong> {{ error }}
{% endif %}
</div>
{% endblock %}

View File

@ -9,6 +9,8 @@
<h2>Board info @ {{ timestamp }}</h2>
<br>
<h4> Iotronic </h4>
<li> Lightning-rod version: {{lr_version}}</li>
<li> Iotronic connection status: {{iotronic_status}}</li>
<li>Name: {{ board_name }} </li>
<li>UUID: {{ board_id }} </li>

View File

@ -0,0 +1,97 @@
{% extends 'base.html' %}
{% block header %}
<h3>
{% block title %}
System
{% endblock %}
</h3>
{% endblock %}
{% block content %}
<center>
<br>
<h5> Lightning-rod status: {{ board_status }}</h5>
</center>
<br><br>
<br><br>
<div class="d-flex">
<hr class="my-auto flex-grow-1">
<div class="px-4"> <h5> Actions </h5></div>
<hr class="my-auto flex-grow-1">
</div>
<br>
<div class="form-group" align="center">
<table class="table table-hover">
<form method="get" action="/config">
<tr>
<td style="width:30%"> <input class="btn btn-primary btn-block" type="submit" value="LR restart" name="lr_restart_btn"> </td>
<td> Restart Lightning-rod. </td>
</tr>
</form>
</table>
</div>
<!--
<link href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://getbootstrap.com/2.3.2/assets/js/bootstrap.js"></script>
<div id="confirm" class="modal hide fade">
<div class="modal-body">
Are you sure?
</div>
<div class="modal-footer">
<form method="get" action="/config">
<input type="button" data-dismiss="modal" class="btn btn-primary" id="delete" type="submit" value="LR restart" name="lr_restart_btn">
<input type="button" data-dismiss="modal" class="btn" value="Cancel">
</form>
</div>
</div>
<div class="form-group" align="center">
<table class="table table-hover">
<tr>
<td style="width:30%"> <input data-toggle="modal" data-target="#confirm" class="btn btn-primary btn-block btn-xs" value="LR restart"> </td>
<td> Restart Lightning-rod. </td>
</tr>
</table>
</div>
-->
{% endblock %}

View File

@ -1,20 +1,27 @@
#!/bin/bash
echo "Option chosen: "$1 $2
echo "Option chosen: "$1
if [ "$1" = "backup" ]; then
if [ "$#" -ne 1 ]; then
echo "You have to specify: 'restore' <BACKUP_FILE> "
exit
fi
# BACKUP
echo "Backing up Iotronic configuration"
now_date=`date '+%Y%m%d%H%M%S'`
device=`cat /etc/iotronic/settings.json | grep name | awk '{print $2}' | tr -d \" | tr -d ,`
bkp_filename="bkp_"$device"_"$now_date".tar.gz"
echo "-> backup filename: " $bkp_filename
tar zcvf $bkp_filename /var/lib/iotronic /etc/iotronic /etc/letsencrypt /etc/nginx > /dev/null
if [ "$2" = "--path" ]; then
bkp_path=$3
else
bkp_path=""
fi
# BACKUP
echo "Backing up Iotronic configuration"
now_date=`date '+%Y%m%d%H%M%S'`
device=`cat /etc/iotronic/settings.json | grep name | awk '{print $2}' | tr -d \" | tr -d ,`
bkp_filename=$bkp_path"/bkp_"$device"_"$now_date".tar.gz"
echo "-> backup filename: " $bkp_filename
tar zcvf $bkp_filename /var/lib/iotronic /etc/iotronic /etc/letsencrypt /etc/nginx/conf.d &>/dev/null
elif [ "$1" = "restore" ]; then
if [ "$#" -ne 2 ]; then

View File

@ -1,7 +1,7 @@
FROM ubuntu:bionic
RUN apt-get update && apt-get install -y \
socat dsniff git ntpdate python build-essential vim lsof gdb screen python3 python3-setuptools python3-pip npm
socat dsniff git ntpdate python build-essential vim lsof gdb screen python3 python3-setuptools python3-pip npm net-tools
ENV TZ 'Europe/Rome'
ENV DEBIAN_FRONTEND=noninteractive

View File

@ -1,7 +1,7 @@
FROM ubuntu:bionic
RUN apt-get update && apt-get install -y \
socat dsniff git ntpdate python build-essential vim lsof gdb screen python3 python3-setuptools python3-pip npm
socat dsniff git ntpdate python build-essential vim lsof gdb screen python3 python3-setuptools python3-pip npm net-tools
ENV TZ 'Europe/Rome'
ENV DEBIAN_FRONTEND=noninteractive