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:
parent
f351ff5caf
commit
562147263e
|
@ -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
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
<!--
|
||||
   
|
||||
|
||||
<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>
|
||||
|
||||
<!--
|
||||
   
|
||||
|
||||
<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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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"></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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue