Skip to content

Commit 0633099

Browse files
committed
Merge branch 'umbreld'
2 parents d1e8ca8 + 317e036 commit 0633099

File tree

4 files changed

+114
-51
lines changed

4 files changed

+114
-51
lines changed

packages/os/overlay-arm64/opt/umbrel-external-storage/umbrel-external-storage

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,6 @@ get_block_device_model () {
6161
echo "$(echo $vendor) $(echo $model)"
6262
}
6363

64-
# By default Linux uses the UAS driver for most devices. This causes major
65-
# stability problems on the Raspberry Pi, not due to issues with UAS, but due
66-
# to devices running in UAS mode using much more power. The Pi can't reliably
67-
# provide enough power to the USB port and the entire system experiences
68-
# extreme instability. By blacklisting all devices from the UAS driver on boot
69-
# we fall back to the mass-storage driver, which results in decreased
70-
# performance, but lower power usage, and much better system stability.
71-
blacklist_uas () {
72-
usb_quirks=$(lsusb | awk '{print $6":u"}' | tr '\n' ',' | sed 's/,$//')
73-
echo -n "${usb_quirks}" > /sys/module/usb_storage/parameters/quirks
74-
75-
echo "Rebinding USB drivers..."
76-
for i in /sys/bus/pci/drivers/[uoex]hci_hcd/*:*; do
77-
[[ -e "$i" ]] || continue;
78-
echo "${i##*/}" > "${i%/*}/unbind"
79-
echo "${i##*/}" > "${i%/*}/bind"
80-
done
81-
}
82-
8364
is_partition_ext4 () {
8465
partition_path="${1}"
8566
# We need to run sync here to make sure the filesystem is reflecting the
@@ -181,37 +162,6 @@ main () {
181162
block_device_model=$(get_block_device_model $block_device)
182163
echo "Found device \"${block_device_model}\""
183164

184-
echo "Checking if partition exsts before blacklisting USB device IDs against UAS driver..."
185-
partition_should_exist="false"
186-
if [[ -e "${partition_path}" ]]; then
187-
echo "Partition exists"
188-
partition_should_exist="true"
189-
fi
190-
191-
echo "Blacklisting USB device IDs against UAS driver..."
192-
blacklist_uas
193-
194-
echo "Checking USB devices are back..."
195-
retry_for_usb_devices=1
196-
while [[ ! -e "${block_device_path}" ]]; do
197-
retry_for_usb_devices=$(( $retry_for_usb_devices + 1 ))
198-
if [[ $retry_for_usb_devices -gt 10 ]]; then
199-
echo "USB devices weren't registered after 10 tries..."
200-
echo "Exiting mount script without doing anything"
201-
exit 1
202-
fi
203-
204-
echo "Waiting for USB devices..."
205-
sleep 1
206-
done
207-
208-
echo "Checking if partition exists after blacklisting USB device IDs against UAS driver..."
209-
if [[ $partition_should_exist = "true" ]] && [[ ! -e "${partition_path}" ]]; then
210-
echo "Partition didn't return after USB rebinding"
211-
echo "Exiting mount script without doing anything"
212-
exit 1
213-
fi
214-
215165
echo "Checking if the device is ext4..."
216166

217167
if is_partition_ext4 "${partition_path}" ; then

packages/umbreld/source/index.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import path from 'node:path'
22

3+
import {globby} from 'globby'
4+
import fse from 'fs-extra'
5+
import {$} from 'execa'
6+
37
// @ts-expect-error I can't get tsconfig setup in a way that allows this without breaking other things.
48
// However we execute with tsx and it's able to resolve the import without issues.
59
import packageJson from '../package.json' assert {type: 'json'}
@@ -12,7 +16,7 @@ import Server from './modules/server/index.js'
1216
import User from './modules/user.js'
1317
import AppStore from './modules/apps/app-store.js'
1418
import Apps from './modules/apps/apps.js'
15-
import {detectDevice, setCpuGovernor} from './modules/system.js'
19+
import {detectDevice, setCpuGovernor, reboot} from './modules/system.js'
1620

1721
import {commitOsPartition} from './modules/system.js'
1822

@@ -84,6 +88,88 @@ export default class Umbreld {
8488
}
8589
}
8690

91+
// By default Linux uses the UAS driver for most devices. This causes major
92+
// stability problems on the Raspberry Pi 4, not due to issues with UAS, but due
93+
// to devices running in UAS mode using much more power. The Pi can't reliably
94+
// provide enough power to the USB port and the entire system experiences
95+
// extreme instability. By blacklisting all devices from the UAS driver on first
96+
// and then rebooting we fall back to the mass-storage driver, which results in
97+
// decreased performance, but lower power usage, and much better system stability.
98+
// TODO: Move this to a system module
99+
async blacklistUASDriver() {
100+
try {
101+
const justDidRebootFile = '/umbrel-just-did-reboot'
102+
// Only run on Raspberry Pi 4
103+
const {deviceId} = await detectDevice()
104+
if (deviceId !== 'pi-4') return
105+
this.logger.log('Checking for UAS devices to blacklist')
106+
const blacklist = []
107+
// Get all USB device uevent files
108+
const usbDeviceUeventFiles = await globby('/sys/bus/usb/devices/*/uevent')
109+
for (const ueventFile of usbDeviceUeventFiles) {
110+
const uevent = await fse.readFile(ueventFile, 'utf8')
111+
if (!uevent.includes('DRIVER=uas')) continue
112+
const [vendorId, productId] = uevent
113+
.split('\n')
114+
.find((line) => line?.startsWith('PRODUCT='))
115+
.replace('PRODUCT=', '')
116+
.split('/')
117+
const deviceId = `${vendorId}:${productId}`
118+
this.logger.log(`UAS device found ${deviceId}`)
119+
blacklist.push(deviceId)
120+
}
121+
122+
// Don't reboot if we don't have any UAS devices
123+
if (blacklist.length === 0) {
124+
this.logger.log('No UAS devices found!')
125+
await fse.remove(justDidRebootFile)
126+
return
127+
}
128+
129+
// Check we're not in a boot loop
130+
if (await fse.pathExists(justDidRebootFile)) {
131+
this.logger.log('We just rebooted, we could be in a bootloop, skipping reboot')
132+
return
133+
}
134+
135+
// Read current cmdline
136+
this.logger.log(`Applying quirks to cmdline.txt`)
137+
let cmdline = await fse.readFile('/boot/cmdline.txt', 'utf8')
138+
139+
// Don't apply quirks if they're already applied
140+
const quirksAlreadyApplied = blacklist.every((deviceId) => cmdline.includes(`${deviceId}:u`))
141+
if (quirksAlreadyApplied) {
142+
this.logger.log('UAS quirks already applied, skipping')
143+
return
144+
}
145+
146+
// Remove any current quirks
147+
cmdline = cmdline
148+
.trim()
149+
.split(' ')
150+
.filter((flag) => !flag.startsWith('usb-storage.quirks='))
151+
.join(' ')
152+
// Add new quirks
153+
const quirks = blacklist.map((deviceId) => `${deviceId}:u`).join(',')
154+
cmdline = `${cmdline} usb-storage.quirks=${quirks}`
155+
156+
// Remount /boot as writable
157+
await $`mount -o remount,rw /boot`
158+
// Write new cmdline
159+
await fse.writeFile('/boot/cmdline.txt', cmdline)
160+
161+
// Reboot the system
162+
this.logger.log(`Rebooting`)
163+
// We need to make sure we commit before rebooting otherwise
164+
// OTA updates will get instantly rolled back.
165+
await commitOsPartition(this)
166+
await fse.writeFile(justDidRebootFile, cmdline)
167+
await reboot()
168+
} catch (error) {
169+
this.logger.error(`Failed to blacklist UAS driver: ${(error as Error).message}`)
170+
}
171+
}
172+
87173
async start() {
88174
this.logger.log(`☂️ Starting Umbrel v${this.version}`)
89175
this.logger.log()
@@ -98,6 +184,9 @@ export default class Umbreld {
98184
// Set ondemand cpu governer for Raspberry Pi
99185
this.setupPiCpuGoverner()
100186

187+
// Blacklist UAS driver for Raspberry Pi 4
188+
await this.blacklistUASDriver()
189+
101190
// Run migration module before anything else
102191
// TODO: think through if we want to allow the server module to run before migration.
103192
// It might be useful if we add more complicated migrations so we can signal progress.

packages/umbreld/source/modules/apps/app-store.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pRetry from 'p-retry'
2+
13
import type Umbreld from '../../index.js'
24
import runEvery from '../utilities/run-every.js'
35
import AppRepository from './app-repository.js'
@@ -26,6 +28,14 @@ export default class AppStore {
2628

2729
// Initialise repositories
2830
this.logger.log(`Initialising repositories...`)
31+
await pRetry(() => this.update(), {
32+
onFailedAttempt: (error) => {
33+
this.logger.error(
34+
`Attempt ${error.attemptNumber} initialising repositories failed. There are ${error.retriesLeft} retries left.`,
35+
)
36+
},
37+
retries: 5, // This will do exponential backoff for 1s, 2s, 4s, 8s, 16s
38+
})
2939
await this.update()
3040
this.logger.log(`Repositories initialised!`)
3141

packages/umbreld/source/modules/apps/apps.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ export default class Apps {
7676
const appIds = await this.#umbreld.store.get('apps')
7777
this.instances = appIds.map((appId) => new App(this.#umbreld, appId))
7878

79+
// Force the app state to starting so users don't get confused.
80+
// They aren't actually starting yet, we need to make sure the app env is up first.
81+
// But if that takes a long time users see all their apps listed as not running and
82+
// get confused.
83+
for (const app of this.instances) app.state = 'starting'
84+
85+
// Attempt to cleanup old network if possible
86+
// This is needed because sometimes 0.5.4 installs didn't clean up the network
87+
// properly on shutdown on it causes errors on the first boot of 1.0
88+
try {
89+
this.logger.log('Cleaning up old docker network')
90+
await $({stdio: 'inherit'})`docker network rm umbrel_main_network`
91+
} catch (error) {}
92+
7993
// Attempt to pre-load local Docker images
8094
try {
8195
// Loop over iamges in /images

0 commit comments

Comments
 (0)