Skip to content
Snippets Groups Projects
Commit 407d9a88 authored by lejeune quentin's avatar lejeune quentin
Browse files

[IMP] point_of_sale: Add the possibility to the IoT Box to perform a self-flashing


To be able to flash the partition on which the IoT Box is executed,
we must create a third partition and install an OS there to be able
to boot on it and flash the partition of the IoT Box

- Creation of the third partition
- Raspbian download and checksum verification
- Installation of Raspbian on the third partition
- Download the IoT image on the Raspbian partition and check the checksum
- Reboot on the Raspbian partition
- Installation of the IoT image on the second partition
- Reboot on the IoT partition
- Cleaning and deleting the third partition

closes odoo/odoo#43591

Task: 2161955
Signed-off-by: default avatarQuentin Lejeune (qle) <qle@odoo.com>
parent 9ba84dc6
No related branches found
No related tags found
No related merge requests found
......@@ -89,6 +89,33 @@ def check_git_branch():
_logger.error('Could not reach configured server')
_logger.error('A error encountered : %s ' % e)
def check_image():
"""
Check if the current image of IoT Box is up to date
"""
url = 'http://nightly.odoo.com/master/posbox/iotbox/SHA1SUMS.txt'
urllib3.disable_warnings()
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
response = http.request('GET', url)
checkFile = {}
valueActual = ''
for line in response.data.decode().split('\n'):
if line:
value, name = line.split(' ')
checkFile.update({value: name})
if name == 'iotbox-latest.zip':
valueLastest = value
elif name == get_img_name():
valueActual = value
if valueActual == valueLastest:
return False
version = checkFile.get(valueLastest, 'Error').replace('iotboxv', '').replace('.zip', '').split('_')
return {'major': version[0], 'minor': version[1]}
def get_img_name():
major, minor = get_version().split('.')
return 'iotboxv%s_%s.zip' % (major, minor)
def get_ip():
try:
return netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr']
......@@ -113,7 +140,7 @@ def get_token():
return read_file_first_line('token')
def get_version():
return '19.12'
return subprocess.check_output(['cat', '/home/pi/iotbox_version']).decode().rstrip()
def get_wifi_essid():
wifi_options = []
......
......@@ -21,6 +21,7 @@ from odoo.addons.web.controllers import main as web
from odoo.modules.module import get_resource_path
from odoo.addons.hw_drivers.tools import helpers
from odoo.addons.hw_drivers.controllers.driver import iot_devices
from odoo.http import Response
_logger = logging.getLogger(__name__)
......@@ -52,7 +53,10 @@ upgrade_page_template = jinja_env.get_template('upgrade_page.html')
class IoTboxHomepage(web.Home):
def __init__(self):
super(IoTboxHomepage,self).__init__()
self.upgrading = threading.Lock()
self.updating = threading.Lock()
def clean_partition(self):
subprocess.check_call(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; cleanup'])
def get_six_terminal(self):
terminal_id = helpers.read_file_first_line('odoo-six-payment-terminal.conf')
......@@ -289,16 +293,67 @@ class IoTboxHomepage(web.Home):
@http.route('/hw_proxy/upgrade', type='http', auth='none', )
def upgrade(self):
commit = subprocess.check_output(["git", "--work-tree=/home/pi/odoo/", "--git-dir=/home/pi/odoo/.git", "log", "-1"]).decode('utf-8').replace("\n", "<br/>")
flashToVersion = helpers.check_image()
actualVersion = helpers.get_version()
if flashToVersion:
flashToVersion = '%s.%s' % (flashToVersion.get('major', ''), flashToVersion.get('minor', ''))
return upgrade_page_template.render({
'title': "Odoo's IoTBox - Software Upgrade",
'breadcrumb': 'IoT Box Software Upgrade',
'loading_message': 'Updating IoT box',
'commit': commit,
'flashToVersion': flashToVersion,
'actualVersion': actualVersion,
})
@http.route('/hw_proxy/perform_upgrade', type='http', auth='none')
def perform_upgrade(self):
self.upgrading.acquire()
self.updating.acquire()
os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh')
self.upgrading.release()
self.updating.release()
return 'SUCCESS'
@http.route('/hw_proxy/get_version', type='http', auth='none')
def check_version(self):
return helpers.get_version()
@http.route('/hw_proxy/perform_flashing_create_partition', type='http', auth='none')
def perform_flashing_create_partition(self):
try:
response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; create_partition']).decode().split('\n')[-2]
if response == 'Error_Card_Size':
raise Exception(response)
return Response('success', status=200)
except subprocess.CalledProcessError as e:
raise Exception(e.output)
except Exception as e:
_logger.error('A error encountered : %s ' % e)
return Response(str(e), status=500)
@http.route('/hw_proxy/perform_flashing_download_raspbian', type='http', auth='none')
def perform_flashing_download_raspbian(self):
try:
response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; download_raspbian']).decode().split('\n')[-2]
if response == 'Error_Raspbian_Download':
raise Exception(response)
return Response('success', status=200)
except subprocess.CalledProcessError as e:
raise Exception(e.output)
except Exception as e:
self.clean_partition()
_logger.error('A error encountered : %s ' % e)
return Response(str(e), status=500)
@http.route('/hw_proxy/perform_flashing_copy_raspbian', type='http', auth='none')
def perform_flashing_copy_raspbian(self):
try:
response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; copy_raspbian']).decode().split('\n')[-2]
if response == 'Error_Iotbox_Download':
raise Exception(response)
return Response('success', status=200)
except subprocess.CalledProcessError as e:
raise Exception(e.output)
except Exception as e:
self.clean_partition()
_logger.error('A error encountered : %s ' % e)
return Response(str(e), status=500)
......@@ -3,11 +3,11 @@
{% block head %}
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script>
$(function() {
var upgrading = false;
$(async function() {
var updating = false;
$('#upgrade').click(function() {
if (!upgrading) {
upgrading = true;
if (!updating) {
updating = true;
$('.loading-block').removeClass('o_hide');
$.ajax({
url:'/hw_proxy/perform_upgrade/'
......@@ -24,6 +24,38 @@
});
}
});
$('#flash').click(async function() {
if (confirm('Are you sure you want to flash your IoT Box?\nThe box will be unavailable for ~30 min\nDo not turn off the box or close this page during the flash.\nThis page will reaload when your box is ready.')) {
$('.loading-block').removeClass('o_hide');
$('.message-title').text('IoTBox perform a self flashing it take a lot of time (~30min).');
$('.message-status').text('Prepare space for IoTBox.');
try {
await $.ajax({url: '/hw_proxy/perform_flashing_create_partition/'}).promise();
$('.message-status').text('Prepare new boot partition.');
await $.ajax({url: '/hw_proxy/perform_flashing_download_raspbian/'}).promise();
$('.message-status').text('Download file for new boot partition.');
await $.ajax({url: '/hw_proxy/perform_flashing_copy_raspbian/'}).promise();
$('.message-status').text('Prepare to restart and installation of the new version of the IoT Box.');
setTimeout(function() {
$('.message-status').text('The auto flash is almost finished - the page will be automatically reloaded');
setInterval(function() {
$.ajax({
url: '/hw_proxy/get_version',
timeout: 4000,
}).done(function(version) {
if (version === {{ flashToVersion }}) {
window.location = '/';
}
});
} , 2000);
}, 240000);
}
catch(error) {
$('.loading-block').addClass('o_hide');
$('.error-message').text(error.responseText);
}
}
});
});
</script>
<style>
......@@ -53,7 +85,11 @@
<pre style="margin: 0;padding: 15px 0; overflow: auto;">{{ commit|safe }}</pre>
</div>
<div class="text-center" style="margin: 15px auto;">
<a class="btn" href='#' id='upgrade'>Upgrade</a>
{% if flashToVersion %}
<a class="btn" href='#' id='flash'>Upgrade to {{ flashToVersion }}</a>
{% else %}
<a class="btn" href='#' id='upgrade'>Upgrade</a>
{% endif %}
</div>
{{ loading_block_ui(loading_message) }}
{% endblock %}
#!/usr/bin/env bash
file_exists () {
[[ -f $1 ]];
}
create_partition () {
mount -o remount,rw /
echo "Fdisking"
PARTITION=$(lsblk | awk 'NR==2 {print $1}')
PARTITION="/dev/${PARTITION}"
SECTORS_SIZE=$(fdisk -l "${PARTITION}" | awk 'NR==1 {print $7}')
if [ "${SECTORS_SIZE}" -lt 15583488 ] # self-flash not permited if SD size < 16gb
then
echo "Error_Card_Size"
exit 0
fi
PART_ODOO_ROOT=$(fdisk -l | tail -n 1 | awk '{print $1}')
START_OF_ODOO_ROOT_PARTITION=$(fdisk -l | tail -n 1 | awk '{print $2}')
END_OF_ODOO_ROOT_PARTITION=$((START_OF_ODOO_ROOT_PARTITION + 11714061)) # sectors to have a partition of ~5.6Go
START_OF_UPGRADE_ROOT_PARTITION=$((END_OF_ODOO_ROOT_PARTITION + 1)) # sectors to have a partition of ~7.0Go
(echo 'p'; # print
echo 'd'; # delete partition
echo '2'; # number 2
echo 'n'; # create new partition
echo 'p'; # primary
echo '2'; # number 2
echo "${START_OF_ODOO_ROOT_PARTITION}"; # starting at previous offset
echo "${END_OF_ODOO_ROOT_PARTITION}"; # ending at ~9.9Go
echo 'n'; # create new partition
echo 'p'; # primary
echo '3'; # number 3
echo "${START_OF_UPGRADE_ROOT_PARTITION}"; # starting at previous offset
echo ''; # ending at default (fdisk should propose max) ~7.0Go
echo 'p'; # print
echo 'w') |fdisk "${PARTITION}" # write and quit
PART_RASPBIAN_ROOT=$(sudo fdisk -l | tail -n 1 | awk '{print $1}')
sleep 5
# Clean partition
mount -o remount,rw /
partprobe # apply changes to partitions
resize2fs "${PART_ODOO_ROOT}"
mkfs.ext4 -Fv "${PART_RASPBIAN_ROOT}" # change file sytstem
echo "end fdisking"
}
download_raspbian () {
if ! file_exists *raspbian*.img ; then
# download latest Raspbian image and check integrity
LATEST_RASPBIAN=$(curl -LIsw %{url_effective} http://downloads.raspberrypi.org/raspbian_lite_latest | tail -n 1)
wget -c "${LATEST_RASPBIAN}"
RASPBIAN=$(echo *raspbian*.zip)
wget -c "${LATEST_RASPBIAN}".sha256
CHECK=$(sha256sum -c "${RASPBIAN}".sha256)
if [ "${CHECK}" != "${RASPBIAN}: OK" ]
then
# Checksum is not correct so clean and reset self-flashing
mount -o remount,rw /
# Clean raspbian img
rm "${RASPBIAN}" "${RASPBIAN}".sha256
echo "Error_Raspbian_Download"
exit 0
fi
unzip "${RASPBIAN}"
fi
echo "end dowloading raspbian"
}
copy_raspbian () {
umount -v /boot
# mapper raspbian
PART_RASPBIAN_ROOT=$(fdisk -l | tail -n 1 | awk '{print $1}')
PART_ODOO_ROOT=$(fdisk -l | tail -n 2 | awk 'NR==1 {print $1}')
PART_BOOT=$(fdisk -l | tail -n 3 | awk 'NR==1 {print $1}')
RASPBIAN=$(echo *raspbian*.img)
LOOP_RASPBIAN=$(kpartx -avs "${RASPBIAN}")
LOOP_RASPBIAN_ROOT=$(echo "${LOOP_RASPBIAN}" | tail -n 1 | awk '{print $3}')
LOOP_RASPBIAN_ROOT="/dev/mapper/${LOOP_RASPBIAN_ROOT}"
LOOP_BOOT=$(echo "${LOOP_RASPBIAN}" | tail -n 2 | awk 'NR==1 {print $3}')
LOOP_BOOT="/dev/mapper/${LOOP_BOOT}"
mount -o remount,rw /
# copy raspbian
dd if="${LOOP_RASPBIAN_ROOT}" of="${PART_RASPBIAN_ROOT}" bs=4M status=progress
e2fsck -fv "${PART_RASPBIAN_ROOT}" # resize2fs requires clean fs
# Modify startup
mkdir -v raspbian
mount -v "${PART_RASPBIAN_ROOT}" raspbian
resize2fs "${PART_RASPBIAN_ROOT}"
chroot raspbian/ /bin/bash -c "sudo apt-get -y install kpartx"
PATH_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cp -v "${PATH_DIR}"/upgrade.sh raspbian/home/pi/
NBR_LIGNE=$(sed -n -e '$=' raspbian/etc/rc.local)
sed -ie "${NBR_LIGNE}"'i\. /home/pi/upgrade.sh; copy_iot' raspbian/etc/rc.local
cp -v /etc/fstab raspbian/etc/fstab
sed -ie "s/$(echo ${PART_ODOO_ROOT} | sed -e 's/\//\\\//g')/$(echo ${PART_RASPBIAN_ROOT} | sed -e 's/\//\\\//g')/g" raspbian/etc/fstab
mkdir raspbian/home/pi/config
find /home/pi -maxdepth 1 -type f ! -name ".*" -exec cp {} raspbian/home/pi/config/ \;
# download latest IoT Box image and check integrity
wget -c 'http://nightly.odoo.com/master/posbox/iotbox/iotbox-latest.zip' -O raspbian/iotbox-latest.zip
wget -c 'http://nightly.odoo.com/master/posbox/iotbox/SHA1SUMS.txt' -O raspbian/SHA1SUMS.txt
cd raspbian/
CHECK=$(sha1sum -c --ignore-missing SHA1SUMS.txt)
cd ..
umount -v raspbian
if [ "${CHECK}" != "iotbox-latest.zip: OK" ]
then
# Checksum is not correct so clean and reset self-flashing
echo "Error_Iotbox_Download"
exit 0
fi
# copy boot
mkfs.ext4 -Fv "${PART_BOOT}" # format /boot file sytstem
e2fsck -fv "${PART_BOOT}" # clean /boot fs
dd if="${LOOP_BOOT}" of="${PART_BOOT}" bs=4M status=progress
# Modify boot file
mkdir -v boot
mount -v "${PART_BOOT}" boot
PART_IOT_BOOT_ID=$(grep -oP '(?<=root=).*(?=rootfstype)' boot/cmdline.txt)
sed -ie "s/$(echo ${PART_IOT_BOOT_ID} | sed -e 's/\//\\\//g')/$(echo ${PART_RASPBIAN_ROOT} | sed -e 's/\//\\\//g')/g" boot/cmdline.txt
umount -v boot
kpartx -dv "${RASPBIAN}"
rm -v "${RASPBIAN}"
reboot
}
copy_iot () {
mount -o remount,rw /
PART_IOTBOX_ROOT=$(fdisk -l | tail -n 2 | awk 'NR==1 {print $1}')
PART_BOOT=$(fdisk -l | tail -n 3 | awk 'NR==1 {print $1}')
# unzip latest IoT Box image
unzip iotbox-latest.zip
rm -v iotbox-latest.zip
IOTBOX=$(echo *iotbox*.img)
# mapper IoTBox
LOOP_IOTBOX=$(kpartx -avs "${IOTBOX}")
LOOP_IOTBOX_ROOT=$(echo "${LOOP_IOTBOX}" | tail -n 1 | awk '{print $3}')
LOOP_IOTBOX_ROOT="/dev/mapper/${LOOP_IOTBOX_ROOT}"
LOOP_BOOT=$(echo "${LOOP_IOTBOX}" | tail -n 2 | awk 'NR==1 {print $3}')
LOOP_BOOT="/dev/mapper/${LOOP_BOOT}"
umount -v /boot
sleep 5
echo "----------------------------------"
echo "Flash in progress - Please wait..."
echo "----------------------------------"
# copy new IoT Box
dd if="${LOOP_IOTBOX_ROOT}" of="${PART_IOTBOX_ROOT}" bs=4M status=progress
# copy boot of new IoT Box
dd if="${LOOP_BOOT}" of="${PART_BOOT}" bs=4M status=progress
mount -v "${PART_BOOT}" /boot
# Modify boot file
PART_BOOT_ID=$(grep -oP '(?<=root=).*(?=rootfstype)' /boot/cmdline.txt)
sed -ie "s/$(echo ${PART_BOOT_ID} | sed -e 's/\//\\\//g')/$(echo ${PART_IOTBOX_ROOT} | sed -e 's/\//\\\//g')/g" /boot/cmdline.txt
sed -i 's| init=/usr/lib/raspi-config/init_resize.sh||' /boot/cmdline.txt
# Modify startup
mkdir -v odoo
mount -v "${PART_IOTBOX_ROOT}" odoo
cp -v /home/pi/upgrade.sh odoo/home/pi/
NBR_LIGNE=$(sed -n -e '$=' odoo/etc/rc.local)
sed -ie "${NBR_LIGNE}"'i\. /home/pi/upgrade.sh; clean_local' odoo/etc/rc.local
find /home/pi/config -maxdepth 1 -type f ! -name ".*" -exec cp {} odoo/home/pi/ \;
reboot
}
cleanup () {
# clean partitions
PART_RASPBIAN_ROOT=$(fdisk -l | tail -n 1 | awk '{print $1}')
mkfs.ext4 -Fv "${PART_RASPBIAN_ROOT}" # format file sytstem
wipefs -a "${PART_RASPBIAN_ROOT}"
PARTITION=$(echo "${PART_RASPBIAN_ROOT}" | sed 's/..$//')
(echo 'p'; # print
echo 'd'; # delete partition
echo '3'; # number 3
echo 'p'; # print
echo 'w') |fdisk "${PARTITION}" # write and quit
echo "end cleanup"
}
clean_local () {
mount -o remount,rw /
mount -o remount,rw /root_bypass_ramdisks/
cleanup
NBR_LIGNE=$(sed -n -e '$=' /root_bypass_ramdisks/etc/rc.local)
DEL_LIGNE=$((NBR_LIGNE - 1))
sed -i "${DEL_LIGNE}"'d' /root_bypass_ramdisks/etc/rc.local
rm /home/pi/upgrade.sh
mount -o remount,ro /
mount -o remount,ro /root_bypass_ramdisks/
}
......@@ -24,6 +24,7 @@ server {
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_read_timeout 600s;
proxy_pass http://127.0.0.1:8069;
}
......
......@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
set -e
function _start() {
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_escpos,hw_blackbox_be,hw_drivers
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_escpos,hw_blackbox_be,hw_drivers --limit-time-cpu=600 --limit-time-real=1200
}
function _stop() {
......
......@@ -44,6 +44,7 @@ PKGS_TO_INSTALL="
hostapd \
git \
rsync \
kpartx \
swig \
console-data \
lightdm \
......
......@@ -29,6 +29,7 @@ MOUNT_POINT="${__dir}/root_mount"
OVERWRITE_FILES_BEFORE_INIT_DIR="${__dir}/overwrite_before_init"
OVERWRITE_FILES_AFTER_INIT_DIR="${__dir}/overwrite_after_init"
VERSION=13.0
VERSION_IOTBOX=20.01
REPO=https://github.com/odoo/odoo.git
if ! file_exists *raspbian*.img ; then
......@@ -129,6 +130,9 @@ cp -v "${QEMU_ARM_STATIC}" "${MOUNT_POINT}/usr/bin/"
cp -av "${OVERWRITE_FILES_BEFORE_INIT_DIR}"/* "${MOUNT_POINT}"
chroot "${MOUNT_POINT}" /bin/bash -c "sudo /etc/init_posbox_image.sh"
# copy iotbox version
echo "${VERSION_IOTBOX}" > "${MOUNT_POINT}"/home/pi/iotbox_version
# get rid of the git clone
rm -rfv "${CLONE_DIR}"
# and the ngrok usr/bin
......@@ -151,5 +155,7 @@ rm -rfv "${MOUNT_POINT}"
echo "Running zerofree..."
zerofree -v "${LOOP_IOT_ROOT}" || true
sleep 10
kpartx -dv "${LOOP_IOT_PATH}"
kpartx -dv "${LOOP_RASPBIAN_PATH}"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment