diff --git a/README.md b/README.md index 672402c..ab800b0 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ Blocking `snu.lge.com` in `/etc/hosts` is the obvious fix, and it is what the Homebrew Channel's own *Block system updates* toggle does. On a CX it does not stop the boot popup, and it took a session on the TV to work out why. -`/usr/sbin/update` is started by **systemd** (`update.service`, pulled in by -`webos-mbd.target`) and runs its version check against +The updater runs its version check against `https://snu.lge.com/CheckSWAutoUpdate.laf` roughly **20 seconds before** the Homebrew Channel gets as far as running its `init.d` hooks. So on every boot the check reaches a real LG server, gets `RESULT_CD 900 / Success` back, and @@ -32,29 +31,55 @@ the popup is already on screen by the time any hosts entry exists: 00:21:11 lg-update-blocker: hosts entries added ← 19 seconds too late ``` -Losing that race is not fixable from userspace. Every directory systemd reads -units from is a read-only overlay except `/run/systemd/system`, which is tmpfs -and created after the updater has already gone. Nor is there a setting that -gates the check — `automaticUpdate`, `support/softwareUpdateEnable`, -`hotelMode/swUpdateEnable` and the `.UpdateIsInprogress` flag were each tested -by restarting the daemon and counting the requests it made, and none of them -stop it. +That race cannot be won — nothing unprivileged runs earlier than those hooks — +so the app removes it instead. -So this app **closes the popup** instead of trying to prevent it, and blocks -the hostnames to stop the download and every later check in the session. Two -things also turned out not to be true, and the app no longer pretends -otherwise: the popup is not caused by a staged firmware image (the staging -directory is empty at boot; the size the daemon reports is in-memory only), and -it is not caused by an update *service* that can be stopped — `/etc/init` is -dead upstart leftovers on a systemd TV, and `initctl stop update` silently -"succeeds" while doing nothing. +`/usr/sbin/update` is not a normal daemon you can stop. It is launched on +demand by **ls-hubd**, which finds it through a *manifest*: a small JSON file +listing the D-Bus service file that carries `Exec=/usr/sbin/update`. LG's own +manifest is on a read-only partition, but `/etc/luna-service2/ls-hubd.conf` +also lists `ManifestsVolatileDirectories` under `/var` — writable, persistent, +and the mechanism webOS itself uses to ship manifest updates. A manifest there +with the **same id and a higher version replaces** the read-only one. + +So the app drops in a copy that keeps every role and permission file exactly as +it was and only empties the `serviceFiles` list. That single change takes away +ls-hubd's ability to launch the updater, and nothing on the bus can start it +any more: no version check, no alert, nothing to dismiss. Callers get an +immediate `Service does not exist` rather than hanging, and deleting the one +file puts everything back. + +Measured on a CX, before and after: `/tmp/var/log/update.log` used to be 55 kB +with two server checks and an `_gAlertWindowId` per boot — now the file is never +created at all. + +### Things that do not work + +Each of these was tried on the TV before settling on the above, and none of +them stop the popup: + +* **Settings.** `automaticUpdate`, `support/softwareUpdateEnable`, + `hotelMode/swUpdateEnable` and the `.UpdateIsInprogress` flag were each tested + by restarting the daemon and counting the requests it made. It checks anyway. +* **Masking the systemd unit.** `update.service` does ping the updater at boot, + but every directory systemd reads units from is a read-only overlay except + `/run/systemd/system`, which is tmpfs and created too late. +* **Breaking that ping.** `update.service` reads + `EnvironmentFile=-/var/systemd/system/env/update.env`, which *is* writable, and + pointing `LS_HUB_LOCAL_SOCKET_DIRECTORY` at a bogus path does make the ping + fail. The updater still gets activated — something else on the bus asks for it. +* **Stopping a service.** `/etc/init` is dead upstart leftovers on a systemd TV; + `initctl stop update` silently "succeeds" while doing nothing. +* **Deleting a staged image.** The popup is not caused by one — the staging + directory is empty at boot, and the size the daemon reports is in-memory only. ## Protection layers | Layer | What it does | Default | | --- | --- | --- | +| **Stop the updater running** | Overrides LG's ls-hubd manifest with one that has no `serviceFiles`, so `com.webos.service.update` can never be launched. This is the layer that actually stops the popup | on | | **Block LG update servers** | Points `snu`/`su`/`nsu.lge.com`, their TLS variants, the `-dev` fallbacks and the CDN aliases at `127.0.0.1` in `/etc/hosts` | on | -| **Close the update popup** | Closes the alert the updater raised during boot, then stays subscribed to `com.webos.notification` and closes any later one as it opens | on | +| **Close the update popup** | Fallback for a popup that appears anyway: closes the alert the updater raised during boot, then stays subscribed to `com.webos.notification` and closes any later one as it opens | on | | **Delete staged firmware** | Wipes the update image staged in `/mnt/lg/cmn_data/swupdate` if the TV ever does download one | on | | **Lock the staging folder** | Bind-mounts an empty read-only directory over it, so nothing can be staged again | off | | **Turn off auto-update settings** | Reads every `com.webos.settingsservice` category, switches off each update-related key it finds, and remembers the original values. On a CX this includes `hotelMode/swUpdateEnable`, which also hides the *Software Update* menu until you revert | on | @@ -62,13 +87,24 @@ dead upstart leftovers on a systemd TV, and `initctl stop update` silently Every layer is undone by **Remove protection**, which also restores the settings from the backup it made. -Be honest about what this buys you: the popup is closed within a fraction of a -second of the Homebrew Channel starting its hooks, but the updater raised it -15–20 seconds earlier, so on a cold boot **you will see it appear and then -vanish by itself**. It is gone before you can act on it, and you never have to -dismiss it. The hook is installed as `00-lgupdateblocker` so `run-parts` runs -it before the other homebrew hooks, which is as early as anything unprivileged -can go. Measured on a CX: alert raised at `01:26:22`, closed at `01:26:37`. +With *Stop the updater running* on, the popup does not appear at all — there is +nothing to close, because the version check never happens. That layer lives on +a persistent partition, so it also survives a reboot without help; the boot hook +only puts it back if something (a firmware update, an app install that rewrote +the volatile manifest directory) removed it. + +If you turn that layer off and rely on *Close the update popup* alone, the +popup is closed within a fraction of a second of the Homebrew Channel starting +its hooks — but the updater raised it 15–20 seconds earlier, so on a cold boot +**you will see it appear and then vanish by itself**. The hook is installed as +`00-lgupdateblocker` so `run-parts` runs it before the other homebrew hooks, +which is as early as anything unprivileged can go. + +Expect the *Software Update* screen in Settings to stop working while this is +on — it queries the service that is now off the bus, and will get an error +instead of an answer. On a CX the *Turn off auto-update settings* layer already +hides that menu anyway (`hotelMode/swUpdateEnable`). *Remove protection* brings +both back. ## Requirements @@ -119,6 +155,7 @@ Other buttons: **Purge staged update** (wipe a downloaded image right now), | Path | Purpose | | --- | --- | +| `/var/luna-service2/manifests.d/update.json` | The manifest override that takes the updater off the bus. Same id and role files as LG's, higher version, empty `serviceFiles`. Written only if nothing else is there, and only removed again if it is still ours | | `/etc/hosts` | Blocked hostnames, between `# >>> lg-update-blocker >>>` markers. Bind-mounted from `/tmp/lgupdateblocker-hosts` because `/` is read-only | | `/var/lib/webosbrew/init.d/00-lgupdateblocker` | Boot hook, run by the Homebrew Channel startup script; re-applies the enabled layers. The `00-` prefix makes `run-parts` run it first | | `/var/lib/webosbrew/lgupdateblocker/` | `config.json`, `hosts.txt`, `settings-backup.json`, `boot.log`, `alert-watch.sh` | @@ -133,15 +170,18 @@ replaced. * **Badge says "no root"** — open the Homebrew Channel, verify *Root status* is `ok`, then relaunch this app. If the Homebrew Channel itself is unelevated, fix that first (its Settings screen has a button for it). -* **Popup still appears after a reboot** — run *Diagnostics* and look at the - boot-hook log at the bottom. It records, per boot, whether the hosts entries - were added, what was purged, and the id of the popup it closed. If there is - no `dismissed update popup …` line, check `updateLog` in the same dump for - `_gAlertWindowId` — if that is missing too, your TV raises the alert by some - other route and the diagnostics dump is what to open an issue with. -* **The popup appears for ~15 seconds, then disappears on its own** — expected, see - [Why blocking the update servers is not enough](#why-blocking-the-update-servers-is-not-enough). - Nothing running after the Homebrew Channel can beat the updater to it. +* **Popup still appears after a reboot** — run *Diagnostics* and check + `updater` in the status block. `blocked` should be `true` and `running` + `false`; if `found` is `false`, ls-hubd on your firmware locates the updater + some other way and the diagnostics dump is what to open an issue with. If it + says `blocked: true` and the popup still shows up, `updateLog` in the same + dump tells you whether the updater ran at all — on a working setup + `/tmp/var/log/update.log` does not exist. +* **The popup appears for ~15 seconds, then disappears on its own** — that is + the *Close the update popup* fallback doing its job because the updater is + still running. Make sure *Stop the updater running* is ticked and applied. +* **Software Update in Settings shows an error** — expected while the updater + layer is on; see [Protection layers](#protection-layers). * **Belt and braces** — a hosts file only helps if the TV uses DNS. Blocking `snu.lge.com`, `su.lge.com` and `nsu.lge.com` on your router or Pi-hole as well is the one measure that also survives a factory reset. diff --git a/app/index.js b/app/index.js index 8e0316a..7254165 100644 --- a/app/index.js +++ b/app/index.js @@ -13,6 +13,11 @@ '/media/developer/apps/usr/palm/services/org.webosbrew.hbchannel.service/elevate-service'; var TOGGLES = [ + { + key: 'blockUpdater', + title: 'Stop the updater running', + desc: 'Takes /usr/sbin/update off the bus, so no version check happens and no popup appears' + }, { key: 'blockHosts', title: 'Block LG update servers', @@ -21,7 +26,7 @@ { key: 'dismissPopup', title: 'Close the update popup', - desc: 'The TV asks LG about updates ~20s before homebrew starts, so the popup is closed instead' + desc: 'Fallback: closes a popup that appears anyway, e.g. with the updater left running' }, { key: 'purgeCache', @@ -49,6 +54,7 @@ ]; var config = { + blockUpdater: true, blockHosts: true, dismissPopup: true, purgeCache: true, @@ -224,6 +230,13 @@ }); row('Service user', status.root ? 'root' : 'uid ' + status.uid + ' (not elevated)'); + var updater = status.updater || {}; + if (!updater.found) { + row('Update service', 'not present on this TV'); + } else { + row('Update service', (updater.blocked ? 'disabled' : 'launchable') + + (updater.running ? ', running now' : ', not running')); + } row('Hosts entries', status.hosts.blockedDomains + ' / ' + status.hosts.totalDomains + (status.hosts.bindMounted ? ' (bind-mounted)' : '')); row('Staging folders', stagedDirs ? stagedDirs + ' found' : 'none on this TV'); diff --git a/docs/description.html b/docs/description.html index 45be45b..b17e3a4 100644 --- a/docs/description.html +++ b/docs/description.html @@ -4,19 +4,24 @@ that webOS shows on every boot, without touching the firmware itself.
Blocking the update servers alone does not do it: the updater asks LG about new firmware around 20 seconds before the Homebrew Channel gets to run its boot hooks, so on a CX the check succeeds every time and the popup is already up. -Nothing running that late can win the race, so this app closes the popup as -well as blocking the servers.
+Nothing running that late can win that race, so this app removes it instead - +it takes the updater off the bus entirely, and the check never happens. -It applies up to five reversible layers and re-applies them on every boot +
It applies up to six reversible layers and re-applies them on every boot through a Homebrew Channel startup hook:
Requires root and the Homebrew Channel: the app asks the Homebrew Channel to -elevate its own service on first launch. "Remove protection" undoes everything.
+elevate its own service on first launch. "Remove protection" undoes everything, +including putting the updater back on the bus. + +While the updater layer is on, expect the Software Update screen in Settings +to show an error - it queries the service that is now gone.
diff --git a/package.json b/package.json index 60771d5..b307045 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.rkievits.lgupdateblocker", - "version": "1.1.0", + "version": "1.2.0", "title": "LG Update Blocker", "description": "Blocks the recurring LG webOS firmware update popup on rooted TVs", "private": true, diff --git a/service/service.js b/service/service.js index 04254fe..a85c1a2 100644 --- a/service/service.js +++ b/service/service.js @@ -3,29 +3,44 @@ * * Everything this service does is reversible and is applied in layers: * - * 1. hosts - point LG's firmware update servers at 127.0.0.1 - * 2. dismiss - close the "software update available" alert at boot - * 3. purge - delete the firmware image the TV already staged - * 4. lock - bind-mount an empty read-only dir over the staging dir - * 5. settings - switch off the update-related com.webos.settingsservice keys + * 1. updater - stop /usr/sbin/update from ever being launched + * 2. hosts - point LG's firmware update servers at 127.0.0.1 + * 3. dismiss - close the "software update available" alert at boot + * 4. purge - delete the firmware image the TV already staged + * 5. lock - bind-mount an empty read-only dir over the staging dir + * 6. settings - switch off the update-related com.webos.settingsservice keys * * / is read-only on webOS, and /tmp, /etc and the mount namespace are reset on - * every boot, so each layer is re-applied by a boot hook script dropped into - * /var/lib/webosbrew/init.d (run by the Homebrew Channel startup script). + * every boot, so the layers that live there are re-applied by a boot hook + * script dropped into /var/lib/webosbrew/init.d (run by the Homebrew Channel + * startup script). * - * Why the hosts block alone is not enough: /usr/sbin/update is started by - * systemd (webos-mbd.target) and runs its version check against snu.lge.com - * roughly 20 seconds before the Homebrew Channel gets as far as running the - * hooks in init.d. On a CX that check therefore *succeeds* on every boot and - * the popup is already on screen by the time anything of ours runs - which is - * also why the Homebrew Channel's own "block system updates" toggle does not - * stop it. Nothing persistent gates that check (automaticUpdate, - * support/softwareUpdateEnable, hotelMode/swUpdateEnable and the - * .UpdateIsInprogress flag were all measured to make no difference), and there - * is no writable directory early enough in the boot to win the race: every - * systemd unit path except /run is a read-only overlay. So the popup is closed - * instead of prevented, and the hosts block stops the download and every - * later check. + * Why the hosts block alone is not enough: /usr/sbin/update runs its version + * check against snu.lge.com roughly 20 seconds before the Homebrew Channel + * gets as far as running the hooks in init.d. On a CX that check therefore + * *succeeds* on every boot and the popup is already on screen by the time + * anything of ours runs - which is also why the Homebrew Channel's own "block + * system updates" toggle does not stop it, and why closing the popup after the + * fact leaves it visible for those 20 seconds. + * + * The updater layer removes the race instead of trying to win it. The updater + * is not a normal daemon: it is launched on demand by ls-hubd from a D-Bus + * service file, which ls-hubd finds through a manifest. ls-hubd.conf lists + * ManifestsVolatileDirectories under /var - writable, persistent, and how + * webOS itself ships manifest updates - and a manifest there with the same id + * and a higher version replaces the read-only one. Dropping in a copy whose + * serviceFiles list is empty leaves every role and permission file in place + * but removes the launch entry, so nothing on the bus can start the updater: + * no version check, no alert, nothing to dismiss. Callers get an immediate + * "Service does not exist" instead of hanging, and deleting the one file puts + * it all back. + * + * Things that do *not* work, all measured on a CX before settling on the + * above: automaticUpdate, support/softwareUpdateEnable, hotelMode/swUpdateEnable + * and the .UpdateIsInprogress flag change nothing; no systemd unit directory is + * writable, so update.service cannot be masked; breaking update.service's ping + * via its /var/systemd/system/env override does make that ping fail, but + * something else on the bus activates the updater anyway. * * Written in ES5 without dependencies - it runs on the TV's own node with the * platform-provided webos-service module and nothing else. @@ -84,6 +99,15 @@ var CACHE_GUARD = '/mnt/lg/'; var UPDATE_DAEMON_LOG = '/tmp/var/log/update.log'; var ALERT_ID_PATTERN = 'com\\.webos\\.service\\.update-[0-9]*'; +/* ls-hubd launches the updater on demand; its config says which manifest + * directories are read-only and which are the writable ones we may override + * from. Everything about the updater - its manifest, its D-Bus service file, + * even the path of its binary - is discovered from there rather than assumed, + * so this works the same on a firmware that moves them. */ +var LS_HUBD_CONF = '/etc/luna-service2/ls-hubd.conf'; +var UPDATER_SERVICE = 'com.webos.service.update'; +var UPDATER_BINARY_FALLBACK = '/usr/sbin/update'; + var SETTINGS_CATEGORIES = ['option', 'general', 'network', 'commercial', 'support', 'hotelMode']; var SETTINGS_KEY_PATTERN = /(update|upgrade|firmware)/i; /* "ota" and "nsu" are substrings of perfectly innocent keys - screenRotation, @@ -96,6 +120,7 @@ function isUpdateKey(key) { } var DEFAULT_CONFIG = { + blockUpdater: true, blockHosts: true, dismissPopup: true, purgeCache: true, @@ -202,6 +227,185 @@ function anyEnabled(config) { }); } +/* ---------------------------------------------------------------- updater */ + +/** Semicolon-separated directory list out of ls-hubd.conf. */ +function lsHubdDirs(key) { + var match = readFile(LS_HUBD_CONF, '').match(new RegExp('^[ \\t]*' + key + '[ \\t]*=(.*)$', 'm')); + if (!match) return []; + return match[1].split(';').map(function (dir) { + return dir.trim(); + }).filter(Boolean); +} + +/** The read-only manifest whose serviceFiles make the updater launchable. */ +function findUpdaterManifest() { + var found = null; + lsHubdDirs('ManifestsDirectories').forEach(function (dir) { + if (found || !isDirectory(dir)) return; + var entries; + try { + entries = fs.readdirSync(dir); + } catch (err) { + return; + } + entries.forEach(function (entry) { + if (found || !/\.json$/.test(entry)) return; + var file = path.join(dir, entry); + var manifest = readJson(file, null); + if (!manifest || !manifest.id || !Array.isArray(manifest.serviceFiles)) return; + var launchesUpdater = manifest.serviceFiles.some(function (svc) { + return path.basename(svc) === UPDATER_SERVICE + '.service'; + }); + if (launchesUpdater) found = { file: file, manifest: manifest }; + }); + }); + return found; +} + +/** Where our replacement goes - the first writable manifest dir ls-hubd scans. */ +function overridePath(base) { + var dirs = lsHubdDirs('ManifestsVolatileDirectories'); + var dir = dirs.filter(isDirectory)[0] || dirs[0]; + return dir ? path.join(dir, base.manifest.id + '.json') : null; +} + +/** A version that outranks the stock manifest, so ls-hubd prefers ours. */ +function outrankVersion(version) { + var major = parseInt(String(version || '').split('.')[0], 10); + return Math.max((major >= 0 ? major : 0) + 1, 99) + '.0.0'; +} + +/* Same id, higher version, same roles and permissions - only the serviceFiles + * list is emptied, which is what takes away ls-hubd's ability to launch it. */ +function buildOverride(base) { + var override = { id: base.manifest.id, version: outrankVersion(base.manifest.version) }; + ['roleFiles', 'roleFilesPub', 'roleFilesPrv', 'apiPermissionFiles', 'clientPermissionFiles'] + .forEach(function (key) { + if (base.manifest[key]) override[key] = base.manifest[key]; + }); + override.serviceFiles = []; + return override; +} + +/* An empty serviceFiles list is not something a real manifest ever has, so it + * doubles as the signature that a file at that path was written by us. */ +function isOverrideOurs(target, base) { + var manifest = readJson(target, null); + return !!manifest && manifest.id === base.manifest.id && + Array.isArray(manifest.serviceFiles) && manifest.serviceFiles.length === 0; +} + +/** Exec= out of the updater's D-Bus service file. */ +function updaterBinary(base) { + var files = base.manifest.serviceFiles || []; + for (var i = 0; i < files.length; i += 1) { + var match = readFile(files[i], '').match(/^[ \t]*Exec[ \t]*=[ \t]*(\S+)/m); + if (match) return match[1]; + } + return UPDATER_BINARY_FALLBACK; +} + +function updaterPids(binary) { + var pids = []; + try { + fs.readdirSync('/proc').forEach(function (entry) { + if (!/^[0-9]+$/.test(entry)) return; + /* cmdline is NUL-separated; argv[0] is what ls-hubd launched */ + if (readFile('/proc/' + entry + '/cmdline', '').split('\0')[0] === binary) { + pids.push(parseInt(entry, 10)); + } + }); + } catch (err) { + /* /proc unreadable - nothing we can do */ + } + return pids; +} + +function stopUpdater(binary) { + var stopped = 0; + updaterPids(binary).forEach(function (pid) { + try { + process.kill(pid, 'SIGTERM'); + stopped += 1; + } catch (err) { + /* already gone */ + } + }); + return stopped; +} + +function blockUpdater(log) { + var base = findUpdaterManifest(); + if (!base) { + log.add('! no ' + UPDATER_SERVICE + ' manifest on this TV - leaving the updater alone'); + return false; + } + var target = overridePath(base); + if (!target) { + log.add('! ls-hubd has no writable manifest directory on this TV'); + return false; + } + if (exists(target) && !isOverrideOurs(target, base)) { + log.add('! ' + target + ' exists and is not ours - leaving it alone'); + return false; + } + + try { + mkdirp(path.dirname(target)); + writeJson(target, buildOverride(base)); + } catch (err) { + log.add('! could not write ' + target + ': ' + err.message); + return false; + } + + var res = sh('ls-control scan-services'); + if (!res.ok) log.add('! ls-control scan-services failed: ' + res.output); + log.add('updater disabled - ' + UPDATER_SERVICE + ' can no longer be launched'); + + var binary = updaterBinary(base); + var stopped = stopUpdater(binary); + if (stopped) log.add('stopped ' + stopped + ' running ' + binary + ' process(es)'); + return true; +} + +function unblockUpdater(log) { + var base = findUpdaterManifest(); + var target = base && overridePath(base); + if (!target || !exists(target)) { + log.add('updater was not disabled'); + return; + } + if (!isOverrideOurs(target, base)) { + log.add('! ' + target + ' is not ours - leaving it alone'); + return; + } + try { + fs.unlinkSync(target); + } catch (err) { + log.add('! could not remove ' + target + ': ' + err.message); + return; + } + sh('ls-control scan-services'); + log.add('updater re-enabled - removed ' + target); +} + +function updaterStatus() { + var base = findUpdaterManifest(); + if (!base) return { found: false, blocked: false, running: false }; + var target = overridePath(base); + var binary = updaterBinary(base); + return { + found: true, + service: UPDATER_SERVICE, + binary: binary, + manifest: base.file, + overridePath: target, + blocked: !!(target && exists(target) && isOverrideOurs(target, base)), + running: updaterPids(binary).length > 0 + }; +} + /* ------------------------------------------------------------------ hosts */ function readDomains() { @@ -692,6 +896,37 @@ function restoreSettings(log, callback) { /* -------------------------------------------------------------- boot hook */ +/* The updater override lives on a persistent partition, so unlike the other + * layers it normally survives a reboot on its own. The hook only puts it back + * if something removed it - a firmware update, or an app install that rewrote + * the volatile manifest directory. */ +function updaterHookLines(config) { + var base = config.blockUpdater ? findUpdaterManifest() : null; + var target = base && overridePath(base); + if (!target) return []; + return [ + 'UPDATER_MANIFEST=' + target, + 'UPDATER_BIN=' + updaterBinary(base), + '', + 'if [ ! -f "$UPDATER_MANIFEST" ]; then', + ' mkdir -p "$(dirname "$UPDATER_MANIFEST")"', + ' cat > "$UPDATER_MANIFEST" <<\'LGUB_MANIFEST\'', + JSON.stringify(buildOverride(base), null, 2), + 'LGUB_MANIFEST', + ' ls-control scan-services >/dev/null 2>&1', + ' echo "restored updater override $UPDATER_MANIFEST"', + 'fi', + '# it cannot be launched any more, but kill one that slipped through', + 'for proc in /proc/[0-9]*; do', + ' [ -r "$proc/cmdline" ] || continue', + ' case "$(tr \'\\0\' \' \' < "$proc/cmdline")" in', + ' "$UPDATER_BIN "*) kill "${proc#/proc/}" 2>/dev/null && echo "stopped $UPDATER_BIN" ;;', + ' esac', + 'done', + '' + ]; +} + function bootHookScript(config, domains) { return [ '#!/bin/sh', @@ -712,9 +947,10 @@ function bootHookScript(config, domains) { 'if [ -f "$LOG" ] && [ "$(wc -c < "$LOG")" -gt 65536 ]; then rm -f "$LOG"; fi', 'exec >>"$LOG" 2>&1', 'echo "--- $(date) LG Update Blocker ---"', - '', - '# First, because by now the popup has been on screen for ~20 seconds: the', - '# updater runs its version check from webos-mbd.target, long before the', + '' + ].concat(updaterHookLines(config)).concat([ + '# Fallback for when the updater layer is off: by now the popup has been on', + '# screen for ~20 seconds, because the version check runs long before the', '# Homebrew Channel gets round to running these hooks.', 'if [ "$DISMISS_POPUP" = 1 ]; then', ' id=$(sed -n \'s/.*_gAlertWindowId : \\(' + ALERT_ID_PATTERN + '\\).*/\\1/p\' \\', @@ -787,7 +1023,7 @@ function bootHookScript(config, domains) { '', 'echo "done"', '' - ].join('\n'); + ]).join('\n'); } /* Long-lived companion to the boot hook: subscribes to the notification @@ -888,6 +1124,7 @@ function baseStatus() { uid: typeof process.getuid === 'function' ? process.getuid() : -1, root: isRoot(), config: readConfig(), + updater: updaterStatus(), hosts: { path: HOSTS_PATH, writable: hostsWritable(), @@ -933,6 +1170,13 @@ service.register('apply', function (message) { mkdirp(STATE_DIR); writeJson(CONFIG_PATH, config); + /* first: with the updater gone there is no version check to lose a race to */ + if (config.blockUpdater) { + blockUpdater(log); + } else { + unblockUpdater(log); + } + if (config.blockHosts) { applyHosts(log); } else { @@ -992,6 +1236,7 @@ service.register('revert', function (message) { }); try { + unblockUpdater(log); removeHosts(log); unlockCache(log); if (stopWatcher()) log.add('alert watcher stopped');