This commit is contained in:
@@ -4,6 +4,8 @@ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
export CSR_BUILD_ID
|
||||
CSR_BUILD_ID=$(python3 "$ROOT/scripts/release-manifest.py" --source-id)
|
||||
for component in scanner controller gui; do
|
||||
cargo fmt --check --manifest-path "$ROOT/$component/Cargo.toml"
|
||||
cargo clippy --locked --manifest-path "$ROOT/$component/Cargo.toml" --all-targets -- -D warnings
|
||||
cargo test --locked --manifest-path "$ROOT/$component/Cargo.toml"
|
||||
cargo build --locked --release --manifest-path "$ROOT/$component/Cargo.toml"
|
||||
done
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# DESTRUCTIVE: explicitly invoked, strongly confirmed, never automatic.
|
||||
set -Eeuo pipefail
|
||||
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
DEVICE=${1:-}
|
||||
ISO=${2:-}
|
||||
if [[ ! "$DEVICE" =~ ^/dev/[a-zA-Z0-9/_-]+$ || ! -f "$ISO" ]]; then
|
||||
echo "Usage: sudo bash scripts/install-usb.sh /dev/sdb /absolute/path/to/pc-intake.iso" >&2; exit 2
|
||||
fi
|
||||
[[ $EUID -eq 0 ]] || { echo 'Run as root.' >&2; exit 2; }
|
||||
for cmd in lsblk blkid sgdisk mkfs.vfat mkfs.ext4 grub-install mount umount findmnt wipefs dd sync awk bsdtar blockdev partprobe udevadm; do
|
||||
for cmd in lsblk sgdisk mkfs.vfat mkfs.ext4 grub-install mount umount findmnt wipefs sync bsdtar blockdev partprobe udevadm; do
|
||||
command -v "$cmd" >/dev/null || { echo "Missing: $cmd" >&2; exit 2; }
|
||||
done
|
||||
DEVICE=$(readlink -f "$DEVICE")
|
||||
@@ -24,6 +23,29 @@ size_bytes=$(blockdev --getsize64 "$DEVICE")
|
||||
((size_bytes >= 12*1024*1024*1024)) || { echo 'USB must be at least 12 GiB.' >&2; exit 2; }
|
||||
iso_bytes=$(stat -c %s "$ISO")
|
||||
((iso_bytes < 4294967295)) || { echo 'ISO exceeds the FAT32 single-file limit.' >&2; exit 2; }
|
||||
ISO_LIST=$(mktemp)
|
||||
TMP=
|
||||
cleanup(){
|
||||
if [[ -n $TMP ]]; then
|
||||
umount "$TMP/boot" 2>/dev/null || true
|
||||
umount "$TMP/data" 2>/dev/null || true
|
||||
rmdir "$TMP/boot" "$TMP/data" "$TMP" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$ISO_LIST"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
bsdtar -tf "$ISO" > "$ISO_LIST" 2>/dev/null || {
|
||||
echo 'ISO cannot be read as an archive.' >&2
|
||||
exit 2
|
||||
}
|
||||
grep -qx 'arch/boot/x86_64/vmlinuz-linux' "$ISO_LIST" || {
|
||||
echo 'ISO layout does not match GRUB config (kernel missing).' >&2
|
||||
exit 2
|
||||
}
|
||||
grep -qx 'arch/boot/x86_64/initramfs-linux.img' "$ISO_LIST" || {
|
||||
echo 'ISO layout does not match GRUB config (initramfs missing).' >&2
|
||||
exit 2
|
||||
}
|
||||
model=$(lsblk -dn -o MODEL "$DEVICE" | xargs)
|
||||
serial=$(lsblk -dn -o SERIAL "$DEVICE" | xargs)
|
||||
echo '===== DESTRUCTIVE USB INSTALL ====='
|
||||
@@ -55,8 +77,6 @@ done
|
||||
mkfs.vfat -F32 -n INTAKE_BOOT "$p2"
|
||||
mkfs.ext4 -F -L INTAKE_DATA "$p3"
|
||||
TMP=$(mktemp -d)
|
||||
cleanup(){ umount "$TMP/boot" 2>/dev/null || true; umount "$TMP/data" 2>/dev/null || true; rm -rf "$TMP"; }
|
||||
trap cleanup EXIT
|
||||
mkdir -p "$TMP/boot" "$TMP/data"
|
||||
mount "$p2" "$TMP/boot"; mount "$p3" "$TMP/data"
|
||||
mkdir -p "$TMP/boot/boot/grub" "$TMP/data/reports" "$TMP/data/database" "$TMP/data/checklists" "$TMP/data/reference"
|
||||
@@ -69,7 +89,6 @@ if ! cp "$ISO" "$TMP/boot/pc-intake.iso" || ! sync -f "$TMP/boot"; then
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
# Copy video reference files separately under INTAKE_DATA/reference (optional).
|
||||
cat > "$TMP/boot/boot/grub/grub.cfg" <<'GRUBCFG'
|
||||
set timeout=5
|
||||
set default=0
|
||||
@@ -83,13 +102,6 @@ menuentry 'PC Intake Live USB (offline technician mode)' {
|
||||
menuentry 'Reboot' { reboot }
|
||||
menuentry 'Power off' { halt }
|
||||
GRUBCFG
|
||||
# Ensure expected kernel layout exists *in the built ISO* before proceeding.
|
||||
if ! bsdtar -tf "$ISO" 2>/dev/null | grep -qx 'arch/boot/x86_64/vmlinuz-linux'; then
|
||||
echo 'ISO layout does not match GRUB config (kernel missing). Aborting bootloader setup.' >&2; exit 1
|
||||
fi
|
||||
if ! bsdtar -tf "$ISO" 2>/dev/null | grep -qx 'arch/boot/x86_64/initramfs-linux.img'; then
|
||||
echo 'ISO layout does not match GRUB config (initramfs missing).' >&2; exit 1
|
||||
fi
|
||||
grub-install --target=x86_64-efi --efi-directory="$TMP/boot" --boot-directory="$TMP/boot/boot" --removable --no-nvram --recheck
|
||||
grub-install --target=i386-pc --boot-directory="$TMP/boot/boot" --recheck "$DEVICE"
|
||||
sync
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Stdlib only; server assigns numbers and immutable reports stay unchanged.
|
||||
"""
|
||||
import argparse
|
||||
import contextlib
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import time
|
||||
@@ -116,7 +117,8 @@ def credentials(config):
|
||||
base = config.get('url', '')
|
||||
if not base: raise ValueError('Inventory URL is not configured; reports remain offline')
|
||||
parsed = urllib.parse.urlparse(base)
|
||||
if parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
||||
if (parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password
|
||||
or parsed.path not in ('', '/') or parsed.params or parsed.query or parsed.fragment):
|
||||
raise ValueError('Inventory URL must be an HTTPS origin (e.g. https://inventory.example.org)')
|
||||
key = os.environ.get('CSR_API_KEY', '')
|
||||
if not key:
|
||||
@@ -124,7 +126,7 @@ def credentials(config):
|
||||
key = secret.read_text(encoding='utf-8').strip()
|
||||
if not key:
|
||||
raise ValueError('No API key configured')
|
||||
return base, key
|
||||
return base.rstrip('/'), key
|
||||
|
||||
def connection_error(ex):
|
||||
# Never include server responses, request headers, or raw URLs in errors.
|
||||
@@ -252,6 +254,8 @@ def process_queue(root, db, config, retry=False, selected=None, retry_seconds=60
|
||||
failures += 1
|
||||
# Error strings deliberately exclude URLs, API keys and raw server responses.
|
||||
error = connection_error(ex)
|
||||
if isinstance(ex, urllib.error.HTTPError):
|
||||
ex.close()
|
||||
delay = min(3600, retry_seconds * (2 ** min(attempts, 6)))
|
||||
with db:
|
||||
db.execute("UPDATE inventory_queue SET status='ERROR', error=?, next_attempt=? WHERE report_sha256=?", (error, stamp + delay, sha))
|
||||
@@ -347,7 +351,7 @@ def run_station(root,action,report_path):
|
||||
'hwid':hardware_id(report),'expected_revision':device['revision'],'specifications':map_fields(report)})
|
||||
if action == 'intake' and report_path:
|
||||
raw=report_path.read_bytes(); sha=hashlib.sha256(raw).hexdigest()
|
||||
with sqlite3.connect(root/'database/intake.sqlite3') as db:
|
||||
with contextlib.closing(sqlite3.connect(root/'database/intake.sqlite3')) as db, db:
|
||||
init_db(db);discover_queue(root,db)
|
||||
db.execute("INSERT OR REPLACE INTO inventory_sync(report_sha256,asset_id,inventory_number,status,last_attempt_utc,response) VALUES(?,?,?,'SYNCED',?,'OK')",(sha,report.get('asset_id',''),device['number'],dt.datetime.now(dt.timezone.utc).isoformat()))
|
||||
db.execute("UPDATE inventory_queue SET status='SYNCED',error='',next_attempt=0 WHERE report_sha256=?",(sha,))
|
||||
@@ -388,10 +392,12 @@ def main():
|
||||
return 2
|
||||
if args.station:
|
||||
try: return run_station(root,args.station,args.report)
|
||||
except (OSError,ValueError,KeyError) as ex:
|
||||
except (OSError,ValueError,KeyError,urllib.error.URLError) as ex:
|
||||
print(connection_error(ex),file=sys.stderr)
|
||||
if isinstance(ex, urllib.error.HTTPError):
|
||||
ex.close()
|
||||
return 2
|
||||
with sqlite3.connect(root / 'database' / 'intake.sqlite3', timeout=20) as db:
|
||||
with contextlib.closing(sqlite3.connect(root / 'database' / 'intake.sqlite3', timeout=20)) as db, db:
|
||||
init_db(db)
|
||||
discover_queue(root, db)
|
||||
if args.list:
|
||||
|
||||
Reference in New Issue
Block a user