Commit 107fac31 authored by Andrey Filippov's avatar Andrey Filippov

Add LWIR16 workflow helpers

parent 5db686ca
......@@ -84,6 +84,25 @@ Use known-good `.107` local.conf by default during stage-1 builds:
./scripts/compare_localconf_with_107.sh
```
LWIR16 profile workflow (single entrypoint):
```bash
# list configured profiles (from mmc_profiles.tsv)
./scripts/lwir16_ctl.sh profiles list
# build all lwir16_* profiles and bundle outputs into deploy/images/elphel393/lwir16/
./scripts/lwir16_ctl.sh build
# compare running camera /etc with each profile rootfs.tar.gz
./scripts/lwir16_ctl.sh diff
# save sparse /etc overrides
./scripts/lwir16_ctl.sh save
# after replacing SD cards and rebooting, restore overrides
./scripts/lwir16_ctl.sh restore
```
## References
- Main upstream repository: `git@git.elphel.com:Elphel/elphel393.git` (`warrior` branch)
......
......@@ -99,7 +99,7 @@ These are the two command lines currently used for quick IMU stream checks:
```bash
cltool -c /dev/ttyUSB0 -baud=921600 -stats -did DID_INS_1 DID_GPS1_POS DID_GPS2_POS DID_GPS1_UBX_POS DID_STROBE_IN_TIME DID_PIMU
cltool -c /dev/ttyUSB0 -baud=921600 -did DID_INS_1 DID_GPS1_POS DID_GPS2_POS DID_GPS1_UBX_POS DID_STROBE_IN_TIME DID_PIMU
cltool -c /dev/ttyUSB0 -baud=921600 -did DID_INS_1 DID_INS_2 DID_GPS1_POS DID_GPS2_POS DID_GPS1_UBX_POS DID_STROBE_IN_TIME DID_PIMU
```
Operational notes:
......
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
usage() {
cat <<'EOF'
Usage:
autocampars_sync.sh save [options]
autocampars_sync.sh restore [options]
Description:
Save/restore camera autocampars XML files between camera hosts and local storage.
Default host set:
192.168.0.41 .. 192.168.0.46
Default storage:
<repo>/workspace/bootable-images/versions/<year>/autocampars/
(example: .../versions/2026/autocampars/192.168.0.45/etc/elphel393/)
Options:
--user USER SSH user (default: root)
--prefix A.B.C First 3 octets for generated hosts (default: 192.168.0)
--from N Starting host number (default: 41)
--count N Number of hosts (default: 6)
--hosts "H1 H2 ..." Space-separated host list (IPs or hostnames)
--base-dir DIR Base directory for saved trees
--connect-timeout S SSH/SCP connect timeout in seconds (default: 7)
--dry-run Print commands without executing
-h, --help Show this help
Examples:
autocampars_sync.sh save
autocampars_sync.sh restore
autocampars_sync.sh save --hosts "192.168.0.45"
autocampars_sync.sh restore --hosts "192.168.0.45" --dry-run
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
run_cmd() {
if [[ "${dry_run}" -eq 1 ]]; then
printf 'DRY-RUN:'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
mode="${1:-}"
if [[ -z "${mode}" || "${mode}" == "-h" || "${mode}" == "--help" ]]; then
usage
exit 0
fi
if [[ "${mode}" != "save" && "${mode}" != "restore" ]]; then
echo "ERROR: first argument must be 'save' or 'restore'" >&2
usage
exit 1
fi
shift
ssh_user="root"
host_prefix="192.168.0"
start_octet=41
host_count=6
hosts_csv=""
base_dir="${REPO_ROOT}/workspace/bootable-images/versions/$(date +%Y)/autocampars"
connect_timeout=7
dry_run=0
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
ssh_user="$2"
shift 2
;;
--prefix)
host_prefix="$2"
shift 2
;;
--from)
start_octet="$2"
shift 2
;;
--count)
host_count="$2"
shift 2
;;
--hosts)
hosts_csv="$2"
shift 2
;;
--base-dir)
base_dir="$2"
shift 2
;;
--connect-timeout)
connect_timeout="$2"
shift 2
;;
--dry-run)
dry_run=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: unknown option: $1" >&2
usage
exit 1
;;
esac
done
declare -a hosts=()
if [[ -n "${hosts_csv}" ]]; then
# shellcheck disable=SC2206
hosts=(${hosts_csv})
else
for ((i = 0; i < host_count; i++)); do
hosts+=("${host_prefix}.$((start_octet + i))")
done
fi
if [[ "${#hosts[@]}" -eq 0 ]]; then
echo "ERROR: host list is empty" >&2
exit 1
fi
run_cmd mkdir -p "${base_dir}"
ok=0
fail=0
if [[ "${mode}" == "save" ]]; then
log "Saving autocampars XML files to ${base_dir}"
for host in "${hosts[@]}"; do
target="${ssh_user}@${host}"
dst="${base_dir}/${host}/etc/elphel393"
run_cmd rm -rf "${base_dir:?}/${host}"
run_cmd mkdir -p "${dst}"
log "Host ${host}: listing /etc/elphel393/autocampars*.xml"
mapfile -t remote_files < <(
ssh -o BatchMode=yes -o ConnectTimeout="${connect_timeout}" "${target}" \
"ls -1 /etc/elphel393/autocampars*.xml 2>/dev/null" || true
)
if [[ "${#remote_files[@]}" -eq 0 ]]; then
log "Host ${host}: no files found or host unreachable"
fail=$((fail + 1))
continue
fi
copied=0
for remote_file in "${remote_files[@]}"; do
if run_cmd scp -O -p -o BatchMode=yes -o ConnectTimeout="${connect_timeout}" \
"${target}:${remote_file}" "${dst}/"; then
copied=$((copied + 1))
fi
done
if [[ "${copied}" -gt 0 ]]; then
log "Host ${host}: copied ${copied} file(s)"
ok=$((ok + 1))
else
log "Host ${host}: copy failed"
fail=$((fail + 1))
fi
done
else
log "Restoring autocampars subtree from ${base_dir}"
for host in "${hosts[@]}"; do
target="${ssh_user}@${host}"
src="${base_dir}/${host}/etc/elphel393"
if [[ ! -d "${src}" ]]; then
log "Host ${host}: missing local source dir ${src}"
fail=$((fail + 1))
continue
fi
if ! find "${src}" -mindepth 1 -print -quit | grep -q .; then
log "Host ${host}: local source dir is empty: ${src}"
fail=$((fail + 1))
continue
fi
if ! run_cmd ssh -o BatchMode=yes -o ConnectTimeout="${connect_timeout}" \
"${target}" "mkdir -p /etc/elphel393"; then
log "Host ${host}: unable to prepare remote destination"
fail=$((fail + 1))
continue
fi
if run_cmd scp -O -r -p -o BatchMode=yes -o ConnectTimeout="${connect_timeout}" \
"${src}/." "${target}:/etc/elphel393/"; then
log "Host ${host}: restore completed"
ok=$((ok + 1))
else
log "Host ${host}: restore failed"
fail=$((fail + 1))
fi
done
fi
log "Summary: mode=${mode} ok=${ok} fail=${fail}"
if [[ "${fail}" -gt 0 ]]; then
exit 1
fi
......@@ -5,22 +5,26 @@ set -euo pipefail
# Default targets:
# root@192.168.0.41 ... root@192.168.0.46
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<'EOF'
Usage:
konsole_ssh_2x3.sh [user@host ...]
konsole_ssh_2x3.sh [--user USER] [--prefix A.B.C] [--from N] [--dmesg]
konsole_ssh_2x3.sh [--user USER] [--prefix A.B.C] [--from N] [--dmesg] [--new]
Options:
--user USER SSH user for generated host list (default: root)
--prefix A.B.C First 3 octets for generated host list (default: 192.168.0)
--from N Starting host number, creates 6 hosts N..N+5 (default: 41)
--dmesg Run 'dmesg' on remote after SSH login, then keep interactive shell
--new Reset known_hosts and run ssh-copy-id for selected targets first
-h, --help Show this help
Examples:
konsole_ssh_2x3.sh
konsole_ssh_2x3.sh --dmesg
konsole_ssh_2x3.sh --new
konsole_ssh_2x3.sh --user root --prefix 192.168.0 --from 41
konsole_ssh_2x3.sh root@192.168.0.41 root@192.168.0.42 root@192.168.0.43 \
root@192.168.0.44 root@192.168.0.45 root@192.168.0.46
......@@ -46,6 +50,7 @@ ssh_user="root"
host_prefix="192.168.0"
start_octet=41
run_dmesg=0
prepare_new=0
declare -a targets=()
while [[ $# -gt 0 ]]; do
......@@ -66,6 +71,10 @@ while [[ $# -gt 0 ]]; do
run_dmesg=1
shift
;;
--new)
prepare_new=1
shift
;;
-h|--help)
usage
exit 0
......@@ -101,6 +110,27 @@ if [[ "${#targets[@]}" -ne 6 ]]; then
exit 1
fi
if [[ "$prepare_new" -eq 1 ]]; then
reset_script="${SCRIPT_DIR}/reset_camera_ssh_keys.sh"
if [[ ! -x "$reset_script" ]]; then
echo "ERROR: missing helper script: $reset_script" >&2
exit 1
fi
log "Preparing SSH trust for selected targets (--new)"
for target in "${targets[@]}"; do
if [[ "$target" == *@* ]]; then
prep_user="${target%@*}"
prep_host="${target#*@}"
else
prep_user="$ssh_user"
prep_host="$target"
fi
log " reset+copy: ${prep_user}@${prep_host}"
"$reset_script" --user "$prep_user" --hosts "$prep_host"
done
fi
layout_file="$(mktemp --tmpdir konsole_2x3_layout_XXXXXX.json)"
trap 'rm -f "$layout_file"' EXIT
......
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
WORKSPACE="${REPO_ROOT}/workspace"
BUILD_CONF_DIR="${WORKSPACE}/poky/build/conf"
DEPLOY_DIR="${WORKSPACE}/poky/build/tmp/deploy/images/elphel393"
CATALOG_DEFAULT="${BUILD_CONF_DIR}/mmc_profiles.tsv"
OUT_DIR_DEFAULT="${DEPLOY_DIR}/lwir16"
TARGET_DEFAULT="core-image-elphel393"
usage() {
cat <<'EOF'
Usage:
lwir16_build_bundle.sh [options]
Description:
Build and bundle LWIR16 image sets by iterating over lwir16_* profiles from
mmc_profiles.tsv. For each profile:
1) copy profile local.conf -> build/conf/local.conf
2) run bitbake (unless --skip-build)
3) copy deploy/mmc -> deploy/lwir16/mmc_<host_octet>
4) copy deploy/nand -> deploy/lwir16/nand_<host_octet> (if exists)
Options:
--catalog PATH Profile catalog TSV (default: build/conf/mmc_profiles.tsv)
--out-dir PATH Output directory (default: deploy/images/elphel393/lwir16)
--target TARGET BitBake target (default: core-image-elphel393)
--profiles "N1 N2" Space-separated profile names to process
--skip-build Do not run bitbake, only bundle from existing deploy files
--no-nand Do not copy nand artifacts
--clean-out Remove --out-dir before processing
--dry-run Print commands without executing
-h, --help Show this help
Examples:
lwir16_build_bundle.sh
lwir16_build_bundle.sh --profiles "lwir16_boson640_41 lwir16_parallel_45"
lwir16_build_bundle.sh --skip-build --clean-out
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
die() {
echo "ERROR: $*" >&2
exit 1
}
run_cmd() {
if [[ "${DRY_RUN}" -eq 1 ]]; then
printf 'DRY-RUN:'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
write_sums_if_possible() {
local dir="$1"
if [[ ! -d "$dir" ]]; then
return 0
fi
(
cd "$dir"
if [[ -f boot.bin && -f u-boot-dtb.img && -f devicetree.dtb && -f uImage ]]; then
if [[ -f rootfs.tar.gz ]]; then
sha256sum boot.bin u-boot-dtb.img devicetree.dtb uImage rootfs.tar.gz >SHA256SUMS
elif [[ -f rootfs.ubi && -f rootfs.ubifs ]]; then
sha256sum boot.bin u-boot-dtb.img devicetree.dtb uImage rootfs.ubi rootfs.ubifs >SHA256SUMS
fi
fi
)
}
extract_remote_ip() {
local conf_file="$1"
awk -F'"' '/^[[:space:]]*REMOTE_IP[[:space:]]*\?=/{print $2; exit}' "$conf_file"
}
extract_host_octet() {
local conf_file="$1"
local ip
ip="$(extract_remote_ip "$conf_file")"
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}([0-9]{1,3})$ ]]; then
echo "${BASH_REMATCH[2]}"
return 0
fi
return 1
}
CATALOG="${CATALOG_DEFAULT}"
OUT_DIR="${OUT_DIR_DEFAULT}"
TARGET="${TARGET_DEFAULT}"
PROFILES=""
SKIP_BUILD=0
NO_NAND=0
CLEAN_OUT=0
DRY_RUN=0
while [[ $# -gt 0 ]]; do
case "$1" in
--catalog)
CATALOG="$2"
shift 2
;;
--out-dir)
OUT_DIR="$2"
shift 2
;;
--target)
TARGET="$2"
shift 2
;;
--profiles)
PROFILES="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=1
shift
;;
--no-nand)
NO_NAND=1
shift
;;
--clean-out)
CLEAN_OUT=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
CATALOG="${CATALOG/#\~/${HOME}}"
OUT_DIR="${OUT_DIR/#\~/${HOME}}"
[[ -f "${CATALOG}" ]] || die "catalog not found: ${CATALOG}"
[[ -f "${BUILD_CONF_DIR}/local.conf" ]] || die "missing ${BUILD_CONF_DIR}/local.conf"
[[ -d "${DEPLOY_DIR}/mmc" ]] || die "missing deploy mmc directory: ${DEPLOY_DIR}/mmc"
orig_local_conf="$(mktemp)"
if [[ "${DRY_RUN}" -eq 0 ]]; then
cp -f "${BUILD_CONF_DIR}/local.conf" "${orig_local_conf}"
fi
cleanup() {
if [[ "${DRY_RUN}" -eq 0 && -f "${orig_local_conf}" ]]; then
cp -f "${orig_local_conf}" "${BUILD_CONF_DIR}/local.conf" || true
fi
rm -f "${orig_local_conf}" || true
}
trap cleanup EXIT
if [[ "${CLEAN_OUT}" -eq 1 ]]; then
run_cmd rm -rf "${OUT_DIR}"
fi
run_cmd mkdir -p "${OUT_DIR}"
index_file="${OUT_DIR}/index.tsv"
run_cmd bash -lc "printf 'slot\\tprofile\\tremote_ip\\tlocal_conf\\tbuilt_at\\tmmc_dir\\tnand_dir\\tnotes\\n' > '$index_file'"
declare -a selected_profiles=()
if [[ -n "${PROFILES}" ]]; then
# shellcheck disable=SC2206
selected_profiles=(${PROFILES})
else
mapfile -t selected_profiles < <(awk -F'\t' 'NR>1 && $1 ~ /^lwir16_/ {print $1}' "${CATALOG}")
fi
[[ "${#selected_profiles[@]}" -gt 0 ]] || die "no profiles selected"
total_profiles="${#selected_profiles[@]}"
idx=0
declare -A used_slots=()
ok=0
fail=0
for profile in "${selected_profiles[@]}"; do
idx=$((idx + 1))
line="$(awk -F'\t' -v n="${profile}" 'NR>1 && $1==n {print; exit}' "${CATALOG}")"
if [[ -z "${line}" ]]; then
log "[${idx}/${total_profiles}] Profile not found in catalog: ${profile}"
fail=$((fail + 1))
continue
fi
IFS=$'\t' read -r p_name p_local_conf p_mmc_dir p_created p_notes <<<"${line}"
p_local_conf="${p_local_conf/#\~/${HOME}}"
[[ -f "${p_local_conf}" ]] || {
log "[${idx}/${total_profiles}] Missing local.conf for ${p_name}: ${p_local_conf}"
fail=$((fail + 1))
continue
}
remote_ip="$(extract_remote_ip "${p_local_conf}" || true)"
host_octet="$(extract_host_octet "${p_local_conf}" || true)"
if [[ -z "${host_octet}" ]]; then
host_octet="${p_name}"
fi
slot="mmc_${host_octet}"
if [[ -n "${used_slots[${slot}]:-}" ]]; then
slot="${slot}_${p_name}"
fi
used_slots["${slot}"]=1
log "[${idx}/${total_profiles}] Processing ${p_name} (REMOTE_IP=${remote_ip:-unknown}, slot=${slot})"
run_cmd cp -f "${p_local_conf}" "${BUILD_CONF_DIR}/local.conf"
if [[ "${SKIP_BUILD}" -eq 0 ]]; then
run_cmd "${SCRIPT_DIR}/run_docker.sh" bash -lc "
set -e
cd /work/elphel393/poky
set +u
. ./oe-init-build-env build
set -u
export DL_DIR=/cache/downloads
export SSTATE_DIR=/cache/sstate-cache
bitbake ${TARGET}
"
fi
mmc_slot_dir="${OUT_DIR}/${slot}"
run_cmd rm -rf "${mmc_slot_dir}"
run_cmd mkdir -p "${mmc_slot_dir}"
run_cmd rsync -a --delete "${DEPLOY_DIR}/mmc/." "${mmc_slot_dir}/"
run_cmd cp -f "${p_local_conf}" "${mmc_slot_dir}/local.conf"
run_cmd bash -lc "printf 'profile=%s\nremote_ip=%s\nbuilt_at=%s\ntarget=%s\nnotes=%s\n' \
'${p_name}' '${remote_ip:-}' '$(date -Is)' '${TARGET}' '${p_notes}' > '${mmc_slot_dir}/PROFILE.txt'"
if [[ "${DRY_RUN}" -eq 0 ]]; then
write_sums_if_possible "${mmc_slot_dir}"
fi
nand_slot_dir=""
if [[ "${NO_NAND}" -eq 0 && -d "${DEPLOY_DIR}/nand" ]]; then
nand_slot_dir="${OUT_DIR}/nand_${host_octet}"
run_cmd rm -rf "${nand_slot_dir}"
run_cmd mkdir -p "${nand_slot_dir}"
run_cmd rsync -a --delete "${DEPLOY_DIR}/nand/." "${nand_slot_dir}/"
run_cmd cp -f "${p_local_conf}" "${nand_slot_dir}/local.conf"
run_cmd bash -lc "printf 'profile=%s\nremote_ip=%s\nbuilt_at=%s\ntarget=%s\nnotes=%s\n' \
'${p_name}' '${remote_ip:-}' '$(date -Is)' '${TARGET}' '${p_notes}' > '${nand_slot_dir}/PROFILE.txt'"
if [[ "${DRY_RUN}" -eq 0 ]]; then
write_sums_if_possible "${nand_slot_dir}"
fi
fi
run_cmd bash -lc "printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
'${slot}' '${p_name}' '${remote_ip:-}' '${p_local_conf}' '$(date -Is)' \
'${mmc_slot_dir}' '${nand_slot_dir}' '${p_notes}' >> '${index_file}'"
log "[${idx}/${total_profiles}] Completed ${p_name}"
ok=$((ok + 1))
done
log "Done: ok=${ok} fail=${fail}"
if [[ "${fail}" -gt 0 ]]; then
exit 1
fi
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
WORKSPACE="${REPO_ROOT}/workspace"
BUILD_CONF_DIR="${WORKSPACE}/poky/build/conf"
DEFAULT_CATALOG="${BUILD_CONF_DIR}/mmc_profiles.tsv"
usage() {
cat <<'EOF'
Usage:
lwir16_profile_catalog.sh <command> [options]
Commands:
init Create catalog file if missing
list List catalog entries
show Show one profile
register Register existing local.conf + mmc directory
snapshot Copy current mmc directory to versions/, store local.conf, register
activate Copy selected profile local.conf to build/conf/local.conf
remove Remove profile entry from catalog
Common options:
--catalog PATH Catalog path (default: workspace/poky/build/conf/mmc_profiles.tsv)
-h, --help Show this help
Command options:
show/remove/activate:
--name NAME
register:
--name NAME
--local-conf PATH
--mmc-dir PATH
--notes TEXT
--replace
snapshot:
--name NAME
--local-conf PATH (default: workspace/poky/build/conf/local.conf)
--mmc-src PATH (default: workspace/bootable-images/mmc)
--dest-root PATH (default: workspace/bootable-images/versions/<year>)
--notes TEXT
--replace
Examples:
lwir16_profile_catalog.sh init
lwir16_profile_catalog.sh register --name lwir16_boson640_41 \
--local-conf /work/elphel393/poky/build/conf/versions_107/local_boson_41.conf \
--mmc-dir /home/elphel/git/imagej-elphel/attic/elphel393-docker/workspace/bootable-images/versions/2026/mmc_boson640_41-2026
lwir16_profile_catalog.sh list
lwir16_profile_catalog.sh activate --name lwir16_boson640_41
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
normalize_path() {
local p="${1:-}"
p="${p/#\~/${HOME}}"
if [[ "$p" == /work/elphel393/* ]]; then
printf '%s\n' "${WORKSPACE}/${p#/work/elphel393/}"
else
printf '%s\n' "$p"
fi
}
abspath() {
local p
p="$(normalize_path "$1")"
if [[ "$p" != /* ]]; then
p="$(pwd)/$p"
fi
p="${p%/}"
p="${p//\/\//\/}"
printf '%s\n' "$p"
}
ensure_catalog() {
local cat_file="$1"
mkdir -p "$(dirname "$cat_file")"
if [[ ! -f "$cat_file" || ! -s "$cat_file" ]]; then
printf 'name\tlocal_conf\tmmc_dir\tcreated_at\tnotes\n' >"$cat_file"
return
fi
local first
first="$(head -n1 "$cat_file" || true)"
if [[ "$first" != $'name\tlocal_conf\tmmc_dir\tcreated_at\tnotes' ]]; then
die "catalog header mismatch in $cat_file"
fi
}
profile_line_by_name() {
local cat_file="$1"
local name="$2"
awk -F'\t' -v n="$name" 'NR>1 && $1==n {print; exit}' "$cat_file"
}
sanitize_notes() {
local n="${1:-}"
n="${n//$'\t'/ }"
n="${n//$'\n'/ }"
printf '%s\n' "$n"
}
upsert_profile() {
local cat_file="$1"
local name="$2"
local local_conf="$3"
local mmc_dir="$4"
local created_at="$5"
local notes="$6"
local tmp
tmp="$(mktemp)"
awk -F'\t' -v OFS='\t' \
-v n="$name" -v lc="$local_conf" -v md="$mmc_dir" -v ct="$created_at" -v no="$notes" '
NR==1 {print; next}
$1==n {print n,lc,md,ct,no; found=1; next}
{print}
END {if (!found) print n,lc,md,ct,no}
' "$cat_file" >"$tmp"
mv "$tmp" "$cat_file"
}
remove_profile() {
local cat_file="$1"
local name="$2"
local tmp
tmp="$(mktemp)"
awk -F'\t' -v OFS='\t' -v n="$name" '
NR==1 {print; next}
$1==n {removed=1; next}
{print}
END {if (!removed) exit 3}
' "$cat_file" >"$tmp" || {
local rc=$?
rm -f "$tmp"
if [[ "$rc" -eq 3 ]]; then
die "profile not found: $name"
fi
exit "$rc"
}
mv "$tmp" "$cat_file"
}
validate_local_conf() {
local lc="$1"
[[ -f "$lc" ]] || die "local.conf file not found: $lc"
}
validate_mmc_dir() {
local md="$1"
[[ -d "$md" ]] || die "mmc directory not found: $md"
local req
for req in boot.bin u-boot-dtb.img devicetree.dtb uImage rootfs.tar.gz; do
[[ -f "$md/$req" ]] || die "missing $req in $md"
done
}
write_sha256sums() {
local md="$1"
(
cd "$md"
sha256sum boot.bin u-boot-dtb.img devicetree.dtb uImage rootfs.tar.gz >SHA256SUMS
)
}
command_name="${1:-}"
if [[ -z "$command_name" || "$command_name" == "-h" || "$command_name" == "--help" ]]; then
usage
exit 0
fi
shift
catalog="$DEFAULT_CATALOG"
name=""
local_conf=""
mmc_dir=""
mmc_src="${WORKSPACE}/bootable-images/mmc"
dest_root="${WORKSPACE}/bootable-images/versions/$(date +%Y)"
notes=""
replace=0
while [[ $# -gt 0 ]]; do
case "$1" in
--catalog)
catalog="$2"
shift 2
;;
--name)
name="$2"
shift 2
;;
--local-conf)
local_conf="$2"
shift 2
;;
--mmc-dir)
mmc_dir="$2"
shift 2
;;
--mmc-src)
mmc_src="$2"
shift 2
;;
--dest-root)
dest_root="$2"
shift 2
;;
--notes)
notes="$2"
shift 2
;;
--replace)
replace=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
catalog="$(abspath "$catalog")"
notes="$(sanitize_notes "$notes")"
case "$command_name" in
init)
ensure_catalog "$catalog"
log "Catalog ready: $catalog"
;;
list)
ensure_catalog "$catalog"
if command -v column >/dev/null 2>&1; then
column -ts $'\t' "$catalog"
else
cat "$catalog"
fi
;;
show)
ensure_catalog "$catalog"
[[ -n "$name" ]] || die "--name is required"
line="$(profile_line_by_name "$catalog" "$name" || true)"
[[ -n "${line:-}" ]] || die "profile not found: $name"
IFS=$'\t' read -r p_name p_lc p_md p_ct p_no <<<"$line"
printf 'name: %s\n' "$p_name"
printf 'local_conf: %s\n' "$p_lc"
printf 'mmc_dir: %s\n' "$p_md"
printf 'created_at: %s\n' "$p_ct"
printf 'notes: %s\n' "$p_no"
;;
remove)
ensure_catalog "$catalog"
[[ -n "$name" ]] || die "--name is required"
remove_profile "$catalog" "$name"
log "Removed profile: $name"
;;
register)
ensure_catalog "$catalog"
[[ -n "$name" ]] || die "--name is required"
[[ -n "$local_conf" ]] || die "--local-conf is required"
[[ -n "$mmc_dir" ]] || die "--mmc-dir is required"
local_conf="$(abspath "$local_conf")"
mmc_dir="$(abspath "$mmc_dir")"
validate_local_conf "$local_conf"
validate_mmc_dir "$mmc_dir"
write_sha256sums "$mmc_dir"
existing="$(profile_line_by_name "$catalog" "$name" || true)"
if [[ -n "$existing" && "$replace" -ne 1 ]]; then
die "profile '$name' already exists (use --replace to overwrite)"
fi
created_at="$(date -Is)"
upsert_profile "$catalog" "$name" "$local_conf" "$mmc_dir" "$created_at" "$notes"
log "Registered profile: $name"
;;
snapshot)
ensure_catalog "$catalog"
[[ -n "$name" ]] || die "--name is required"
if [[ -z "$local_conf" ]]; then
local_conf="${BUILD_CONF_DIR}/local.conf"
fi
local_conf="$(abspath "$local_conf")"
mmc_src="$(abspath "$mmc_src")"
dest_root="$(abspath "$dest_root")"
validate_local_conf "$local_conf"
validate_mmc_dir "$mmc_src"
snapshot_dir="${dest_root}/${name}"
if [[ -e "$snapshot_dir" ]]; then
if [[ "$replace" -eq 1 ]]; then
rm -rf "$snapshot_dir"
else
die "snapshot destination exists: $snapshot_dir (use --replace)"
fi
fi
mkdir -p "$snapshot_dir"
cp -a "${mmc_src}/." "$snapshot_dir/"
cp -f "$local_conf" "$snapshot_dir/local.conf"
write_sha256sums "$snapshot_dir"
existing="$(profile_line_by_name "$catalog" "$name" || true)"
if [[ -n "$existing" && "$replace" -ne 1 ]]; then
die "profile '$name' already exists (use --replace)"
fi
created_at="$(date -Is)"
upsert_profile "$catalog" "$name" "$snapshot_dir/local.conf" "$snapshot_dir" "$created_at" "$notes"
log "Snapshot created: $snapshot_dir"
log "Registered profile: $name"
;;
activate)
ensure_catalog "$catalog"
[[ -n "$name" ]] || die "--name is required"
line="$(profile_line_by_name "$catalog" "$name" || true)"
[[ -n "${line:-}" ]] || die "profile not found: $name"
IFS=$'\t' read -r p_name p_lc p_md p_ct p_no <<<"$line"
validate_local_conf "$p_lc"
validate_mmc_dir "$p_md"
cp -f "$p_lc" "${BUILD_CONF_DIR}/local.conf"
log "Activated profile: $p_name"
log "local.conf <- $p_lc"
log "mmc dir $p_md"
;;
*)
die "unknown command: $command_name"
;;
esac
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "${SCRIPT_DIR}/lwir16_profile_catalog.sh" "$@"
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
reset_camera_ssh_keys.sh [host ...]
reset_camera_ssh_keys.sh [--user USER] [--password PASS] [--prefix A.B.C] [--from N] [--count N]
reset_camera_ssh_keys.sh [--user USER] [--password PASS] [--hosts "H1 H2 ..."]
Description:
1) Removes known-host entries for selected hosts from ~/.ssh/known_hosts
2) Runs ssh-copy-id for each host (default credentials: root/pass)
Defaults:
user = root
password = pass
prefix = 192.168.0
from = 41
count = 6 (hosts .41 .. .46)
Options:
--user USER SSH user (default: root)
--password PASS Password used by sshpass (default: pass)
--prefix A.B.C First 3 octets for generated hosts (default: 192.168.0)
--from N Start host number (default: 41)
--count N Number of hosts (default: 6)
--hosts "H1 H2 ..." Space-separated host list (IPs or hostnames)
--known-hosts PATH known_hosts file path (default: ~/.ssh/known_hosts)
--connect-timeout S SSH connect timeout seconds (default: 7)
--no-copy Only remove known hosts, do not run ssh-copy-id
-h, --help Show this help
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "ERROR: required command not found: $1" >&2
exit 1
}
}
need_cmd ssh-keygen
need_cmd ssh-copy-id
ssh_user="root"
ssh_pass="pass"
prefix="192.168.0"
start_octet=41
count=6
hosts_csv=""
known_hosts="${HOME}/.ssh/known_hosts"
connect_timeout=7
do_copy=1
declare -a explicit_hosts=()
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
ssh_user="$2"
shift 2
;;
--password)
ssh_pass="$2"
shift 2
;;
--prefix)
prefix="$2"
shift 2
;;
--from)
start_octet="$2"
shift 2
;;
--count)
count="$2"
shift 2
;;
--hosts)
hosts_csv="$2"
shift 2
;;
--known-hosts)
known_hosts="$2"
shift 2
;;
--connect-timeout)
connect_timeout="$2"
shift 2
;;
--no-copy)
do_copy=0
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
explicit_hosts+=("$1")
shift
done
;;
-*)
echo "ERROR: unknown option: $1" >&2
usage
exit 1
;;
*)
explicit_hosts+=("$1")
shift
;;
esac
done
declare -a hosts=()
if [[ -n "$hosts_csv" ]]; then
# shellcheck disable=SC2206
hosts=($hosts_csv)
elif [[ "${#explicit_hosts[@]}" -gt 0 ]]; then
hosts=("${explicit_hosts[@]}")
else
for ((i = 0; i < count; i++)); do
hosts+=("${prefix}.$((start_octet + i))")
done
fi
if [[ "${#hosts[@]}" -eq 0 ]]; then
echo "ERROR: empty host list" >&2
exit 1
fi
mkdir -p "$(dirname "$known_hosts")"
touch "$known_hosts"
chmod 600 "$known_hosts"
have_sshpass=0
if command -v sshpass >/dev/null 2>&1; then
have_sshpass=1
fi
if [[ "$do_copy" -eq 1 ]] && [[ "$have_sshpass" -eq 0 ]]; then
log "sshpass is not installed, ssh-copy-id will prompt for password interactively."
fi
declare -a failed=()
declare -a ok=()
for host in "${hosts[@]}"; do
target="${ssh_user}@${host}"
log "Reset known_hosts entry for ${host}"
ssh-keygen -R "${host}" -f "${known_hosts}" >/dev/null 2>&1 || true
ssh-keygen -R "[${host}]:22" -f "${known_hosts}" >/dev/null 2>&1 || true
if [[ "$do_copy" -eq 0 ]]; then
ok+=("${host} (known_hosts only)")
continue
fi
log "Install SSH key to ${target}"
if [[ "$have_sshpass" -eq 1 ]]; then
if sshpass -p "${ssh_pass}" ssh-copy-id -f \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile="${known_hosts}" \
-o ConnectTimeout="${connect_timeout}" \
"${target}" >/dev/null 2>&1; then
ok+=("${host}")
else
failed+=("${host}")
fi
else
if ssh-copy-id -f \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile="${known_hosts}" \
-o ConnectTimeout="${connect_timeout}" \
"${target}"; then
ok+=("${host}")
else
failed+=("${host}")
fi
fi
done
log "Summary: ok=${#ok[@]} failed=${#failed[@]}"
if [[ "${#ok[@]}" -gt 0 ]]; then
printf ' OK: %s\n' "${ok[@]}"
fi
if [[ "${#failed[@]}" -gt 0 ]]; then
printf ' FAIL: %s\n' "${failed[@]}" >&2
exit 1
fi
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
syncall.sh [host ...]
syncall.sh [--user USER] [--prefix A.B.C] [--from N] [--count N]
syncall.sh [--user USER] [--hosts "H1 H2 ..."]
Description:
Run "sync; sync; sync" on selected camera hosts over SSH.
Use this before power-cycling cameras after remote writes.
Defaults:
user = root
prefix = 192.168.0
from = 41
count = 6 (hosts .41 .. .46)
Options:
--user USER SSH user (default: root)
--prefix A.B.C First 3 octets for generated hosts (default: 192.168.0)
--from N Start host number (default: 41)
--count N Number of hosts (default: 6)
--hosts "H1 H2 ..." Space-separated host list (IPs or hostnames)
--connect-timeout S SSH connect timeout seconds (default: 7)
--dry-run Print commands without executing
-h, --help Show this help
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
run_cmd() {
if [[ "${DRY_RUN}" -eq 1 ]]; then
printf 'DRY-RUN:'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "ERROR: required command not found: $1" >&2
exit 1
}
}
need_cmd ssh
ssh_user="root"
prefix="192.168.0"
start_octet=41
count=6
hosts_csv=""
connect_timeout=7
DRY_RUN=0
declare -a explicit_hosts=()
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
ssh_user="$2"
shift 2
;;
--prefix)
prefix="$2"
shift 2
;;
--from)
start_octet="$2"
shift 2
;;
--count)
count="$2"
shift 2
;;
--hosts)
hosts_csv="$2"
shift 2
;;
--connect-timeout)
connect_timeout="$2"
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
explicit_hosts+=("$1")
shift
done
;;
-*)
echo "ERROR: unknown option: $1" >&2
usage
exit 1
;;
*)
explicit_hosts+=("$1")
shift
;;
esac
done
declare -a hosts=()
if [[ -n "$hosts_csv" ]]; then
# shellcheck disable=SC2206
hosts=($hosts_csv)
elif [[ "${#explicit_hosts[@]}" -gt 0 ]]; then
hosts=("${explicit_hosts[@]}")
else
for ((i = 0; i < count; i++)); do
hosts+=("${prefix}.$((start_octet + i))")
done
fi
if [[ "${#hosts[@]}" -eq 0 ]]; then
echo "ERROR: empty host list" >&2
exit 1
fi
declare -a ok=()
declare -a failed=()
for host in "${hosts[@]}"; do
target="${ssh_user}@${host}"
log "Syncing ${target}"
if run_cmd ssh -o BatchMode=yes -o ConnectTimeout="${connect_timeout}" \
"${target}" "sync; sync; sync"; then
ok+=("${host}")
else
failed+=("${host}")
fi
done
log "Summary: ok=${#ok[@]} failed=${#failed[@]}"
if [[ "${#ok[@]}" -gt 0 ]]; then
printf ' OK: %s\n' "${ok[@]}"
fi
if [[ "${#failed[@]}" -gt 0 ]]; then
printf ' FAIL: %s\n' "${failed[@]}" >&2
exit 1
fi
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment