Monorepo for Aesthetic.Computer
aesthetic.computer
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