Skip to content

Commit 8cf3585

Browse files
committed
Major rework for CustomPiOs v2 - add config loading of meta and remote modules, get meta modules and images name to match #214
1 parent 3718f75 commit 8cf3585

12 files changed

+263
-46
lines changed

src/base_image_downloader.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import yaml
4+
import os
5+
import urllib.request
6+
import tempfile
7+
import hashlib
8+
import shutil
9+
import re
10+
PRECENT_PROGRESS_SIZE = 5
11+
12+
class ChecksumFailException(Exception):
13+
pass
14+
15+
IMAGES_CONFIG = os.path.join(os.path.dirname(__file__), "images.yml")
16+
RETRY = 3
17+
18+
def ensure_dir(d, chmod=0o777):
19+
"""
20+
Ensures a folder exists.
21+
Returns True if the folder already exists
22+
"""
23+
if not os.path.exists(d):
24+
os.makedirs(d, chmod)
25+
os.chmod(d, chmod)
26+
return False
27+
return True
28+
29+
def read_images():
30+
if not os.path.isfile(IMAGES_CONFIG):
31+
raise Exception(f"Error: Remotes config file not found: {IMAGES_CONFIG}")
32+
with open(IMAGES_CONFIG,'r') as f:
33+
output = yaml.safe_load(f)
34+
return output
35+
36+
class DownloadProgress:
37+
last_precent: float = 0
38+
def show_progress(self, block_num, block_size, total_size):
39+
new_precent = round(block_num * block_size / total_size * 100, 1)
40+
if new_precent > self.last_precent + PRECENT_PROGRESS_SIZE:
41+
print(f"{new_precent}%", end="\r")
42+
self.last_precent = new_precent
43+
44+
def get_file_name(headers):
45+
return re.findall("filename=(\S+)", headers["Content-Disposition"])[0]
46+
47+
def get_sha256(filename):
48+
sha256_hash = hashlib.sha256()
49+
with open(filename,"rb") as f:
50+
for byte_block in iter(lambda: f.read(4096),b""):
51+
sha256_hash.update(byte_block)
52+
file_checksum = sha256_hash.hexdigest()
53+
return file_checksum
54+
return
55+
56+
def download_image_http(board: str, dest_folder: str, redownload: bool = False):
57+
url = board["url"]
58+
checksum = board["checksum"]
59+
60+
with tempfile.TemporaryDirectory() as tmpdirname:
61+
print('created temporary directory', tmpdirname)
62+
temp_file_name = os.path.join(tmpdirname, "image.xz")
63+
temp_file_checksum = os.path.join(tmpdirname, "checksum.sha256")
64+
65+
for r in range(RETRY):
66+
try:
67+
# Get sha and confirm its the right image
68+
download_progress = DownloadProgress()
69+
_, headers_checksum = urllib.request.urlretrieve(checksum, temp_file_checksum, download_progress.show_progress)
70+
file_name_checksum = get_file_name(headers_checksum)
71+
72+
checksum_data = None
73+
with open(temp_file_checksum, 'r') as f:
74+
checksum_data = f.read()
75+
76+
checksum_data_parsed = [x.strip() for x in checksum_data.split()]
77+
online_checksum = checksum_data_parsed[0]
78+
file_name_from_checksum = checksum_data_parsed[1]
79+
dest_file_name = os.path.join(dest_folder, file_name_from_checksum)
80+
print(f"Downloading {dest_file_name}")
81+
82+
if os.path.isfile(dest_file_name):
83+
file_checksum = get_sha256(dest_file_name)
84+
if file_checksum == online_checksum:
85+
# We got file and checksum is right
86+
return
87+
# Get the file
88+
download_progress = DownloadProgress()
89+
_, headers = urllib.request.urlretrieve(url, temp_file_name, download_progress.show_progress)
90+
91+
file_name = get_file_name(headers)
92+
file_checksum = get_sha256(temp_file_name)
93+
if file_checksum != online_checksum:
94+
print(f'Failed. Attempt # {r + 1}, checksum missmatch: {file_checksum} expected: {online_checksum}')
95+
continue
96+
ensure_dir(os.path.dirname(dest_file_name))
97+
shutil.move(temp_file_name, dest_file_name)
98+
99+
except Exception as e:
100+
if r < 2:
101+
print(f'Failed. Attempt # {r + 1}, got: {e}')
102+
else:
103+
print('Error encoutered at {RETRY} attempt')
104+
print(e)
105+
else:
106+
print(f"Success: {temp_file_name}")
107+
break
108+
return
109+
110+
if __name__ == "__main__":
111+
parser = argparse.ArgumentParser(add_help=True, description='Download images based on BASE_BOARD and BASE_O')
112+
parser.add_argument('WORKSPACE_SUFFIX', nargs='?', default="default", help="The workspace folder suffix used folder")
113+
parser.add_argument('-s', '--sha256', action='store_true', help='Create a sha256 hash for the .img file in .sha256')
114+
args = parser.parse_args()
115+
116+
images = read_images()
117+
118+
base_board = os.environ.get("BASE_BOARD", None)
119+
base_image_path = os.environ.get("BASE_IMAGE_PATH", None)
120+
121+
if base_board is not None and base_board in images["images"]:
122+
if images["images"][base_board]["type"] == "http":
123+
download_image_http(images["images"][base_board], base_image_path)
124+
elif images["images"][base_board]["type"] == "torrent":
125+
print("Error: Torrent not implemented")
126+
exit(1)
127+
else:
128+
print("Error: Unsupported image download type")
129+
exit(1)
130+
131+
print("Done")

src/base_image_downloader_wrapper.sh

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
set +x
3+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4+
5+
source "$DIR/argparse.bash" || exit 1
6+
argparse "$@" <<EOF || exit 1
7+
8+
parser.add_argument('WORKSPACE_SUFFIX', nargs='?', default="default", help="The workspace folder suffix used folder")
9+
parser.add_argument('-s', '--sha256', action='store_true', help='Create a sha256 hash for the .img file in .sha256')
10+
EOF
11+
12+
if [ -z "${CUSTOM_PI_OS_PATH}" ];then
13+
echo "Error: you must have \${CUSTOM_PI_OS_PATH} set"
14+
exit 1
15+
fi
16+
17+
# source "${DIST_PATH}/config"
18+
source "${CUSTOM_PI_OS_PATH}/config" "${WORKSPACE_SUFFIX}"
19+
20+
python3 ${CUSTOM_PI_OS_PATH}/base_image_downloader.py "${WORKSPACE_SUFFIX}"
21+

src/build

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ CUSTOM_OS_PATH=$(dirname $(realpath -s $0))
1818
source ${CUSTOM_PI_OS_PATH}/config ${@}
1919
${CUSTOM_PI_OS_PATH}/config_sanity
2020
21-
[ "$CONFIG_ONLY" == "yes" ] || source ${CUSTOM_OS_PATH}/custompios
21+
[ "$CONFIG_ONLY" == "yes" ] || source ${CUSTOM_OS_PATH}/custompios ${@}
2222
EOF
2323

2424
if [ "$LOG" != "no" ]; then

src/common.sh

+33
Original file line numberDiff line numberDiff line change
@@ -554,3 +554,36 @@ function set_config_var() {
554554
# See https://github.com/RPi-Distro/raspi-config/blob/master/raspi-config#L231
555555
raspi-config nonint set_config_var $1 $2 /boot/config.txt
556556
}
557+
558+
559+
function load_module_config() {
560+
# Takes a comma seprated modules list, and exports the environment variables for it
561+
MODULES_AFTER=$1
562+
for module in $(echo "${MODULES_AFTER}" | tr "," "\n")
563+
do
564+
if [ -d "${DIST_PATH}/modules/${module}" ]; then
565+
export MODULE_PATH="${DIST_PATH}/modules/${module}"
566+
elif [ -d "${CUSTOM_PI_OS_PATH}/modules/${module}" ]; then
567+
export MODULE_PATH="${CUSTOM_PI_OS_PATH}/modules/${module}"
568+
fi
569+
570+
echo "loading $module config at ${MODULE_PATH}/config"
571+
if [ -f "${MODULE_PATH}/config" ]; then
572+
source "${MODULE_PATH}/config"
573+
else
574+
echo "WARNING: module ${module} has no config file"
575+
fi
576+
577+
###############################################################################
578+
# Print and export the final configuration.
579+
580+
echo "================================================================"
581+
echo "Using the following config:"
582+
module_up=${module^^} module_up=${module_up//-/_}_
583+
584+
# Export variables that satisfy the $module_up prefix
585+
while IFS= read -r var; do export "$var"; echo "$var"; done < <(compgen -A variable "$module_up")
586+
587+
echo "================================================================"
588+
done
589+
}

src/config

+13-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
CONFIG_DIR=$(dirname $(realpath -s "${BASH_SOURCE}"))
2+
source ${CUSTOM_PI_OS_PATH}/common.sh
23

34
WORKSPACE_POSTFIX=
45

@@ -79,31 +80,16 @@ TMP="${MODULES//(/,}"
7980
TMP="${TMP// /}"
8081
MODULES_LIST="${TMP//)/,}"
8182

82-
for module in $(echo "${MODULES_LIST}" | tr "," "\n")
83-
do
84-
if [ -d "${DIST_PATH}/modules/${module}" ]; then
85-
export MODULE_PATH="${DIST_PATH}/modules/${module}"
86-
elif [ -d "${CUSTOM_PI_OS_PATH}/modules/${module}" ]; then
87-
export MODULE_PATH="${CUSTOM_PI_OS_PATH}/modules/${module}"
88-
fi
89-
90-
echo "loading $module config at ${MODULE_PATH}/config"
91-
if [ -f "${MODULE_PATH}/config" ]; then
92-
source "${MODULE_PATH}/config"
93-
else
94-
echo "WARNING: module ${module} has no config file"
95-
fi
96-
97-
###############################################################################
98-
# Print and export the final configuration.
99-
100-
echo "================================================================"
101-
echo "Using the following config:"
102-
module_up=${module^^} module_up=${module_up//-/_}_
103-
104-
# Export variables that satisfy the $module_up prefix
105-
while IFS= read -r var; do export "$var"; echo "$var"; done < <(compgen -A variable "$module_up")
106-
107-
echo "================================================================"
108-
done
10983

84+
# Base workspace is special, it has to be sourced before the base module, so remote modules could be calcualted
85+
[ -n "$BASE_WORKSPACE" ] || BASE_WORKSPACE=${DIST_PATH}/workspace$WORKSPACE_POSTFIX
86+
# [ -n "$BASE_CHROOT_SCRIPT_PATH" ] || BASE_CHROOT_SCRIPT_PATH=$BASE_SCRIPT_PATH/chroot_script
87+
[ -n "$BASE_MOUNT_PATH" ] || BASE_MOUNT_PATH=$BASE_WORKSPACE/mount
88+
89+
export REMOTE_AND_META_CONFIG="$BASE_WORKSPACE"/remote_and_meta_config
90+
# Remote modules and meta modulese go in first if they want to change standard behaviour
91+
if [ -f "${REMOTE_AND_META_CONFIG}" ]; then
92+
source "${REMOTE_AND_META_CONFIG}"
93+
fi
94+
95+
load_module_config "${MODULES_LIST}"

src/custompios

+14-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,20 @@ pushd $BASE_WORKSPACE
179179
# execute the base chroot script
180180
### execute_chroot_script $BASE_SCRIPT_PATH $BASE_CHROOT_SCRIPT_PATH
181181
CHROOT_SCRIPT=${BASE_WORKSPACE}/chroot_script
182-
python3 ${CUSTOM_PI_OS_PATH}/execution_order.py "${MODULES}" ${CHROOT_SCRIPT}
182+
MODULES_AFTER_PATH=${BASE_WORKSPACE}/modules_after
183+
MODULES_BEFORE="${MODULES}"
184+
${CUSTOM_PI_OS_PATH}/execution_order.py "${MODULES}" "${CHROOT_SCRIPT}" "${MODULES_AFTER_PATH}" "${REMOTE_AND_META_CONFIG}"
185+
if [ -f "${REMOTE_AND_META_CONFIG}" ]; then
186+
echo "Sourcing remote and submodules config"
187+
source "${REMOTE_AND_META_CONFIG}" ${@}
188+
189+
MODULES_AFTER=$(cat "${MODULES_AFTER_PATH}")
190+
load_module_config "${MODULES_AFTER}"
191+
192+
else
193+
echo "No remote and submodules config detected"
194+
fi
195+
echo $ARMBIAN_CONFIG_TXT_FILE
183196
export -f execute_chroot_script
184197
bash -x "${CHROOT_SCRIPT}"
185198

src/execution_order.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
#!/usr/bin/python3
12
#a='base(octopi,a(b,c(a2)),mm)'
23
import argparse
34
import os
45
import subprocess
56
from get_remote_modules import get_remote_module
67
from typing import TextIO, List, Tuple, Dict, Any
78

8-
99
def run_command(command: List[str], **kwargs: Dict[str, Any]):
1010
is_timeout = False
1111
p = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
@@ -138,6 +138,8 @@ def handle_meta_modules(modules: List[Tuple[str,str]]) -> Tuple[List[Tuple[str,s
138138
parser = argparse.ArgumentParser(add_help=True, description='Parse and run CustomPiOS chroot modules')
139139
parser.add_argument('modules', type=str, help='A string showing how the modules should be called')
140140
parser.add_argument('output_script', type=str, help='path to output the chroot script master')
141+
parser.add_argument('modules_after_path', nargs='?', default=None, type=str, help='path to output the chroot script master')
142+
parser.add_argument('remote_and_meta_config_path', nargs='?', default=None, type=str, help='path to output the config script of remote modules and submodules')
141143
args = parser.parse_args()
142144

143145
if os.path.isfile(args.output_script):
@@ -155,6 +157,27 @@ def handle_meta_modules(modules: List[Tuple[str,str]]) -> Tuple[List[Tuple[str,s
155157
for module, state in modules_execution_order:
156158
module_folder = modules_to_modules_folder[module]
157159
write_modules_scripts(module, state, module_folder, f)
160+
161+
# List all new modules add them in, then remove existing ones
162+
list_new_modules = []
163+
for module, state in modules_execution_order:
164+
if module not in list_new_modules:
165+
list_new_modules.append(module)
166+
for module, state in initial_execution_order:
167+
if module in list_new_modules:
168+
list_new_modules.remove(module)
158169

170+
# TODO2: load configs from yaml
171+
if args.modules_after_path is not None:
172+
with open(args.modules_after_path, "w") as w:
173+
w.write(",".join(list_new_modules))
174+
175+
with open(args.remote_and_meta_config_path, "w") as f:
176+
for module in list_new_modules:
177+
module_folder = modules_to_modules_folder[module]
178+
module_config_path = os.path.join(module_folder, "config")
179+
if os.path.isfile(module_config_path):
180+
f.write(f"source {module_config_path}\n")
181+
159182
os.chmod(args.output_script, 0o755)
160183

src/modules/base/config

+13-11
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,24 @@ BASE_VERSION=1.5.0
88
[ -n "$BASE_PRESCRIPT" ] || BASE_PRESCRIPT=
99
[ -n "$BASE_POSTSCRIPT" ] || BASE_POSTSCRIPT=
1010

11+
# Board and OS
12+
[ -n "$BASE_BOARD" ] || BASE_BOARD="raspberrypi"
13+
[ -n "$BASE_OS" ] || BASE_OS="debian_bookworm"
14+
15+
# TODO: UNIFY AND MAKE A SINGLE IMAGE FLOW, ATM THERE IS THE OLD AND NEW FLOW TOGETHER, UBUTU CAN GO IN ITS OWN
16+
1117
#[ -n "$BASE_SCRIPT_PATH" ] || BASE_SCRIPT_PATH=$CONFIG_DIR
12-
[ -n "$BASE_IMAGE_PATH" ] || BASE_IMAGE_PATH=${DIST_PATH}/image
18+
if [ "${BASE_BOARD}" == "raspberrypiarmhf" ] && [ "${BASE_BOARD}" == "raspberrypiarm64" ]; then
19+
[ -n "$BASE_IMAGE_PATH" ] || BASE_IMAGE_PATH=${DIST_PATH}/image
20+
else
21+
[ -n "$BASE_IMAGE_PATH" ] || BASE_IMAGE_PATH=${DIST_PATH}/image-"${BASE_BOARD}"
22+
BASE_ZIP_IMG=`ls -t $BASE_IMAGE_PATH/*.{zip,7z,xz} | head -n 1`
23+
fi
1324
[ -n "$BASE_IMAGE_RASPBIAN" ] || BASE_IMAGE_RASPBIAN=yes
1425

1526
# Distro
1627
[ -n "$BASE_DISTRO" ] || BASE_DISTRO=raspbian
1728

18-
# Board and OS
19-
[ -n "$BASE_BOARD" ] || BASE_BOARD="raspberrypi"
20-
[ -n "$BASE_OS" ] || BASE_OS="debian_bookworm"
21-
22-
2329
# Note: Set BASE_ZIP_IMG relative to the distro/src/workspace directory to pass a custom named file or an already extracted '.img'-file.
2430
if [ "${BASE_DISTRO}" == "ubuntu" ]; then
2531
# Default image ubuntu
@@ -33,7 +39,7 @@ if [ "${BASE_DISTRO}" == "ubuntu" ]; then
3339
[ -n "$BASE_USER_PASSWORD" ] || BASE_USER_PASSWORD=ubuntu
3440
else
3541
# Default image raspbian
36-
if [ "${BASE_DISTRO}" == "raspios64" ]; then
42+
if [ "${BASE_DISTRO}" == "raspios64" ] && [ "${BASE_BOARD}" == "raspberrypiarmhf" ] && [ "${BASE_BOARD}" == "raspberrypiarm64" ]; then
3743
[ -n "$BASE_ZIP_IMG" ] || BASE_ZIP_IMG=`ls -t $BASE_IMAGE_PATH/*-{raspbian,raspios}-*-arm64-*.{zip,7z,xz} | head -n 1`
3844
else
3945
[ -n "$BASE_ZIP_IMG" ] || BASE_ZIP_IMG=`ls -t $BASE_IMAGE_PATH/*-{raspbian,raspios}*.{zip,7z,xz} | head -n 1`
@@ -50,10 +56,6 @@ fi
5056
[ -n "$BASE_RELEASE_IMG_NAME" ] || BASE_RELEASE_IMG_NAME=default
5157
[ -n "$BASE_RELEASE_ZIP_NAME" ] || BASE_RELEASE_ZIP_NAME=default
5258

53-
[ -n "$BASE_WORKSPACE" ] || BASE_WORKSPACE=${DIST_PATH}/workspace$WORKSPACE_POSTFIX
54-
# [ -n "$BASE_CHROOT_SCRIPT_PATH" ] || BASE_CHROOT_SCRIPT_PATH=$BASE_SCRIPT_PATH/chroot_script
55-
[ -n "$BASE_MOUNT_PATH" ] || BASE_MOUNT_PATH=$BASE_WORKSPACE/mount
56-
5759
if [ "${BASE_DISTRO}" == "ubuntu" ]; then
5860
[ -n "${BASE_BOOT_MOUNT_PATH}" ] || BASE_BOOT_MOUNT_PATH=boot/firmware
5961
else

src/modules/base/meta

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export LC_ALL=C
1010

1111
FINAL_MODULES=()
1212

13-
if [ "${BASE_BOARD}" == "armbian" ]; then
13+
if [[ "${BASE_BOARD}" = armbian* ]]; then
1414
FINAL_MODULES+=("armbian")
15-
elif [ "${BASE_BOARD}" == "orange" ]; then
15+
elif [[ "${BASE_BOARD}" = orange* ]]; then
1616
FINAL_MODULES+=("orange")
1717
fi
1818

src/modules/network/meta

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export LC_ALL=C
1010

1111
FINAL_MODULES=()
1212

13-
if [ "${BASE_BOARD}" == "armbian" ]; then
13+
if [[ "${BASE_BOARD}" = armbian* ]]; then
1414
FINAL_MODULES+=("armbian_net")
15-
elif [ "${BASE_BOARD}" == "orange" ]; then
15+
elif [[ "${BASE_BOARD}" = orange* ]]; then
1616
FINAL_MODULES+=("orange_net")
1717
fi
1818

0 commit comments

Comments
 (0)