forked from tfgrid/zosbuilder
feat(rfs): flist pack to S3 + read-only route embedding + zinit mount scripts; docs; dev-container tooling
Summary
- Implemented plain S3-only flist workflow (no web endpoint). rfs pack uploads blobs using write creds; flist route.url is patched to embed read-only S3 credentials so rfs mount reads directly from S3.
Changes
1) New RFS tooling (scripts/rfs/)
- common.sh:
- Compute FULL_KERNEL_VERSION from configs (no uname).
- Load S3 config and construct pack store URI.
- Build read-only S3 route URL and patch flist (sqlite).
- Helpers to locate modules/firmware trees and rfs binary.
- pack-modules.sh:
- Pack /lib/modules/<FULL_KERNEL_VERSION> to dist/flists/modules-<FULL_KERNEL_VERSION>.fl
- Patch flist route to s3://READ:READ@host:port/ROUTE_PATH?region=ROUTE_REGION (default /blobs, garage).
- Optional upload of .fl using MinIO client (mcli/mc).
- pack-firmware.sh:
- Source firmware from $PROJECT_ROOT/firmware (fallback to initramfs/lib/firmware).
- Pack to dist/flists/firmware-<TAG_OR_DATE>.fl (FIRMWARE_TAG or YYYYMMDD).
- Patch flist route to read-only S3; optional .fl upload via mcli/mc.
- verify-flist.sh:
- rfs flist inspect/tree; optional mount test (best effort).
- patch-stores.sh:
- Helper to patch stores (kept though not used by default).
2) Dev-container (Dockerfile)
- Added sqlite and MinIO client package for manifest patching/upload (expect mcli binary at runtime; scripts support both mcli/mc).
- Retains rustup and musl target for building rfs/zinit/mycelium.
3) Config and examples
- config/rfs.conf.example:
- S3_ENDPOINT/S3_REGION/S3_BUCKET/S3_PREFIX
- S3_ACCESS_KEY/S3_SECRET_KEY (write)
- READ_ACCESS_KEY/READ_SECRET_KEY (read-only)
- ROUTE_ENDPOINT (defaults to S3_ENDPOINT), ROUTE_PATH=/blobs, ROUTE_REGION=garage
- MANIFESTS_SUBPATH, UPLOAD_MANIFESTS (mcli upload optional)
- config/rfs.conf updated by user with real values (not committed here; example included).
- config/modules.conf minor tweak (staged).
4) Zinit mount scripts (config/zinit/init/)
- firmware.sh:
- Mounts firmware-latest.fl over /usr/lib/firmware using rfs mount (env override FIRMWARE_FLIST supported).
- modules.sh:
- Mounts modules-$(uname -r).fl over /lib/modules/$(uname -r) (env override MODULES_FLIST supported).
- Both skip if target already mounted and respect RFS_BIN env.
5) Documentation
- docs/rfs-flists.md:
- End-to-end flow, S3-only route URL patching, mcli upload notes.
- docs/review-rfs-integration.md:
- Integration points, build flow, and post-build standalone usage.
- docs/depmod-behavior.md:
- depmod reads .modinfo; recommend prebuilt modules.*(.bin); use depmod -A only on mismatch.
6) Utility
- scripts/functionlist.md synced with current functions.
Behavioral details
- Pack (write):
s3://S3_ACCESS_KEY:S3_SECRET_KEY@HOST:PORT/S3_BUCKET/S3_PREFIX?region=REGION
- Flist route (read, post-patch):
s3://READ_ACCESS_KEY:READ_SECRET_KEY@HOST:PORT/ROUTE_PATH?region=ROUTE_REGION
Defaults: ROUTE_PATH=/blobs, ROUTE_REGION=garage; ROUTE_ENDPOINT derived from S3_ENDPOINT if not set.
Runtime mount examples
- Modules:
rfs mount -m dist/flists/modules-6.12.44-Zero-OS.fl /lib/modules/6.12.44-Zero-OS
- Firmware:
rfs mount -m dist/flists/firmware-YYYYMMDD.fl /usr/lib/firmware
Notes
- FUSE policy: If "allow_other" error occurs, enable user_allow_other in /etc/fuse.conf or run mounts as root.
- WEB_ENDPOINT rewrite is disabled by default (set WEB_ENDPOINT=""). Plain S3 route is embedded in flists.
- MinIO client binary in dev-container is mcli; scripts support mcli (preferred) and mc (fallback).
Files added/modified
- Added: scripts/rfs/{common.sh,pack-modules.sh,pack-firmware.sh,verify-flist.sh,patch-stores.sh}
- Added: config/zinit/init/{firmware.sh,modules.sh}
- Added: docs/{rfs-flists.md,review-rfs-integration.md,depmod-behavior.md}
- Added: config/rfs.conf.example
- Modified: Dockerfile, scripts/functionlist.md, config/modules.conf, config/zinit/sshd-setup.yaml, .gitignore
This commit is contained in:
474
scripts/rfs/common.sh
Executable file
474
scripts/rfs/common.sh
Executable file
@@ -0,0 +1,474 @@
|
||||
#!/bin/bash
|
||||
# Common helpers for RFS flist creation and manifest patching
|
||||
# - No changes to existing build pipeline; this library is used by standalone scripts under scripts/rfs
|
||||
# - Computes FULL_KERNEL_VERSION from configs (never uses uname -r)
|
||||
# - Loads S3 (garage) config and builds rfs S3 store URI
|
||||
# - Locates rfs binary and source trees for modules/firmware
|
||||
# - Provides helper to patch .fl (sqlite) stores table to use HTTPS web endpoint
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve project root from this file location
|
||||
rfs_common_project_root() {
|
||||
local here
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# scripts/rfs -> project root is two levels up
|
||||
dirname "$(dirname "$here")"
|
||||
}
|
||||
|
||||
PROJECT_ROOT="${PROJECT_ROOT:-$(rfs_common_project_root)}"
|
||||
SCRIPT_DIR="${PROJECT_ROOT}/scripts"
|
||||
LIB_DIR="${SCRIPT_DIR}/lib"
|
||||
|
||||
# Bring in logging and helpers if available
|
||||
if [[ -f "${LIB_DIR}/common.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${LIB_DIR}/common.sh"
|
||||
else
|
||||
# Minimal logging fallbacks
|
||||
log_info() { echo "[INFO] $*"; }
|
||||
log_warn() { echo "[WARN] $*" >&2; }
|
||||
log_error() { echo "[ERROR] $*" >&2; }
|
||||
log_debug() { if [[ "${DEBUG:-0}" == "1" ]]; then echo "[DEBUG] $*"; fi }
|
||||
safe_execute() { echo "[EXEC] $*"; "$@"; }
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Config loaders
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Load build.conf (KERNEL_VERSION, etc.) and compute FULL_KERNEL_VERSION
|
||||
# FULL_KERNEL_VERSION = KERNEL_VERSION + CONFIG_LOCALVERSION from config/kernel.config
|
||||
rfs_common_load_build_kernel_version() {
|
||||
local build_conf="${PROJECT_ROOT}/config/build.conf"
|
||||
local kcfg="${PROJECT_ROOT}/config/kernel.config"
|
||||
|
||||
if [[ -f "$build_conf" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$build_conf"
|
||||
else
|
||||
log_error "Missing build config: ${build_conf}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local base_ver="${KERNEL_VERSION:-}"
|
||||
if [[ -z "$base_ver" ]]; then
|
||||
log_error "KERNEL_VERSION not set in ${build_conf}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$kcfg" ]]; then
|
||||
log_error "Missing kernel config: ${kcfg}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract CONFIG_LOCALVERSION="..."; may include leading '-' in value
|
||||
local localver
|
||||
localver="$(grep -E '^CONFIG_LOCALVERSION=' "$kcfg" | cut -d'"' -f2 || true)"
|
||||
local full_ver="${base_ver}${localver}"
|
||||
|
||||
if [[ -z "$full_ver" ]]; then
|
||||
log_error "Failed to compute FULL_KERNEL_VERSION from configs"
|
||||
return 1
|
||||
fi
|
||||
|
||||
export FULL_KERNEL_VERSION="$full_ver"
|
||||
log_info "Computed FULL_KERNEL_VERSION: ${FULL_KERNEL_VERSION}"
|
||||
}
|
||||
|
||||
# Load RFS S3 configuration from config/rfs.conf or config/rfs.conf.example
|
||||
# Required:
|
||||
# S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_PREFIX, S3_ACCESS_KEY, S3_SECRET_KEY
|
||||
rfs_common_load_rfs_s3_config() {
|
||||
local conf_real="${PROJECT_ROOT}/config/rfs.conf"
|
||||
local conf_example="${PROJECT_ROOT}/config/rfs.conf.example"
|
||||
|
||||
if [[ -f "$conf_real" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$conf_real"
|
||||
log_info "Loaded RFS S3 config: ${conf_real}"
|
||||
elif [[ -f "$conf_example" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$conf_example"
|
||||
log_warn "Using example RFS config: ${conf_example} (override with config/rfs.conf)"
|
||||
else
|
||||
log_error "No RFS config found. Create config/rfs.conf or config/rfs.conf.example"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Allow environment to override sourced values
|
||||
S3_ENDPOINT="${S3_ENDPOINT:-}"
|
||||
S3_REGION="${S3_REGION:-}"
|
||||
S3_BUCKET="${S3_BUCKET:-}"
|
||||
S3_PREFIX="${S3_PREFIX:-}"
|
||||
S3_ACCESS_KEY="${S3_ACCESS_KEY:-}"
|
||||
S3_SECRET_KEY="${S3_SECRET_KEY:-}"
|
||||
|
||||
local missing=0
|
||||
for v in S3_ENDPOINT S3_REGION S3_BUCKET S3_PREFIX S3_ACCESS_KEY S3_SECRET_KEY; do
|
||||
if [[ -z "${!v}" ]]; then
|
||||
log_error "Missing required S3 config variable: ${v}"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [[ $missing -ne 0 ]]; then
|
||||
log_error "Incomplete RFS S3 configuration"
|
||||
return 1
|
||||
fi
|
||||
|
||||
export S3_ENDPOINT S3_REGION S3_BUCKET S3_PREFIX S3_ACCESS_KEY S3_SECRET_KEY
|
||||
|
||||
# Validate placeholders are not left as defaults
|
||||
if [[ "${S3_ACCESS_KEY}" == "REPLACE_ME" || "${S3_SECRET_KEY}" == "REPLACE_ME" ]]; then
|
||||
log_error "S3_ACCESS_KEY / S3_SECRET_KEY in config/rfs.conf are placeholders. Please set real credentials."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Optional read-only credentials for route URL; default to write keys if not provided
|
||||
READ_ACCESS_KEY="${READ_ACCESS_KEY:-$S3_ACCESS_KEY}"
|
||||
READ_SECRET_KEY="${READ_SECRET_KEY:-$S3_SECRET_KEY}"
|
||||
# Garage blob route path (default /blobs)
|
||||
ROUTE_PATH="${ROUTE_PATH:-/blobs}"
|
||||
export READ_ACCESS_KEY READ_SECRET_KEY ROUTE_PATH
|
||||
}
|
||||
|
||||
# Build rfs S3 store URI from loaded S3 config
|
||||
# Format: s3://ACCESS:SECRET@HOST:PORT/BUCKET/PREFIX?region=REGION
|
||||
rfs_common_build_s3_store_uri() {
|
||||
if [[ -z "${S3_ENDPOINT:-}" ]]; then
|
||||
log_error "S3_ENDPOINT not set; call rfs_common_load_rfs_s3_config first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Strip scheme from endpoint
|
||||
local hostport="${S3_ENDPOINT#http://}"
|
||||
hostport="${hostport#https://}"
|
||||
hostport="${hostport%/}"
|
||||
# Ensure explicit port; default to Garage S3 port 3900 when missing
|
||||
if [[ "$hostport" != *:* ]]; then
|
||||
hostport="${hostport}:3900"
|
||||
fi
|
||||
|
||||
# Minimal percent-encoding for ':' and '@' in credentials
|
||||
local ak="${S3_ACCESS_KEY//:/%3A}"
|
||||
ak="${ak//@/%40}"
|
||||
local sk="${S3_SECRET_KEY//:/%3A}"
|
||||
sk="${sk//@/%40}"
|
||||
|
||||
local path="${S3_BUCKET}/${S3_PREFIX}"
|
||||
path="${path#/}" # ensure no leading slash duplication
|
||||
|
||||
local uri="s3://${ak}:${sk}@${hostport}/${path}?region=${S3_REGION}"
|
||||
export RFS_S3_STORE_URI="$uri"
|
||||
log_info "Constructed RFS S3 store URI: ${RFS_S3_STORE_URI}"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tool discovery
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Locate rfs binary: prefer PATH, fallback to components build
|
||||
rfs_common_locate_rfs() {
|
||||
if command -v rfs >/dev/null 2>&1; then
|
||||
export RFS_BIN="$(command -v rfs)"
|
||||
log_info "Using rfs from PATH: ${RFS_BIN}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fallback to components
|
||||
local rtarget
|
||||
if [[ -f "${PROJECT_ROOT}/config/build.conf" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/config/build.conf"
|
||||
fi
|
||||
rtarget="${RUST_TARGET:-x86_64-unknown-linux-musl}"
|
||||
|
||||
local candidate="${PROJECT_ROOT}/components/rfs/target/${rtarget}/release/rfs"
|
||||
if [[ -x "$candidate" ]]; then
|
||||
export RFS_BIN="$candidate"
|
||||
log_info "Using rfs from components: ${RFS_BIN}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "rfs binary not found. Build it via components stage or install it in PATH."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Ensure sqlite3 is available (for manifest patch)
|
||||
rfs_common_require_sqlite3() {
|
||||
if ! command -v sqlite3 >/dev/null 2>&1; then
|
||||
log_error "sqlite3 not found. Install sqlite3 to patch .fl manifest stores."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Source tree discovery
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Locate modules directory for FULL_KERNEL_VERSION
|
||||
# Priority:
|
||||
# 1) /lib/modules/<FULL_KERNEL_VERSION>
|
||||
# 2) ${PROJECT_ROOT}/kernel/lib/modules/<FULL_KERNEL_VERSION>
|
||||
# 3) ${PROJECT_ROOT}/initramfs/lib/modules/<FULL_KERNEL_VERSION>
|
||||
rfs_common_locate_modules_dir() {
|
||||
local kver="${1:-${FULL_KERNEL_VERSION:-}}"
|
||||
if [[ -z "$kver" ]]; then
|
||||
log_error "rfs_common_locate_modules_dir: FULL_KERNEL_VERSION is empty"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local candidates=(
|
||||
"/lib/modules/${kver}"
|
||||
"${PROJECT_ROOT}/kernel/lib/modules/${kver}"
|
||||
"${PROJECT_ROOT}/initramfs/lib/modules/${kver}"
|
||||
)
|
||||
local d
|
||||
for d in "${candidates[@]}"; do
|
||||
if [[ -d "$d" ]]; then
|
||||
export MODULES_DIR="$d"
|
||||
log_info "Found modules dir: ${MODULES_DIR}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
log_error "No modules directory found for ${kver}. Checked: ${candidates[*]}"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Locate firmware directory
|
||||
# Priority:
|
||||
# 1) ${PROJECT_ROOT}/firmware
|
||||
# 2) ${PROJECT_ROOT}/initramfs/lib/firmware
|
||||
# 3) /lib/firmware
|
||||
rfs_common_locate_firmware_dir() {
|
||||
local candidates=(
|
||||
"${PROJECT_ROOT}/firmware"
|
||||
"${PROJECT_ROOT}/initramfs/lib/firmware"
|
||||
"/lib/firmware"
|
||||
)
|
||||
local d
|
||||
for d in "${candidates[@]}"; do
|
||||
if [[ -d "$d" ]]; then
|
||||
export FIRMWARE_DIR="$d"
|
||||
log_info "Found firmware dir: ${FIRMWARE_DIR}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
log_error "No firmware directory found. Checked: ${candidates[*]}"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Ensure precomputed modules metadata are present (to avoid depmod at boot)
|
||||
rfs_common_validate_modules_metadata() {
|
||||
local md="${MODULES_DIR:-}"
|
||||
if [[ -z "$md" || ! -d "$md" ]]; then
|
||||
log_error "MODULES_DIR not set or invalid"
|
||||
return 1
|
||||
fi
|
||||
local ok=1
|
||||
local files=(modules.dep modules.dep.bin modules.alias modules.alias.bin modules.symbols.bin modules.order modules.builtin modules.builtin.modinfo)
|
||||
local missing=()
|
||||
for f in "${files[@]}"; do
|
||||
if [[ ! -f "${md}/${f}" ]]; then
|
||||
missing+=("$f")
|
||||
ok=0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $ok -eq 1 ]]; then
|
||||
log_info "Modules metadata present in ${md}"
|
||||
return 0
|
||||
else
|
||||
log_warn "Missing some modules metadata in ${md}: ${missing[*]}"
|
||||
# Not fatal; rfs pack can proceed, but boot may require depmod -A or full scan
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Manifest patching (sqlite .fl)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Patch the .fl manifest's stores table to use an HTTPS web endpoint
|
||||
# Args:
|
||||
# $1 = path to .fl file
|
||||
# $2 = HTTPS base (e.g., https://hub.grid.tf/zos/zosbuilder) - no trailing slash
|
||||
# $3 = keep_s3_fallback ("true"/"false") - if true, retain existing s3:// row(s)
|
||||
rfs_common_patch_flist_stores() {
|
||||
local fl="$1"
|
||||
local web_base="$2"
|
||||
local keep_s3="${3:-false}"
|
||||
|
||||
if [[ ! -f "$fl" ]]; then
|
||||
log_error "Manifest file not found: ${fl}"
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$web_base" ]]; then
|
||||
log_error "Web endpoint base is empty"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rfs_common_require_sqlite3
|
||||
|
||||
# Ensure no trailing slash
|
||||
web_base="${web_base%/}"
|
||||
|
||||
# Heuristic: if stores table exists, update any s3:// URI to the web_base, or insert web_base if none.
|
||||
local has_table
|
||||
has_table="$(sqlite3 "$fl" "SELECT name FROM sqlite_master WHERE type='table' AND name='stores';" || true)"
|
||||
if [[ -z "$has_table" ]]; then
|
||||
log_error "stores table not found in manifest (unexpected schema): ${fl}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Does any s3 store exist?
|
||||
local s3_count
|
||||
s3_count="$(sqlite3 "$fl" "SELECT COUNT(*) FROM stores WHERE uri LIKE 's3://%';" || echo 0)"
|
||||
|
||||
if [[ "${keep_s3}" != "true" ]]; then
|
||||
# Replace all s3://... URIs with the HTTPS web base
|
||||
log_info "Replacing s3 stores with HTTPS: ${web_base}"
|
||||
sqlite3 "$fl" "UPDATE stores SET uri='${web_base}' WHERE uri LIKE 's3://%';"
|
||||
else
|
||||
# Keep s3, but ensure https row exists and is ordered first if applicable
|
||||
local https_count
|
||||
https_count="$(sqlite3 "$fl" "SELECT COUNT(*) FROM stores WHERE uri='${web_base}';" || echo 0)"
|
||||
if [[ "$https_count" -eq 0 ]]; then
|
||||
log_info "Adding HTTPS store ${web_base} alongside existing s3 store(s)"
|
||||
# Attempt simple insert; table schema may include more columns, so try a best-effort approach:
|
||||
# Assume minimal schema: (id INTEGER PRIMARY KEY, uri TEXT UNIQUE)
|
||||
# If fails, user can adjust with rfs CLI.
|
||||
set +e
|
||||
sqlite3 "$fl" "INSERT OR IGNORE INTO stores(uri) VALUES('${web_base}');"
|
||||
local rc=$?
|
||||
set -e
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
log_warn "Could not INSERT into stores; schema may be different. Consider using rfs CLI to add store."
|
||||
fi
|
||||
else
|
||||
log_info "HTTPS store already present in manifest"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Patched stores in manifest: ${fl}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
# Manifest route URL patching (sqlite .fl) - use read-only credentials
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Build route URL for the flist 'route' table using read-only keys
|
||||
# Result example:
|
||||
# s3://READ_KEY:READ_SECRET@host:3900/blobs?region=garage
|
||||
rfs_common_build_route_url() {
|
||||
# Ensure sqlite available for later patch step
|
||||
rfs_common_require_sqlite3
|
||||
|
||||
# Defaults applicable to Garage
|
||||
local route_region="${ROUTE_REGION:-garage}"
|
||||
local route_path="${ROUTE_PATH:-/blobs}"
|
||||
|
||||
# Derive host:port from ROUTE_ENDPOINT or S3_ENDPOINT
|
||||
local endpoint="${ROUTE_ENDPOINT:-${S3_ENDPOINT:-}}"
|
||||
if [[ -z "$endpoint" ]]; then
|
||||
log_error "No ROUTE_ENDPOINT or S3_ENDPOINT set; cannot build route URL"
|
||||
return 1
|
||||
fi
|
||||
local hostport="${endpoint#http://}"
|
||||
hostport="${hostport#https://}"
|
||||
hostport="${hostport%/}"
|
||||
# Ensure explicit port; default to Garage S3 port 3900 when missing
|
||||
if [[ "$hostport" != *:* ]]; then
|
||||
hostport="${hostport}:3900"
|
||||
fi
|
||||
|
||||
# Percent-encode credentials minimally for ':' and '@'
|
||||
local rak="${READ_ACCESS_KEY//:/%3A}"
|
||||
rak="${rak//@/%40}"
|
||||
local rsk="${READ_SECRET_KEY//:/%3A}"
|
||||
rsk="${rsk//@/%40}"
|
||||
|
||||
# Normalize route path (ensure leading slash)
|
||||
if [[ "$route_path" != /* ]]; then
|
||||
route_path="/${route_path}"
|
||||
fi
|
||||
|
||||
local url="s3://${rak}:${rsk}@${hostport}${route_path}?region=${route_region}"
|
||||
export RFS_ROUTE_URL="$url"
|
||||
log_info "Constructed route URL for flist: ${RFS_ROUTE_URL}"
|
||||
}
|
||||
|
||||
# Patch the 'route' table URL inside the .fl manifest to use read-only key URL
|
||||
# Args:
|
||||
# $1 = path to .fl file
|
||||
rfs_common_patch_flist_route_url() {
|
||||
local fl="$1"
|
||||
if [[ -z "${RFS_ROUTE_URL:-}" ]]; then
|
||||
log_error "RFS_ROUTE_URL is empty; call rfs_common_build_route_url first"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$fl" ]]; then
|
||||
log_error "Manifest file not found: ${fl}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rfs_common_require_sqlite3
|
||||
|
||||
# Ensure 'route' table exists
|
||||
local has_route
|
||||
has_route="$(sqlite3 "$fl" "SELECT name FROM sqlite_master WHERE type='table' AND name='route';" || true)"
|
||||
if [[ -z "$has_route" ]]; then
|
||||
log_error "route table not found in manifest (unexpected schema): ${fl}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Updating route.url to: ${RFS_ROUTE_URL}"
|
||||
sqlite3 "$fl" "UPDATE route SET url='${RFS_ROUTE_URL}';"
|
||||
log_info "Patched route URL in manifest: ${fl}"
|
||||
}
|
||||
# Packaging helpers
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Ensure output directory exists and echo final manifest path
|
||||
# Args:
|
||||
# $1 = basename for manifest (e.g., modules-6.12.44-Zero-OS.fl)
|
||||
rfs_common_prepare_output() {
|
||||
local base="$1"
|
||||
local outdir="${PROJECT_ROOT}/dist/flists"
|
||||
mkdir -p "$outdir"
|
||||
echo "${outdir}/${base}"
|
||||
}
|
||||
|
||||
# Sanitize firmware tag or generate date-based tag (YYYYMMDD)
|
||||
rfs_common_firmware_tag() {
|
||||
local tag="${FIRMWARE_TAG:-}"
|
||||
if [[ -n "$tag" ]]; then
|
||||
# Replace path-unfriendly chars
|
||||
tag="${tag//[^A-Za-z0-9._-]/_}"
|
||||
echo "$tag"
|
||||
else
|
||||
date -u +%Y%m%d
|
||||
fi
|
||||
}
|
||||
|
||||
# If executed directly, show a quick status summary
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
log_info "rfs-common self-check..."
|
||||
rfs_common_load_build_kernel_version
|
||||
rfs_common_load_rfs_s3_config
|
||||
rfs_common_build_s3_store_uri
|
||||
rfs_common_locate_rfs
|
||||
rfs_common_locate_modules_dir "${FULL_KERNEL_VERSION}"
|
||||
rfs_common_validate_modules_metadata
|
||||
rfs_common_locate_firmware_dir
|
||||
log_info "All checks passed."
|
||||
log_info "FULL_KERNEL_VERSION=${FULL_KERNEL_VERSION}"
|
||||
log_info "RFS_S3_STORE_URI=${RFS_S3_STORE_URI}"
|
||||
log_info "MODULES_DIR=${MODULES_DIR}"
|
||||
log_info "FIRMWARE_DIR=${FIRMWARE_DIR}"
|
||||
log_info "RFS_BIN=${RFS_BIN}"
|
||||
fi
|
||||
79
scripts/rfs/pack-firmware.sh
Executable file
79
scripts/rfs/pack-firmware.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# Pack firmware tree into an RFS flist and patch manifest stores for Garage web endpoint.
|
||||
# - Computes FULL_KERNEL_VERSION from configs (not strictly needed for firmware, but kept uniform)
|
||||
# - Selects firmware directory with priority:
|
||||
# 1) $PROJECT_ROOT/firmware
|
||||
# 2) $PROJECT_ROOT/initramfs/lib/firmware
|
||||
# 3) /lib/firmware
|
||||
# - Manifest name: firmware-<FIRMWARE_TAG or YYYYMMDD>.fl
|
||||
# - Uploads blobs to S3 (Garage) via rfs store URI
|
||||
# - Patches .fl sqlite stores table to use WEB_ENDPOINT for read-only fetches
|
||||
# - Optionally uploads the .fl manifest to S3 manifests/ using aws CLI
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${HERE}/common.sh"
|
||||
|
||||
section() { echo -e "\n==== $* ====\n"; }
|
||||
|
||||
section "Loading configuration (kernel + RFS S3) and locating rfs"
|
||||
# Kernel version is computed for consistency/logging (not required to pack firmware)
|
||||
rfs_common_load_build_kernel_version
|
||||
rfs_common_load_rfs_s3_config
|
||||
rfs_common_build_s3_store_uri
|
||||
rfs_common_locate_rfs
|
||||
|
||||
section "Locating firmware directory"
|
||||
rfs_common_locate_firmware_dir
|
||||
|
||||
TAG="$(rfs_common_firmware_tag)"
|
||||
MANIFEST_NAME="firmware-${TAG}.fl"
|
||||
MANIFEST_PATH="$(rfs_common_prepare_output "${MANIFEST_NAME}")"
|
||||
|
||||
section "Packing firmware to flist"
|
||||
log_info "Firmware dir: ${FIRMWARE_DIR}"
|
||||
log_info "rfs pack -m ${MANIFEST_PATH} -s ${RFS_S3_STORE_URI} ${FIRMWARE_DIR}"
|
||||
safe_execute "${RFS_BIN}" pack -m "${MANIFEST_PATH}" -s "${RFS_S3_STORE_URI}" "${FIRMWARE_DIR}"
|
||||
|
||||
# Patch manifest route URL to include read-only S3 credentials (Garage)
|
||||
section "Updating route URL in manifest to include read-only S3 credentials"
|
||||
rfs_common_build_route_url
|
||||
rfs_common_patch_flist_route_url "${MANIFEST_PATH}"
|
||||
# Patch manifest stores to HTTPS web endpoint if provided
|
||||
if [[ -n "${WEB_ENDPOINT:-}" ]]; then
|
||||
section "Patching manifest stores to HTTPS web endpoint"
|
||||
log_info "Patching ${MANIFEST_PATH} stores to: ${WEB_ENDPOINT} (keep_s3_fallback=${KEEP_S3_FALLBACK:-false})"
|
||||
rfs_common_patch_flist_stores "${MANIFEST_PATH}" "${WEB_ENDPOINT}" "${KEEP_S3_FALLBACK:-false}"
|
||||
else
|
||||
log_warn "WEB_ENDPOINT not set in config; manifest will reference only s3:// store"
|
||||
fi
|
||||
|
||||
# Optional: upload .fl manifest to Garage via MinIO Client (separate from blobs)
|
||||
if [[ "${UPLOAD_MANIFESTS:-false}" == "true" ]]; then
|
||||
section "Uploading manifest .fl via MinIO Client to S3 manifests/"
|
||||
# Support both mcli (new) and mc (legacy) binaries
|
||||
if command -v mcli >/dev/null 2>&1; then
|
||||
MCLI_BIN="mcli"
|
||||
elif command -v mc >/dev/null 2>&1; then
|
||||
MCLI_BIN="mc"
|
||||
else
|
||||
log_warn "MinIO Client not found (expected mcli or mc); skipping manifest upload"
|
||||
MCLI_BIN=""
|
||||
fi
|
||||
|
||||
if [[ -n "${MCLI_BIN}" ]]; then
|
||||
local_subpath="${MANIFESTS_SUBPATH:-manifests}"
|
||||
# Configure alias and upload using MinIO client
|
||||
safe_execute "${MCLI_BIN}" alias set rfs "${S3_ENDPOINT}" "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}"
|
||||
mcli_dst="rfs/${S3_BUCKET}/${S3_PREFIX%/}/${local_subpath%/}/${MANIFEST_NAME}"
|
||||
log_info "${MCLI_BIN} cp ${MANIFEST_PATH} ${mcli_dst}"
|
||||
safe_execute "${MCLI_BIN}" cp "${MANIFEST_PATH}" "${mcli_dst}"
|
||||
fi
|
||||
else
|
||||
log_info "UPLOAD_MANIFESTS=false; skipping manifest upload"
|
||||
fi
|
||||
|
||||
section "Done"
|
||||
log_info "Manifest: ${MANIFEST_PATH}"
|
||||
73
scripts/rfs/pack-modules.sh
Executable file
73
scripts/rfs/pack-modules.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# Pack kernel modules into an RFS flist and patch manifest stores for Garage web endpoint.
|
||||
# - Computes FULL_KERNEL_VERSION from configs (never uses uname -r)
|
||||
# - Packs /lib/modules/<FULL_KERNEL_VERSION> (or fallback paths) to dist/flists/modules-<FULL_KERNEL_VERSION>.fl
|
||||
# - Uploads blobs to S3 (Garage) via rfs store URI
|
||||
# - Patches .fl sqlite stores table to use WEB_ENDPOINT for read-only fetches
|
||||
# - Optionally uploads the .fl manifest to S3 manifests/ using aws CLI
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${HERE}/common.sh"
|
||||
|
||||
section() { echo -e "\n==== $* ====\n"; }
|
||||
|
||||
section "Loading configuration and computing kernel version"
|
||||
rfs_common_load_build_kernel_version
|
||||
rfs_common_load_rfs_s3_config
|
||||
rfs_common_build_s3_store_uri
|
||||
rfs_common_locate_rfs
|
||||
|
||||
section "Locating modules directory for ${FULL_KERNEL_VERSION}"
|
||||
rfs_common_locate_modules_dir "${FULL_KERNEL_VERSION}"
|
||||
rfs_common_validate_modules_metadata
|
||||
|
||||
MANIFEST_NAME="modules-${FULL_KERNEL_VERSION}.fl"
|
||||
MANIFEST_PATH="$(rfs_common_prepare_output "${MANIFEST_NAME}")"
|
||||
|
||||
section "Packing modules to flist"
|
||||
log_info "rfs pack -m ${MANIFEST_PATH} -s ${RFS_S3_STORE_URI} ${MODULES_DIR}"
|
||||
safe_execute "${RFS_BIN}" pack -m "${MANIFEST_PATH}" -s "${RFS_S3_STORE_URI}" "${MODULES_DIR}"
|
||||
|
||||
# Patch manifest route URL to include read-only S3 credentials (Garage)
|
||||
section "Updating route URL in manifest to include read-only S3 credentials"
|
||||
rfs_common_build_route_url
|
||||
rfs_common_patch_flist_route_url "${MANIFEST_PATH}"
|
||||
# Patch manifest stores to HTTPS web endpoint if provided
|
||||
if [[ -n "${WEB_ENDPOINT:-}" ]]; then
|
||||
section "Patching manifest stores to HTTPS web endpoint"
|
||||
log_info "Patching ${MANIFEST_PATH} stores to: ${WEB_ENDPOINT} (keep_s3_fallback=${KEEP_S3_FALLBACK:-false})"
|
||||
rfs_common_patch_flist_stores "${MANIFEST_PATH}" "${WEB_ENDPOINT}" "${KEEP_S3_FALLBACK:-false}"
|
||||
else
|
||||
log_warn "WEB_ENDPOINT not set in config; manifest will reference only s3:// store"
|
||||
fi
|
||||
|
||||
# Optional: upload .fl manifest to Garage via MinIO Client (separate from blobs)
|
||||
if [[ "${UPLOAD_MANIFESTS:-false}" == "true" ]]; then
|
||||
section "Uploading manifest .fl via MinIO Client to S3 manifests/"
|
||||
# Support both mcli (new) and mc (legacy) binaries
|
||||
if command -v mcli >/dev/null 2>&1; then
|
||||
MCLI_BIN="mcli"
|
||||
elif command -v mc >/dev/null 2>&1; then
|
||||
MCLI_BIN="mc"
|
||||
else
|
||||
log_warn "MinIO Client not found (expected mcli or mc); skipping manifest upload"
|
||||
MCLI_BIN=""
|
||||
fi
|
||||
|
||||
if [[ -n "${MCLI_BIN}" ]]; then
|
||||
local_subpath="${MANIFESTS_SUBPATH:-manifests}"
|
||||
# Configure alias and upload using MinIO client
|
||||
safe_execute "${MCLI_BIN}" alias set rfs "${S3_ENDPOINT}" "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}"
|
||||
mcli_dst="rfs/${S3_BUCKET}/${S3_PREFIX%/}/${local_subpath%/}/${MANIFEST_NAME}"
|
||||
log_info "${MCLI_BIN} cp ${MANIFEST_PATH} ${mcli_dst}"
|
||||
safe_execute "${MCLI_BIN}" cp "${MANIFEST_PATH}" "${mcli_dst}"
|
||||
fi
|
||||
else
|
||||
log_info "UPLOAD_MANIFESTS=false; skipping manifest upload"
|
||||
fi
|
||||
|
||||
section "Done"
|
||||
log_info "Manifest: ${MANIFEST_PATH}"
|
||||
24
scripts/rfs/patch-stores.sh
Executable file
24
scripts/rfs/patch-stores.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Wrapper to patch an .fl manifest's stores to use an HTTPS web endpoint.
|
||||
# Usage:
|
||||
# ./scripts/rfs/patch-stores.sh dist/flists/modules-6.12.44-Zero-OS.fl https://hub.grid.tf/zos/zosbuilder/store [keep_s3_fallback]
|
||||
#
|
||||
# keep_s3_fallback: "true" to keep existing s3:// store rows as fallback; default "false"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${HERE}/common.sh"
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: $0 /path/to/file.fl https://web.endpoint/base [keep_s3_fallback]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FL="$1"
|
||||
WEB="$2"
|
||||
KEEP="${3:-false}"
|
||||
|
||||
rfs_common_patch_flist_stores "${FL}" "${WEB}" "${KEEP}"
|
||||
echo "[INFO] Patched stores in: ${FL}"
|
||||
65
scripts/rfs/verify-flist.sh
Executable file
65
scripts/rfs/verify-flist.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Verify an RFS flist (.fl) by inspecting, listing tree, and optional mount test.
|
||||
# Usage:
|
||||
# ./scripts/rfs/verify-flist.sh /path/to/foo.fl
|
||||
# ./scripts/rfs/verify-flist.sh /path/to/foo.fl --mount
|
||||
#
|
||||
# Notes:
|
||||
# - Requires the rfs binary (PATH or components fallback).
|
||||
# - --mount performs a temporary mount (needs FUSE and privileges).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${HERE}/common.sh"
|
||||
|
||||
section() { echo -e "\n==== $* ====\n"; }
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 /path/to/foo.fl [--mount]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FL_PATH="$1"
|
||||
DO_MOUNT="${2:-}"
|
||||
|
||||
if [[ ! -f "$FL_PATH" ]]; then
|
||||
echo "[ERROR] flist not found: ${FL_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
section "Locating rfs binary"
|
||||
rfs_common_locate_rfs
|
||||
|
||||
section "Inspect flist"
|
||||
safe_execute "${RFS_BIN}" flist inspect "${FL_PATH}" || true
|
||||
|
||||
section "Tree (first 100 entries)"
|
||||
safe_execute "${RFS_BIN}" flist tree "${FL_PATH}" | head -n 100 || true
|
||||
|
||||
if [[ "$DO_MOUNT" == "--mount" ]]; then
|
||||
section "Attempting temporary mount"
|
||||
MNT="$(mktemp -d /tmp/rfs-mnt-XXXXXX)"
|
||||
cleanup() {
|
||||
set +e
|
||||
if mountpoint -q "${MNT}" 2>/dev/null; then
|
||||
echo "[INFO] Unmounting ${MNT}"
|
||||
fusermount -u "${MNT}" 2>/dev/null || umount "${MNT}" 2>/dev/null || true
|
||||
fi
|
||||
rmdir "${MNT}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "[INFO] Mountpoint: ${MNT}"
|
||||
# Try mount; some environments require sudo or have limited FUSE. Best-effort.
|
||||
if "${RFS_BIN}" mount -m "${FL_PATH}" "${MNT}"; then
|
||||
echo "[INFO] Mounted. Listing top-level entries:"
|
||||
ls -la "${MNT}" | head -n 50 || true
|
||||
else
|
||||
echo "[WARN] rfs mount failed (FUSE/permissions?). Skipping mount verification." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
section "Done"
|
||||
echo "[INFO] Verified flist: ${FL_PATH}"
|
||||
Reference in New Issue
Block a user