Skip to content

Commit c9a0914

Browse files
committed
Meta modules support #214
1 parent 3bbea66 commit c9a0914

15 files changed

+413
-69
lines changed

src/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1616
python3-distutils \
1717
python3-dev \
1818
python3-git \
19+
python3-yaml \
1920
binfmt-support \
2021
qemu-system \
2122
qemu-user \

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

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
@@ -187,7 +187,20 @@ pushd $BASE_WORKSPACE
187187
# execute the base chroot script
188188
### execute_chroot_script $BASE_SCRIPT_PATH $BASE_CHROOT_SCRIPT_PATH
189189
CHROOT_SCRIPT=${BASE_WORKSPACE}/chroot_script
190-
python3 ${CUSTOM_PI_OS_PATH}/execution_order.py "${MODULES}" ${CHROOT_SCRIPT}
190+
MODULES_AFTER_PATH=${BASE_WORKSPACE}/modules_after
191+
MODULES_BEFORE="${MODULES}"
192+
${CUSTOM_PI_OS_PATH}/execution_order.py "${MODULES}" "${CHROOT_SCRIPT}" "${MODULES_AFTER_PATH}" "${REMOTE_AND_META_CONFIG}"
193+
if [ -f "${REMOTE_AND_META_CONFIG}" ]; then
194+
echo "Sourcing remote and submodules config"
195+
source "${REMOTE_AND_META_CONFIG}" ${@}
196+
197+
MODULES_AFTER=$(cat "${MODULES_AFTER_PATH}")
198+
load_module_config "${MODULES_AFTER}"
199+
200+
else
201+
echo "No remote and submodules config detected"
202+
fi
203+
echo $ARMBIAN_CONFIG_TXT_FILE
191204
export -f execute_chroot_script
192205
bash -x "${CHROOT_SCRIPT}"
193206

0 commit comments

Comments
 (0)