diff --git a/.github/workflows/custopize.yml b/.github/workflows/custopize.yml index 2465784..21f3ba4 100644 --- a/.github/workflows/custopize.yml +++ b/.github/workflows/custopize.yml @@ -41,12 +41,14 @@ jobs: uses: pozetroninc/github-action-get-latest-release@master with: repository: guysoft/OctoPi + excludes: prerelease, draft - name: "🔎 Determine SimplyPrint version" id: simplyprint_version uses: pozetroninc/github-action-get-latest-release@master with: repository: SimplyPrint/OctoPrint-SimplyPrint + excludes: prerelease, draft - name: "⬇ Download latest OctoPi" id: octopi_download diff --git a/scripts/03-install-simplyprint b/scripts/03-install-simplyprint index c3daf2c..028129b 100644 --- a/scripts/03-install-simplyprint +++ b/scripts/03-install-simplyprint @@ -8,7 +8,7 @@ install_cleanup_trap plugins=( # add quoted URLs for install archives, separated by newlines, e.g.: - "https://github.com/SimplyPrint/OctoPrint-SimplyPrint/archive/refs/heads/master.zip" + "https://github.com/SimplyPrint/OctoPrint-SimplyPrint/archive/refs/tags/3.1.2rc9.zip" "https://github.com/eyal0/OctoPrint-PrintTimeGenius/archive/master.zip" ) diff --git a/scripts/06-install-raspiwifi b/scripts/06-install-raspiwifi new file mode 100644 index 0000000..9765511 --- /dev/null +++ b/scripts/06-install-raspiwifi @@ -0,0 +1,23 @@ +set -x +set -e + +export LC_ALL=C + +source /common.sh +install_cleanup_trap + +apt install python3 python3-pip dnsmasq hostapd -y +pip3 install flask cryptography==36.0.0 pyopenssl +cp -a /files/root/* / +mv /etc/wpa_supplicant/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf.original +mv /etc/dnsmasq.conf /etc/dnsmasq.conf.original +cp /usr/lib/raspiwifi/reset_device/static_files/dnsmasq.conf /etc/ +cp /usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.wpa /etc/hostapd/hostapd.conf +mv /etc/dhcpcd.conf /etc/dhcpcd.conf.original +cp /usr/lib/raspiwifi/reset_device/static_files/dhcpcd.conf /etc/ +mkdir /etc/cron.raspiwifi +cp /usr/lib/raspiwifi/reset_device/static_files/aphost_bootstrapper /etc/cron.raspiwifi +chmod +x /etc/cron.raspiwifi/aphost_bootstrapper +echo "# RaspiWiFi Startup" >> /etc/crontab +echo "@reboot root run-parts /etc/cron.raspiwifi/" >> /etc/crontab +touch /etc/raspiwifi/host_mode diff --git a/scripts/files/root/etc/raspiwifi/raspiwifi.conf b/scripts/files/root/etc/raspiwifi/raspiwifi.conf new file mode 100644 index 0000000..f598d24 --- /dev/null +++ b/scripts/files/root/etc/raspiwifi/raspiwifi.conf @@ -0,0 +1,7 @@ +ssid_prefix=SimplyPi +auto_config=1 +auto_config_delay=300 +ssl_enabled=0 +server_port=8000 +wpa_enabled=0 +wpa_key= \ No newline at end of file diff --git a/scripts/files/root/usr/lib/raspiwifi/configuration_app/app.py b/scripts/files/root/usr/lib/raspiwifi/configuration_app/app.py new file mode 100644 index 0000000..31c17b0 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/configuration_app/app.py @@ -0,0 +1,103 @@ +from flask import Flask, request, jsonify +import subprocess +import os +import time +from threading import Thread +import fileinput + +app = Flask(__name__) +app.debug = True + + +@app.route('/') +def index(): + wifi_ap_array = scan_wifi_networks() + config_hash = config_file_hash() + + # return render_template('app.html', wifi_ap_array = wifi_ap_array, config_hash = config_hash) + return jsonify({'wifi_ap_array': wifi_ap_array, 'config_hash': config_hash}) + + +@app.route('/save_credentials', methods = ['POST']) +def save_credentials(): + data = request.json + ssid = data['ssid'] + wifi_key = data['wifi_key'] + + create_wpa_supplicant(ssid, wifi_key) + + # Call set_ap_client_mode() in a thread otherwise the reboot will prevent + # the response from getting to the browser + def sleep_and_start_ap(): + time.sleep(2) + set_ap_client_mode() + t = Thread(target=sleep_and_start_ap) + t.start() + + # return render_template('save_credentials.html', ssid = ssid) + return jsonify({'success': True, 'ssid': ssid}) + + +######## FUNCTIONS ########## + +def scan_wifi_networks(): + iwlist_raw = subprocess.Popen(['iwlist', 'scan'], stdout=subprocess.PIPE) + ap_list, err = iwlist_raw.communicate() + ap_array = [] + + for line in ap_list.decode('utf-8').rsplit('\n'): + if 'ESSID' in line: + ap_ssid = line[27:-1] + if ap_ssid != '': + ap_array.append(ap_ssid) + + return ap_array + +def create_wpa_supplicant(ssid, wifi_key): + temp_conf_file = open('wpa_supplicant.conf.tmp', 'w') + + temp_conf_file.write('ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n') + temp_conf_file.write('update_config=1\n') + temp_conf_file.write('\n') + temp_conf_file.write('network={\n') + temp_conf_file.write(' ssid="' + ssid + '"\n') + + if wifi_key == '': + temp_conf_file.write(' key_mgmt=NONE\n') + else: + temp_conf_file.write(' psk="' + wifi_key + '"\n') + + temp_conf_file.write(' }') + + temp_conf_file.close + + os.system('mv wpa_supplicant.conf.tmp /etc/wpa_supplicant/wpa_supplicant.conf') + +def set_ap_client_mode(): + os.system('rm -f /etc/raspiwifi/host_mode') + os.system('rm /etc/cron.raspiwifi/aphost_bootstrapper') + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/apclient_bootstrapper /etc/cron.raspiwifi/') + os.system('chmod +x /etc/cron.raspiwifi/apclient_bootstrapper') + os.system('mv /etc/dnsmasq.conf.original /etc/dnsmasq.conf') + os.system('mv /etc/dhcpcd.conf.original /etc/dhcpcd.conf') + os.system('reboot') + +def config_file_hash(): + config_file = open('/etc/raspiwifi/raspiwifi.conf') + config_hash = {} + + for line in config_file: + line_key = line.split("=")[0] + line_value = line.split("=")[1].rstrip() + config_hash[line_key] = line_value + + return config_hash + + +if __name__ == '__main__': + config_hash = config_file_hash() + + if config_hash['ssl_enabled'] == "1": + app.run(host = '0.0.0.0', port = int(config_hash['server_port']), ssl_context='adhoc') + else: + app.run(host = '0.0.0.0', port = int(config_hash['server_port'])) diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/__pycache__/reset_lib.cpython-37.pyc b/scripts/files/root/usr/lib/raspiwifi/reset_device/__pycache__/reset_lib.cpython-37.pyc new file mode 100644 index 0000000..fd10f34 Binary files /dev/null and b/scripts/files/root/usr/lib/raspiwifi/reset_device/__pycache__/reset_lib.cpython-37.pyc differ diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/button_chime.wav b/scripts/files/root/usr/lib/raspiwifi/reset_device/button_chime.wav new file mode 100644 index 0000000..7123fe1 Binary files /dev/null and b/scripts/files/root/usr/lib/raspiwifi/reset_device/button_chime.wav differ diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/connection_monitor.py b/scripts/files/root/usr/lib/raspiwifi/reset_device/connection_monitor.py new file mode 100644 index 0000000..b8ca8a8 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/connection_monitor.py @@ -0,0 +1,45 @@ +import time +import sys +import os +import reset_lib +import requests + +no_conn_counter = 0 +consecutive_active_reports = 0 +config_hash = reset_lib.config_file_hash() + +# If auto_config is set to 0 in /etc/raspiwifi/raspiwifi.conf exit this script +if config_hash['auto_config'] == "0": + sys.exit() +else: + # Main connection monitoring loop at 10 second interval + while True: + time.sleep(10) + + # If iwconfig report no association with an AP add 10 to the "No + # Connection Couter" + if reset_lib.is_wifi_active() == False: + no_conn_counter += 10 + consecutive_active_reports = 0 + # If iwconfig report association with an AP add 1 to the + # consecutive_active_reports counter and 10 to the no_conn_counter + else: + consecutive_active_reports += 1 + no_conn_counter += 10 + # Since wpa_supplicant seems to breifly associate with an AP for + # 6-8 seconds to check the network key the below will reset the + # no_conn_counter to 0 only if two 10 second checks have come up active. + if consecutive_active_reports >= 2: + no_conn_counter = 0 + consecutive_active_reports = 0 + + # If the number of seconds not associated with an AP is greater or + # equal to the auto_config_delay specified in the /etc/raspiwifi/raspiwifi.conf + # trigger a reset into AP Host (Configuration) mode. + if no_conn_counter >= int(config_hash['auto_config_delay']): + check_reboot = requests.get("http://localhost:5000/plugin/SimplyPrint/can_reboot") + if check_reboot.json().get("can_reboot", False) is True: + reset_lib.reset_to_host_mode() + else: + no_conn_counter = 0 + diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/manual_reset.py b/scripts/files/root/usr/lib/raspiwifi/reset_device/manual_reset.py new file mode 100644 index 0000000..9a1c570 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/manual_reset.py @@ -0,0 +1,3 @@ +import reset_lib + +reset_lib.reset_to_host_mode() diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/reset.py b/scripts/files/root/usr/lib/raspiwifi/reset_device/reset.py new file mode 100644 index 0000000..7926184 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/reset.py @@ -0,0 +1,49 @@ +import os +import re +import subprocess +import reset_lib + +pi_map = {'Raspberry Pi Zero 2 W Rev 1.0': 'PiZero2', + 'Raspberry Pi 3 Model B Rev 1.2': 'Pi3B', + 'Raspberry Pi 3 Model B Plus Rev 1.3': 'Pi3B+', + 'Raspberry Pi 4 Model B Rev 1.2': 'Pi4B', + 'Raspberry Pi 4 Model B Rev 1.1': 'Pi4B', + 'Raspberry Pi 4 Model B Rev 1.4': 'Pi4B', + 'Raspberry Pi 2 Model B Rev 1.1': 'Pi2B', + 'Raspberry Pi Zero W Rev 1.1': 'PiZero', + 'Raspberry Pi Zero 2 Rev 1.0': 'PiZero2', + 'Raspberry Pi 3 Model A Plus Rev 1.0': 'Pi3A', + 'Raspberry Pi Model B Rev 2': 'Pi1B', + 'Raspberry Pi Model B Plus Rev 1.2': 'Pi1B+', + 'Raspberry Pi 400 Rev 1.0': 'Pi400', + 'Hardkernel Odroid XU4': 'OdroidXU4', + 'Raspberry Pi Compute Module 3 Plus Rev 1.0': 'PiCompute3+', + 'Xunlong Orange Pi Zero': 'OrangePiZero', + 'Xunlong Orange Pi PC': 'OrangePiPC' + } + +counter = 0 +cpuinfo = subprocess.check_output(['cat', '/proc/cpuinfo']).decode('utf-8') + +serial_match = re.search(r"Serial\s+:\s(\S+)$", cpuinfo, re.MULTILINE) +if serial_match: + serial_last_four = serial_match.group(1)[-4:] +else: + serial_last_four = '0000' + +model_info_match = re.search(r"Model\s+:\s(.+)$", cpuinfo, re.MULTILINE) +if model_info_match: + model_info = pi_map[model_info_match.group(1).strip()] or '' +else: + model_info = '' + +config_hash = reset_lib.config_file_hash() +ssid_prefix = config_hash['ssid_prefix'].strip() +reboot_required = False + +reboot_required = reset_lib.wpa_check_activate(config_hash['wpa_enabled'], config_hash['wpa_key']) + +reboot_required = reset_lib.update_ssid(ssid_prefix, serial_last_four, model_info) + +if reboot_required: + os.system('reboot') diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/reset_lib.py b/scripts/files/root/usr/lib/raspiwifi/reset_device/reset_lib.py new file mode 100644 index 0000000..ca88f26 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/reset_lib.py @@ -0,0 +1,92 @@ +import os +import fileinput +import subprocess + +def config_file_hash(): + config_file = open('/etc/raspiwifi/raspiwifi.conf') + config_hash = {} + + for line in config_file: + line_key = line.split("=")[0] + line_value = line.split("=")[1].rstrip() + config_hash[line_key] = line_value + + return config_hash + +def wpa_check_activate(wpa_enabled, wpa_key): + wpa_active = False + reboot_required = False + + with open('/etc/hostapd/hostapd.conf') as hostapd_conf: + for line in hostapd_conf: + if 'wpa_passphrase' in line: + wpa_active = True + + if wpa_enabled == '1' and wpa_active == False: + reboot_required = True + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.wpa /etc/hostapd/hostapd.conf') + + if wpa_enabled == '1': + with fileinput.FileInput('/etc/hostapd/hostapd.conf', inplace=True) as hostapd_conf: + for line in hostapd_conf: + if 'wpa_passphrase' in line: + if 'wpa_passphrase=' + wpa_key not in line: + print('wpa_passphrase=' + wpa_key) + os.system('reboot') + else: + print(line, end = '') + else: + print(line, end = '') + + if wpa_enabled == '0' and wpa_active == True: + reboot_required = True + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.nowpa /etc/hostapd/hostapd.conf') + + return reboot_required + +def update_ssid(ssid_prefix, serial_last_four, model_info): + reboot_required = False + ssid_correct = False + + with open('/etc/hostapd/hostapd.conf') as hostapd_conf: + for line in hostapd_conf: + if ssid_prefix in line: + ssid_correct = True + + if ssid_correct == False: + with fileinput.FileInput("/etc/hostapd/hostapd.conf", inplace=True) as file: + for line in file: + if 'ssid=' in line: + line_array = line.split('=') + line_array[1] = ssid_prefix + '-' + serial_last_four + ' ' + model_info.strip() + print(line_array[0] + '=' + line_array[1]) + else: + print(line, end = '') + + reboot_required = True + + return reboot_required + +def is_wifi_active(): + iwconfig_out = subprocess.check_output(['iwconfig']).decode('utf-8') + wifi_active = True + + if "Access Point: Not-Associated" in iwconfig_out: + wifi_active = False + + return wifi_active + +def reset_to_host_mode(): + if not os.path.isfile('/etc/raspiwifi/host_mode'): + os.system('aplay /usr/lib/raspiwifi/reset_device/button_chime.wav') + os.system('rm -f /etc/wpa_supplicant/wpa_supplicant.conf') + os.system('rm -f /home/pi/Projects/RaspiWifi/tmp/*') + os.system('rm /etc/cron.raspiwifi/apclient_bootstrapper') + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/aphost_bootstrapper /etc/cron.raspiwifi/') + os.system('chmod +x /etc/cron.raspiwifi/aphost_bootstrapper') + os.system('mv /etc/dhcpcd.conf /etc/dhcpcd.conf.original') + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/dhcpcd.conf /etc/') + os.system('mv /etc/dnsmasq.conf /etc/dnsmasq.conf.original') + os.system('cp /usr/lib/raspiwifi/reset_device/static_files/dnsmasq.conf /etc/') + os.system('touch /etc/raspiwifi/host_mode') + os.system('reboot') diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/apclient_bootstrapper b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/apclient_bootstrapper new file mode 100644 index 0000000..51a9e98 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/apclient_bootstrapper @@ -0,0 +1,5 @@ +#!/bin/bash + +python3 /usr/lib/raspiwifi/reset_device/reset.py & + +python3 /usr/lib/raspiwifi/reset_device/connection_monitor.py & diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/aphost_bootstrapper b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/aphost_bootstrapper new file mode 100644 index 0000000..6911a35 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/aphost_bootstrapper @@ -0,0 +1,7 @@ +#!/bin/bash + +python3 /usr/lib/raspiwifi/reset_device/reset.py & + +python3 /usr/lib/raspiwifi/configuration_app/app.py & + +hostapd -dd /etc/hostapd/hostapd.conf & diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dhcpcd.conf b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dhcpcd.conf new file mode 100644 index 0000000..24bad28 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dhcpcd.conf @@ -0,0 +1,2 @@ +interface wlan0 +static ip_address=192.168.100.1/24 diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dnsmasq.conf b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dnsmasq.conf new file mode 100644 index 0000000..b989473 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/dnsmasq.conf @@ -0,0 +1,10 @@ +interface=wlan0 +dhcp-range=192.168.100.10,192.168.100.15,12h +#dhcp-option=option:router,192.168.100.1 +#dhcp-option=option:netmask,255.255.255.0 + +# Delays sending DHCPOFFER and proxydhcp replies for at least the specified number of seconds. +dhcp-mac=set:client_is_a_pi,B8:27:EB:*:*:* +dhcp-reply-delay=tag:client_is_a_pi,2 + +address=/simplypisetup.com/192.168.100.1 diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.nowpa b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.nowpa new file mode 100644 index 0000000..2613e79 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.nowpa @@ -0,0 +1,4 @@ +interface=wlan0 +driver=nl80211 +ssid=temp-ssid +channel=1 \ No newline at end of file diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.wpa b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.wpa new file mode 100644 index 0000000..39fd197 --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/hostapd.conf.wpa @@ -0,0 +1,9 @@ +interface=wlan0 +driver=nl80211 +ssid=temp-ssid +channel=1 +auth_algs=1 +wpa=2 +wpa_key_mgmt=WPA-PSK +rsn_pairwise=CCMP +wpa_passphrase=0 \ No newline at end of file diff --git a/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/wpa_supplicant.conf.default b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/wpa_supplicant.conf.default new file mode 100644 index 0000000..71912ec --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/reset_device/static_files/wpa_supplicant.conf.default @@ -0,0 +1,2 @@ +ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev +update_config=1 \ No newline at end of file diff --git a/scripts/files/root/usr/lib/raspiwifi/uninstall.py b/scripts/files/root/usr/lib/raspiwifi/uninstall.py new file mode 100644 index 0000000..fb45b9e --- /dev/null +++ b/scripts/files/root/usr/lib/raspiwifi/uninstall.py @@ -0,0 +1,40 @@ +import os +import sys + +os.system('clear') +print() +print() +print("#################################") +print("##### RaspiWiFi Uninstaller #####") +print("#################################") +print() +print() +uninstall_answer = input("Would you like to uninstall RaspiWiFi? [y/N]: ") +print() + +if (uninstall_answer.lower() == "y"): + print('Uninstalling RaspiWiFi from your system...') + + os.system('cp ' + os.path.dirname(os.path.realpath(__file__)) + '/reset_device/static_files/wpa_supplicant.conf.default /etc/wpa_supplicant/wpa_supplicant.conf') + os.system('chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf') + os.system('mv /etc/wpa_supplicant/wpa_supplicant.conf.original /etc/wpa_supplicant/wpa_supplicant.conf 2>/dev/null') + os.system('rm -rf /etc/raspiwifi') + os.system('rm -rf /usr/lib/raspiwifi') + os.system('rm -rf /etc/cron.raspiwifi') + os.system('rm /etc/dnsmasq.conf') + os.system('mv /etc/dnsmasq.conf.original /etc/dnsmasq.conf 2>/dev/null') + os.system('rm /etc/hostapd/hostapd.conf') + os.system('rm /etc/dhcpcd.conf') + os.system('mv /etc/dhcpcd.conf.original /etc/dhcpcd.conf 2>/dev/null') + os.system('sed -i \'s/# RaspiWiFi Startup//\' /etc/crontab') + os.system('sed -i \'s/@reboot root run-parts \/etc\/cron.raspiwifi\///\' /etc/crontab') + + print() + print() + reboot_answer = input('Uninstallation is complete. Would you like to reboot the system now?: ') + + if(reboot_answer.lower() == "y"): + os.system('reboot') +else: + print() + print('No changes made. Exiting unistaller...') \ No newline at end of file