Monorepo for Aesthetic.Computer aesthetic.computer
at main 238 lines 11 kB view raw
1#!/bin/bash 2# ac-usb — List and inspect USB devices through Docker (for devcontainer environments) 3# Usage: 4# ac-usb List all USB block devices 5# ac-usb ls Same as above 6# ac-usb read PATH Read a file from USB partition (e.g., ac-usb read EFI/BOOT/BOOTX64.EFI) 7# ac-usb logs Pull /logs/ and /perf/ from USB /mnt partition 8# ac-usb sha256 Show SHA256 of BOOTX64.EFI on USB 9set -e 10 11CMD="${1:-ls}" 12 13find_usb_dev() { 14 for dev in sda sdb sdc; do 15 if [ -e "/sys/block/${dev}/removable" ]; then 16 local rem=$(cat "/sys/block/${dev}/removable" 2>/dev/null) 17 if [ "${rem}" = "1" ] && [ -e "/sys/block/${dev}" ]; then 18 echo "/dev/${dev}" 19 return 0 20 fi 21 fi 22 done 23 if [ -e "/sys/block/sda" ]; then 24 echo "/dev/sda" 25 return 0 26 fi 27 return 1 28} 29 30docker_usb() { 31 # Run a command inside a privileged container with USB access 32 sudo docker run --rm --privileged -v /dev:/dev alpine:latest sh -c "$1" 2>&1 33} 34 35case "${CMD}" in 36 ls|list) 37 echo "=== USB Block Devices ===" 38 lsblk 2>/dev/null | grep -E "^sd|─sd" || echo "(none found)" 39 echo "" 40 USB_DEV="$(find_usb_dev 2>/dev/null)" || { echo "No removable USB detected"; exit 0; } 41 echo "USB device: ${USB_DEV}" 42 echo "" 43 echo "=== Partition contents ===" 44 docker_usb " 45 apk add --quiet dosfstools 46 mkdir -p /mnt/usb 47 mounted='' 48 for p in ${USB_DEV}1 ${USB_DEV}2; do 49 [ -b \"\$p\" ] && mount \"\$p\" /mnt/usb 2>/dev/null && { mounted=\"\$p\"; break; } 50 done 51 [ -n \"\$mounted\" ] && { 52 echo \"--- \$mounted ---\" 53 find /mnt/usb -type f | head -50 54 echo '' 55 du -sh /mnt/usb/EFI/BOOT/BOOTX64.EFI 2>/dev/null 56 umount /mnt/usb 57 } || echo 'Could not mount ${USB_DEV}1 or ${USB_DEV}2' 58 " 59 ;; 60 sha256|hash) 61 USB_DEV="$(find_usb_dev)" || { echo "No USB found"; exit 1; } 62 docker_usb " 63 apk add --quiet dosfstools 64 mkdir -p /mnt/usb 65 mounted='' 66 for p in ${USB_DEV}1 ${USB_DEV}2; do 67 [ -b \"\$p\" ] && mount \"\$p\" /mnt/usb 2>/dev/null && { mounted=\"\$p\"; break; } 68 done 69 [ -z \"\$mounted\" ] && { echo 'Could not mount USB'; exit 1; } 70 sha256sum /mnt/usb/EFI/BOOT/BOOTX64.EFI 2>/dev/null || echo 'No EFI file found' 71 umount /mnt/usb 72 " 73 ;; 74 logs) 75 USB_DEV="$(find_usb_dev)" || { echo "No USB found"; exit 1; } 76 mkdir -p /tmp/usb-logs 77 CONTAINER=$(sudo docker create --privileged -v /dev:/dev alpine:latest sh -c " 78 apk add --quiet dosfstools 79 mkdir -p /mnt/usb /out 80 # Try ESP (sda1) first — that's where ac-native writes logs 81 mounted='' 82 for p in ${USB_DEV}1 ${USB_DEV}2; do 83 [ -b \"\$p\" ] && mount \"\$p\" /mnt/usb 2>/dev/null && { mounted=\"\$p\"; break; } 84 done 85 [ -z \"\$mounted\" ] && { echo 'Could not mount USB'; exit 1; } 86 echo \"Mounted \$mounted\" 87 ls -la /mnt/usb/ 88 # Copy logs and perf data 89 cp /mnt/usb/ac-native.log /out/ 2>/dev/null || true 90 cp -r /mnt/usb/perf /out/ 2>/dev/null || true 91 cp /mnt/usb/config.json /out/ 2>/dev/null || true 92 cp /mnt/usb/booted-version /out/ 2>/dev/null || true 93 cp /mnt/usb/dmesg.log /out/ 2>/dev/null || true 94 cp /mnt/usb/init.log /out/ 2>/dev/null || true 95 ls -la /out/ 96 umount /mnt/usb 97 ") 98 sudo docker start -a "${CONTAINER}" 99 sudo docker cp "${CONTAINER}:/out/." /tmp/usb-logs/ 2>/dev/null 100 sudo docker rm "${CONTAINER}" 2>/dev/null 101 echo "" 102 echo "Logs copied to /tmp/usb-logs/" 103 ls -la /tmp/usb-logs/ 104 ;; 105 read) 106 FILE="${2:?Usage: ac-usb read <path>}" 107 USB_DEV="$(find_usb_dev)" || { echo "No USB found"; exit 1; } 108 docker_usb " 109 apk add --quiet dosfstools 110 mkdir -p /mnt/usb 111 mounted='' 112 for p in ${USB_DEV}1 ${USB_DEV}2; do 113 [ -b \"\$p\" ] && mount \"\$p\" /mnt/usb 2>/dev/null && { mounted=\"\$p\"; break; } 114 done 115 [ -z \"\$mounted\" ] && { echo 'Could not mount USB'; exit 1; } 116 cat '/mnt/usb/${FILE}' 2>/dev/null || echo 'File not found: ${FILE}' 117 umount /mnt/usb 118 " 119 ;; 120 flash) 121 VMLINUZ="${2:-}" 122 if [ -z "${VMLINUZ}" ]; then 123 echo "Downloading latest OTA build..." 124 VMLINUZ="/tmp/ac-native-latest.vmlinuz" 125 curl -fSL -o "${VMLINUZ}" \ 126 "https://releases-aesthetic-computer.sfo3.digitaloceanspaces.com/os/native-notepat-latest.vmlinuz" 127 fi 128 [ -f "${VMLINUZ}" ] || { echo "File not found: ${VMLINUZ}"; exit 1; } 129 USB_DEV="$(find_usb_dev)" || { echo "No USB found"; exit 1; } 130 SIZE=$(wc -c < "${VMLINUZ}") 131 echo "Flashing ${VMLINUZ} ($(( SIZE / 1024 / 1024 ))MB) to ${USB_DEV}..." 132 SPLASH_EFI="$(dirname "$0")/bootloader/splash.efi" 133 # Pipe kernel via stdin into Docker (volume mounts fail in devcontainer) 134 cat "${VMLINUZ}" | sudo docker run --rm -i --privileged -v /dev:/dev \ 135 alpine:latest sh -c " 136 apk add --quiet dosfstools sfdisk 137 cat > /tmp/vmlinuz 138 echo 'label: gpt 139type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, size=1024M' | sfdisk ${USB_DEV} 140 sync; sleep 1 141 mkfs.vfat -F 32 -n AC-NATIVE ${USB_DEV}1 142 mkdir -p /mnt/usb 143 mount ${USB_DEV}1 /mnt/usb 144 mkdir -p /mnt/usb/EFI/BOOT 145 cp /tmp/vmlinuz /mnt/usb/EFI/BOOT/BOOTX64.EFI 146 ls -lh /mnt/usb/EFI/BOOT/BOOTX64.EFI 147 sync 148 umount /mnt/usb 149 echo 'Done!' 150 " 151 rm -f /workspaces/aesthetic-computer/.flash-vmlinuz 2>/dev/null 152 # Inject handle (always) + all available credentials into config.json 153 HANDLE="${3:-jeffrey}" 154 CLAUDE_TOKEN="" 155 GITHUB_PAT="" 156 # Claude token: check .credentials.json first, then env 157 if [ -f "${HOME}/.claude/.credentials.json" ]; then 158 CLAUDE_TOKEN=$(node -e " 159 const c=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude/.credentials.json','utf8')); 160 process.stdout.write(c.claudeAiOauth?.accessToken || ''); 161 " 2>/dev/null || true) 162 fi 163 [ -z "${CLAUDE_TOKEN}" ] && CLAUDE_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN:-}" 164 # GitHub PAT: check gh config, then env vars 165 if [ -f "${HOME}/.config/gh/hosts.yml" ]; then 166 GITHUB_PAT=$(grep 'oauth_token:' "${HOME}/.config/gh/hosts.yml" 2>/dev/null | head -1 | awk '{print $2}' || true) 167 fi 168 [ -z "${GITHUB_PAT}" ] && GITHUB_PAT="${GH_TOKEN:-${GITHUB_TOKEN:-}}" 169 # AC auth token + device tokens: fetch from device-token API 170 AC_TOKEN="" 171 if [ -n "${HANDLE}" ]; then 172 echo " Fetching credentials for @${HANDLE}..." 173 DEVICE_JSON=$(curl -s "https://aesthetic.computer/api/device-token?handle=${HANDLE}" 2>/dev/null || true) 174 AC_TOKEN=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.token||'')}catch{}})" 2>/dev/null || true) 175 # Grab Claude/GitHub tokens from API if not already set locally 176 if [ -z "${CLAUDE_TOKEN}" ]; then 177 CLAUDE_TOKEN=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.claudeToken||'')}catch{}})" 2>/dev/null || true) 178 fi 179 if [ -z "${GITHUB_PAT}" ]; then 180 GITHUB_PAT=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.githubPat||'')}catch{}})" 2>/dev/null || true) 181 fi 182 [ -n "${AC_TOKEN}" ] && echo " AC auth token: yes" 183 fi 184 # Always write config.json with at least the handle 185 CONFIG_JSON=$(node -e " 186 const c = {handle: '${HANDLE}', piece: 'notepat'}; 187 if ('${CLAUDE_TOKEN}') c.claudeToken = '${CLAUDE_TOKEN}'; 188 if ('${GITHUB_PAT}') c.githubPat = '${GITHUB_PAT}'; 189 if ('${AC_TOKEN}') c.token = '${AC_TOKEN}'; 190 process.stdout.write(JSON.stringify(c)); 191 " 2>/dev/null || true) 192 if [ -z "${CONFIG_JSON}" ]; then 193 CONFIG_JSON="{\"handle\":\"${HANDLE}\"}" 194 fi 195 # Prompt for WiFi credentials (optional) 196 WIFI_SSID="${WIFI_SSID:-}" 197 WIFI_PASS="${WIFI_PASS:-}" 198 if [ -z "${WIFI_SSID}" ] && [ -t 0 ]; then 199 echo "" 200 read -p "WiFi SSID (enter to skip): " WIFI_SSID 201 if [ -n "${WIFI_SSID}" ]; then 202 read -p "WiFi password: " WIFI_PASS 203 fi 204 fi 205 WIFI_JSON="" 206 if [ -n "${WIFI_SSID}" ]; then 207 WIFI_JSON=$(node -e "process.stdout.write(JSON.stringify([{ssid:'${WIFI_SSID}',pass:'${WIFI_PASS}'}]))" 2>/dev/null || true) 208 [ -z "${WIFI_JSON}" ] && WIFI_JSON="[{\"ssid\":\"${WIFI_SSID}\",\"pass\":\"${WIFI_PASS}\"}]" 209 fi 210 # Write config.json + wifi_creds.json to USB 211 { 212 echo "CONFIG:${CONFIG_JSON}" 213 [ -n "${WIFI_JSON}" ] && echo "WIFI:${WIFI_JSON}" 214 } | sudo docker run --rm -i --privileged -v /dev:/dev \ 215 alpine:latest sh -c " 216 apk add --quiet dosfstools 217 while IFS= read -r line; do 218 case \"\$line\" in 219 CONFIG:*) echo \"\${line#CONFIG:}\" > /tmp/config.json ;; 220 WIFI:*) echo \"\${line#WIFI:}\" > /tmp/wifi_creds.json ;; 221 esac 222 done 223 mkdir -p /mnt/usb 224 mount ${USB_DEV}1 /mnt/usb 225 cp /tmp/config.json /mnt/usb/config.json 226 [ -f /tmp/wifi_creds.json ] && cp /tmp/wifi_creds.json /mnt/usb/wifi_creds.json 227 echo \"Wrote config.json (handle: ${HANDLE}, claude: $([ -n "${CLAUDE_TOKEN}" ] && echo yes || echo no), github: $([ -n "${GITHUB_PAT}" ] && echo yes || echo no))\" 228 [ -f /tmp/wifi_creds.json ] && echo \"Wrote wifi_creds.json (ssid: ${WIFI_SSID})\" 229 sync 230 umount /mnt/usb 231 " 232 echo "Flash complete: ${USB_DEV}" 233 ;; 234 *) 235 echo "Usage: ac-usb {ls|sha256|logs|read <path>|flash [vmlinuz]}" 236 exit 1 237 ;; 238esac