OverviewFilesCommitsIssuesDocsReleasesPeers

host-services commits / 7a50944

host-services 0.1.0: one page for every service on a host + LAN devices
pgh apps, systemd units (system + user) and every listening TCP port, each port tied to its owner and probed (open / HTTP status / title); states active / degraded / dormant / failed. Network tab: /24 sweep -> ARP -> mDNS -> connect scan of ~95 ports per device. Bare app for pgh; Caddy fronts it as http://stortron.local/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7a50944898d83ac08a5d4d782c9646b21480a077
by plankatron <fleeky@gmail.com> · 2026-10-20T00:44:29.000Z

Changed files (25) +5443 -0

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
README.md +71 -0
……@@ -0,0 +1,71 @@
1# host-services
2
3One page that lists everything a host serves, with its state, plus the other
4devices on the LAN and what they serve. A Bare app deployed with pgh; it runs on
5stortron and Caddy fronts it as `http://stortron.local/`.
6
7## What it shows
8
9**Services** (refreshed every ~15 s), each one **active**, **degraded** (running,
10but none of its ports answer), **dormant** (installed or enabled, not running) or
11**failed**:
12
13- every pgh app (`pgh app status`): version, autostart, last exit
14- systemd units, system and user. Hand-installed units (`/etc/systemd`,
15 `~/.config/systemd`) are always listed; units the distribution shipped only when
16 they own a port, are failed, or are named in `labels.json`. The rest is folded
17 into "Operating-system units".
18- every listening TCP port, tied to its owner: our own processes through
19 `/proc/<pid>/fd`; other users' sockets to the running unit with that `User=`;
20 root's well-known ports through `labels.json` (`"unit"`). Anything left over is
21 listed on its own, so nothing hides. Each port is probed: open, HTTP status,
22 page title.
23
24**Network** (every 30 min, or "Scan now"): a TCP sweep of the host's /24 fills
25the ARP table (a device that drops all TCP still answers ARP), then mDNS names
26and advertised services from avahi, then a connect scan of ~95 common ports per
27device with an HTTP probe of each open one. No root needed. The last scan is
28kept in `~/.config/host-services/lan.json`.
29
30## Run / test
31
32```bash
33npm ci
34npm test # brittle-bare
35bare index.js # 127.0.0.1:4060 by default
36```
37
38Env: `HOST_SERVICES_PORT` (4060), `HOST_SERVICES_HOST` (127.0.0.1; the pgh
39defaults set 0.0.0.0), `HOST_SERVICES_CONFIG` (`~/.config/host-services`),
40`HOST_SERVICES_LAN_SCAN=0` to turn the periodic LAN scan off.
41
42Local labels: `~/.config/host-services/labels.json`, same shape as the shipped
43`labels.json`, merged over it key by key.
44
45Dependencies are pinned for pgh's Bare **1.28.7** (lockfile shared with
46cthuloid-web): newer `bare-dns` / `bare-type` / `bare-structured-clone` declare
47newer Bare engines and refuse to load.
48
49⚠ On a dev machine run the real binary, not the npm `bare` wrapper: the wrapper
50does not forward SIGTERM, so `kill` leaves the app running.
51
52## Deploy (pgh)
53
54```bash
55pgh publish . # from this directory
56# on stortron:
57pgh app install pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/host-services --name host-services
58pgh app start host-services
59pgh app autostart host-services on
60```
61
62Caddy (`/etc/caddy/Caddyfile`) on stortron:
63
64```
65http://stortron.local {
66 reverse_proxy 127.0.0.1:4060
67}
68```
69
70⛔ LAN-only, no login, like the services it lists. The one POST
71(`/api/lan/scan`) must carry `x-host-services: 1`.
index.js +57 -0
……@@ -0,0 +1,57 @@
1// host-services — `bare index.js`. One page listing everything this host
2// serves (pgh apps, systemd units, every listening port) with active / dormant
3// state, plus the other devices on the LAN and what they serve.
4//
5// ⛔ LAN-only, no login (the services it lists have none either). The pgh
6// defaults bind 0.0.0.0:4060 and Caddy fronts it as http://<host>.local/; the
7// code default is 127.0.0.1 so a bare `bare index.js` on a dev machine exposes
8// nothing.
9
10import fs from 'bare-fs'
11import os from 'bare-os'
12import path from 'bare-path'
13import process from 'bare-process'
14import { HostCollector } from './lib/collect.js'
15import { LanScanner } from './lib/lan.js'
16import { createServer } from './lib/server.js'
17
18const port = Number(process.env.HOST_SERVICES_PORT || 4060)
19const host = process.env.HOST_SERVICES_HOST || '127.0.0.1'
20const configDir = process.env.HOST_SERVICES_CONFIG || path.join(os.homedir(), '.config', 'host-services')
21const lanScan = process.env.HOST_SERVICES_LAN_SCAN !== '0'
22
23// Friendly names for known ports and units. The one shipped with the app,
24// overridden key-by-key by ~/.config/host-services/labels.json if present.
25function loadLabels () {
26 const read = (p) => {
27 try {
28 return JSON.parse(fs.readFileSync(p).toString())
29 } catch {
30 return {}
31 }
32 }
33 const base = read(path.join(path.dirname(new URL(import.meta.url).pathname), 'labels.json'))
34 const local = read(path.join(configDir, 'labels.json'))
35 return {
36 ports: { ...base.ports, ...local.ports },
37 units: { ...base.units, ...local.units }
38 }
39}
40
41const collector = new HostCollector({ labels: loadLabels() })
42const lan = new LanScanner({ configDir })
43const server = createServer({ host: collector, lan })
44
45server.listen(port, host, () => {
46 console.log(JSON.stringify({ msg: 'host-services up', url: 'http://' + host + ':' + server.address().port, config: configDir, lanScan }))
47 if (lanScan) lan.start()
48})
49lan.on('scanned', (r) => console.log(JSON.stringify({ msg: 'lan scanned', hosts: r.hosts.length, ms: r.durationMs })))
50
51const shutdown = () => {
52 lan.stop()
53 server.close(() => process.exit(0))
54 setTimeout(() => process.exit(0), 3000)
55}
56process.on('SIGTERM', shutdown)
57process.on('SIGINT', shutdown)
labels.json +104 -0
……@@ -0,0 +1,104 @@
1{
2 "ports": {
3 "22": {
4 "name": "SSH",
5 "unit": "ssh.service"
6 },
7 "53": {
8 "name": "DNS (systemd-resolved)",
9 "unit": "systemd-resolved.service"
10 },
11 "80": {
12 "name": "Caddy (HTTP)"
13 },
14 "443": {
15 "name": "Caddy (HTTPS)"
16 },
17 "631": {
18 "name": "CUPS printing",
19 "unit": "cups.service"
20 },
21 "2019": {
22 "name": "Caddy admin API"
23 },
24 "3399": {
25 "name": "favela-gateway: atproto membrane"
26 },
27 "3400": {
28 "name": "favela-gateway: fediverse (ap.7rnx.net)"
29 },
30 "3401": {
31 "name": "favela-gateway: bridge sign-in"
32 },
33 "4041": {
34 "name": "favela-web"
35 },
36 "4042": {
37 "name": "favela-host"
38 },
39 "4050": {
40 "name": "Cthuloid Lights"
41 },
42 "4060": {
43 "name": "host-services (this page)"
44 },
45 "5005": {
46 "name": "pear-git-v3 forge"
47 },
48 "5055": {
49 "name": "Jellyseerr"
50 },
51 "7220": {
52 "name": "stremio-vsembed addon"
53 },
54 "7221": {
55 "name": "Kodi repo (jacktook dist)"
56 },
57 "7878": {
58 "name": "Radarr"
59 },
60 "8081": {
61 "name": "Easynews indexer bridge"
62 },
63 "8085": {
64 "name": "SABnzbd"
65 },
66 "8096": {
67 "name": "Jellyfin"
68 },
69 "8920": {
70 "name": "Jellyfin HTTPS (disabled)"
71 },
72 "8989": {
73 "name": "Sonarr"
74 },
75 "9696": {
76 "name": "Prowlarr"
77 },
78 "32400": {
79 "name": "Plex"
80 },
81 "32401": {
82 "name": "Plex (internal)"
83 },
84 "32600": {
85 "name": "Plex (internal)"
86 },
87 "33445": {
88 "name": "Plex (internal)"
89 }
90 },
91 "units": {
92 "caddy.service": {
93 "name": "Caddy",
94 "description": "Reverse proxy for <host>.local"
95 },
96 "hyperident-gateway.service": {
97 "name": "hyperident-gateway",
98 "description": "P2P VPN (10.22.0.0/16)"
99 },
100 "smartmontools.service": {
101 "name": "SMART disk monitor"
102 }
103 }
104}
lib/collect.js +333 -0
……@@ -0,0 +1,333 @@
1// Everything this host serves, in one snapshot: pgh apps, systemd units
2// (system and user) and every listening TCP port, each port tied to whoever
3// owns it, each service given a state:
4//
5// active running, and its ports (if any) answer
6// degraded running, but none of its ports answer
7// dormant installed / enabled but not running
8// failed systemd says failed, or a pgh app exited with an error
9//
10// Ownership of a socket: inode -> pid through /proc/<pid>/fd, which only works
11// for our own processes. Sockets of other users (jellyfin, plex, caddy...) are
12// tied by uid -> user -> the running unit with that User=.
13
14import fs from 'bare-fs'
15import os from 'bare-os'
16import path from 'bare-path'
17import process from 'bare-process'
18import { run } from './run.js'
19import { probeHttp } from './probe.js'
20import { parseProcNetTcp, bindScope, parseSystemctlShow, isVendorUnit, parsePghStatus } from './parse.js'
21
22const UNIT_PROPS = 'Id,Description,LoadState,ActiveState,SubState,UnitFileState,FragmentPath,MainPID,User,ActiveEnterTimestamp'
23
24function readText (p) {
25 try {
26 return fs.readFileSync(p).toString()
27 } catch {
28 return ''
29 }
30}
31
32// The listening sockets, one entry per port+scope (a port bound on both
33// 0.0.0.0 and :: is one service, not two).
34export function readListeners (procRoot = '/proc') {
35 const all = [
36 ...parseProcNetTcp(readText(path.join(procRoot, 'net/tcp')), 4),
37 ...parseProcNetTcp(readText(path.join(procRoot, 'net/tcp6')), 6)
38 ]
39 const byPort = new Map()
40 for (const l of all) {
41 const scope = bindScope(l.addr)
42 const key = l.port + '/' + (scope === 'addr' ? l.addr : scope)
43 const prev = byPort.get(key)
44 if (prev) {
45 prev.inodes.push(l.inode)
46 continue
47 }
48 byPort.set(key, { port: l.port, addr: l.addr, scope, uid: l.uid, inodes: [l.inode] })
49 }
50 // A port open on all addresses also covers loopback: drop the 'local' twin.
51 const out = []
52 for (const l of byPort.values()) {
53 if (l.scope === 'local' && byPort.has(l.port + '/all')) {
54 byPort.get(l.port + '/all').inodes.push(...l.inodes)
55 continue
56 }
57 out.push(l)
58 }
59 return out.sort((a, b) => a.port - b.port)
60}
61
62// socket inode -> pid, for every process we may look into.
63export function socketOwners (procRoot = '/proc') {
64 const owners = new Map()
65 let pids = []
66 try {
67 pids = fs.readdirSync(procRoot).filter((d) => /^\d+$/.test(d))
68 } catch {}
69 for (const pid of pids) {
70 let fds
71 try {
72 fds = fs.readdirSync(path.join(procRoot, pid, 'fd'))
73 } catch {
74 continue
75 }
76 for (const fd of fds) {
77 let target
78 try {
79 target = fs.readlinkSync(path.join(procRoot, pid, 'fd', fd))
80 } catch {
81 continue
82 }
83 const m = /^socket:\[(\d+)\]$/.exec(target)
84 if (m) owners.set(Number(m[1]), Number(pid))
85 }
86 }
87 return owners
88}
89
90// What a pid is: a pgh app (by its install path), a systemd unit (by cgroup),
91// or just a command.
92export function describePid (pid, procRoot = '/proc') {
93 const cmdline = readText(path.join(procRoot, String(pid), 'cmdline')).split('\0').filter(Boolean)
94 const cgroup = readText(path.join(procRoot, String(pid), 'cgroup'))
95 const unit = /\/([^/]+\.service)\s*$/m.exec(cgroup)
96 const user = /user@\d+\.service/.test(cgroup)
97 return {
98 pid,
99 cmd: cmdline.slice(0, 3).join(' ').slice(0, 200),
100 exe: cmdline.length ? path.basename(cmdline[0]) : '',
101 pghApp: pghAppOf(cmdline),
102 unit: unit && !/^user@\d+\.service$/.test(unit[1]) ? unit[1] : null,
103 userUnit: user
104 }
105}
106
107// Every pgh app runs as `<store>/apps/pgh/runtime/bare <store>/apps/<app>/...`:
108// the app is the script's path, not the runtime's (which is always pgh's).
109export function pghAppOf (cmdline) {
110 let app = null
111 for (const arg of cmdline) {
112 const m = /\/store\/apps\/([^/]+)\/(.*)$/.exec(arg)
113 if (m && !m[2].startsWith('runtime/')) app = m[1]
114 }
115 return app
116}
117
118export function readUsers (passwd = readText('/etc/passwd')) {
119 const users = new Map()
120 for (const line of passwd.split('\n')) {
121 const f = line.split(':')
122 if (f.length > 2) users.set(Number(f[2]), f[0])
123 }
124 return users
125}
126
127// Assemble the snapshot from raw inputs. Pure, so it is tested without a host.
128export function buildServices ({ listeners, owners, pidInfo, users, units, userUnits, pghApps, probes, labels = {} }) {
129 const portLabels = labels.ports || {}
130 const unitLabels = labels.units || {}
131 const services = new Map()
132 const add = (id, base) => {
133 if (!services.has(id)) services.set(id, { id, ports: [], ...base })
134 return services.get(id)
135 }
136
137 for (const a of pghApps) {
138 add('pgh:' + a.name, {
139 kind: 'pgh',
140 name: a.name,
141 label: unitLabels['pgh:' + a.name] || null,
142 running: a.running,
143 version: a.version,
144 autostart: a.autostart,
145 since: a.running ? a.startedAt : a.lastExit,
146 lastExitCode: a.lastExitCode,
147 pid: a.pid
148 })
149 }
150
151 const unitById = new Map()
152 for (const [scope, list] of [['system', units], ['user', userUnits]]) {
153 for (const u of list) {
154 if (u.LoadState && u.LoadState !== 'loaded') continue
155 const id = (scope === 'user' ? 'user:' : 'unit:') + u.Id
156 unitById.set(id, { scope, u })
157 }
158 }
159
160 // Which running unit runs as which user, for sockets we cannot look into.
161 const unitsByUser = new Map()
162 for (const [id, { u }] of unitById) {
163 if (u.ActiveState !== 'active' || !u.User) continue
164 if (!unitsByUser.has(u.User)) unitsByUser.set(u.User, [])
165 unitsByUser.get(u.User).push(id)
166 }
167
168 const ensureUnit = (id) => {
169 const rec = unitById.get(id)
170 if (!rec) return null
171 const { scope, u } = rec
172 return add(id, {
173 kind: scope === 'user' ? 'user-unit' : 'unit',
174 name: u.Id.replace(/\.service$/, ''),
175 label: unitLabels[u.Id] || null,
176 description: u.Description && u.Description !== u.Id ? u.Description : null,
177 running: u.ActiveState === 'active' || u.ActiveState === 'reloading',
178 activeState: u.ActiveState,
179 subState: u.SubState,
180 enabled: u.UnitFileState,
181 since: u.ActiveEnterTimestamp || null,
182 user: u.User || null,
183 vendor: isVendorUnit(u)
184 })
185 }
186
187 for (const l of listeners) {
188 const probe = probes.get(l.port + '/' + l.addr) || null
189 const port = {
190 port: l.port,
191 addr: l.addr,
192 scope: l.scope,
193 label: portLabels[String(l.port)] || null,
194 open: probe ? probe.open : null,
195 http: probe ? probe.http : null,
196 banner: probe ? probe.banner : null,
197 owner: null
198 }
199 let svc = null
200 const pid = l.inodes.map((i) => owners.get(i)).find(Boolean)
201 const info = pid ? (pidInfo.get(pid) || { pid, cmd: '', exe: '', pghApp: null, unit: null, userUnit: false }) : null
202 if (info) {
203 port.owner = { pid, cmd: info.cmd }
204 if (info.pghApp && services.has('pgh:' + info.pghApp)) svc = services.get('pgh:' + info.pghApp)
205 else if (info.unit) svc = ensureUnit((info.userUnit ? 'user:' : 'unit:') + info.unit)
206 } else {
207 const user = users.get(l.uid) || String(l.uid)
208 port.owner = { user }
209 const cands = unitsByUser.get(user) || []
210 if (cands.length === 1) svc = ensureUnit(cands[0])
211 }
212 // Root's services run with an empty User=, so nothing above can tie them;
213 // labels.json may name the unit a well-known port belongs to.
214 if (!svc && port.label && port.label.unit && unitById.has('unit:' + port.label.unit)) svc = ensureUnit('unit:' + port.label.unit)
215 // A plain process of ours (no unit, no pgh app): its ports belong together.
216 if (!svc && info) {
217 svc = add('proc:' + pid, {
218 kind: 'process',
219 name: info.exe || 'pid ' + pid,
220 label: null,
221 running: true,
222 owner: port.owner
223 })
224 }
225 if (!svc) {
226 svc = add('port:' + l.port + '/' + l.addr, {
227 kind: 'port',
228 name: (port.label && port.label.name) || (port.http && port.http.title) || 'port ' + l.port,
229 label: port.label,
230 running: true,
231 owner: port.owner
232 })
233 }
234 svc.ports.push(port)
235 }
236
237 // Units nobody's port pulled in: hand-installed ones, labelled ones and
238 // failed ones are services; the rest is the OS.
239 const system = []
240 for (const [id, { u }] of unitById) {
241 if (services.has(id)) continue
242 const keep = !isVendorUnit(u) || unitLabels[u.Id] || u.ActiveState === 'failed'
243 if (keep && (u.UnitFileState !== 'static' || u.ActiveState !== 'inactive')) ensureUnit(id)
244 else system.push({ id, name: u.Id, state: u.ActiveState, sub: u.SubState, enabled: u.UnitFileState })
245 }
246
247 const list = [...services.values()]
248 for (const s of list) s.state = stateOf(s)
249 return { services: list, system }
250}
251
252export function stateOf (s) {
253 if (s.kind === 'unit' || s.kind === 'user-unit') {
254 if (s.activeState === 'failed') return 'failed'
255 if (!s.running) return 'dormant'
256 } else if (s.kind === 'pgh') {
257 // 75 is pgh's "restart into the new release" exit, not a failure.
258 if (!s.running) return s.lastExitCode && s.lastExitCode !== 75 ? 'failed' : 'dormant'
259 }
260 if (s.ports.length && !s.ports.some((p) => p.open)) return 'degraded'
261 return 'active'
262}
263
264export class HostCollector {
265 constructor ({ labels = {}, pghBin = path.join(os.homedir(), '.local', 'bin', 'pgh'), ttlMs = 15000 } = {}) {
266 this.labels = labels
267 this.pghBin = pghBin
268 this.ttlMs = ttlMs
269 this._cache = null
270 this._at = 0
271 this._inflight = null
272 }
273
274 snapshot () {
275 if (this._cache && Date.now() - this._at < this.ttlMs) return Promise.resolve(this._cache)
276 if (!this._inflight) {
277 this._inflight = this._collect().then((s) => {
278 this._cache = s
279 this._at = Date.now()
280 return s
281 }).finally(() => {
282 this._inflight = null
283 })
284 }
285 return this._inflight
286 }
287
288 async _collect () {
289 const uid = process.getuid ? process.getuid() : 1000
290 // systemctl --user needs the session bus; a daemon started over ssh or by
291 // pgh's supervisor may not have it in its environment.
292 const userEnv = { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR || '/run/user/' + uid }
293 const [sys, usr, pgh] = await Promise.all([
294 run('systemctl', ['show', '--type=service', '--all', '-p', UNIT_PROPS]),
295 run('systemctl', ['--user', 'show', '--type=service', '--all', '-p', UNIT_PROPS], { env: userEnv }),
296 run(this.pghBin, ['app', 'status'], { timeoutMs: 20000 })
297 ])
298 const listeners = readListeners()
299 const owners = socketOwners()
300 const pidInfo = new Map()
301 for (const pid of new Set(owners.values())) pidInfo.set(pid, describePid(pid))
302
303 const probes = new Map()
304 await Promise.all(listeners.map(async (l) => {
305 // Probe the address it listens on: a loopback listener may be on
306 // 127.0.0.53, not 127.0.0.1. Wildcard listeners answer on 127.0.0.1.
307 const host = l.scope === 'all' ? '127.0.0.1' : l.addr.replace(/^::ffff:/, '')
308 probes.set(l.port + '/' + l.addr, await probeHttp(host, l.port))
309 }))
310
311 const built = buildServices({
312 listeners,
313 owners,
314 pidInfo,
315 users: readUsers(),
316 units: parseSystemctlShow(sys.stdout),
317 userUnits: parseSystemctlShow(usr.stdout),
318 pghApps: parsePghStatus(pgh.stdout),
319 probes,
320 labels: this.labels
321 })
322 return {
323 host: os.hostname(),
324 at: new Date().toISOString(),
325 errors: [
326 sys.code !== 0 && 'systemctl: ' + (sys.stderr.trim() || 'exit ' + sys.code),
327 usr.code !== 0 && 'systemctl --user: ' + (usr.stderr.trim() || 'exit ' + usr.code),
328 pgh.code !== 0 && 'pgh app status: ' + (pgh.stderr.trim() || 'exit ' + pgh.code)
329 ].filter(Boolean),
330 ...built
331 }
332 }
333}
lib/lan.js +168 -0
……@@ -0,0 +1,168 @@
1// The rest of the LAN: which devices are up and what they serve.
2//
3// 1. Sweep every address of the host's /24 with a few TCP connects. Any reply,
4// even a refusal, makes the kernel learn the device's MAC, so afterwards
5// /proc/net/arp lists every device that answered ARP -- including ones that
6// drop all TCP (a firewalled Windows box still answers ARP).
7// 2. Names and advertised services from mDNS (avahi).
8// 3. A TCP connect scan of SCAN_PORTS on every device, then an HTTP probe of
9// each open port for its status and page title.
10//
11// Connect scans need no root. The result is kept on disk so a restart shows
12// the last scan at once.
13
14import fs from 'bare-fs'
15import path from 'bare-path'
16import EventEmitter from 'bare-events'
17import { run } from './run.js'
18import { tcpOpen, probeHttp } from './probe.js'
19import { parseArp, parseAvahiBrowse, parseIpAddr } from './parse.js'
20
21const SWEEP_PORTS = [80, 443, 22, 445, 139, 53, 8080, 5357, 62078, 9100]
22
23// Common service ports plus everything a full nmap top-1000 scan of this LAN
24// found open (2026-10-19).
25export const SCAN_PORTS = [
26 21, 22, 23, 25, 53, 80, 81, 88, 110, 111, 135, 139, 143, 389, 443, 445, 515,
27 548, 554, 587, 631, 993, 995, 1080, 1194, 1400, 1883, 1900, 2049, 2179, 2375,
28 3000, 3001, 3128, 3306, 3389, 3399, 3400, 3401, 4040, 4041, 4042, 4050, 4060,
29 4443, 4445, 5000, 5001, 5005, 5055, 5060, 5357, 5432, 5900, 6379, 6443, 7000,
30 7220, 7221, 7878, 8000, 8001, 8008, 8009, 8020, 8080, 8081, 8085, 8086, 8088,
31 8096, 8123, 8181, 8200, 8443, 8800, 8888, 8920, 8989, 9000, 9001, 9090, 9091,
32 9100, 9117, 9696, 9999, 10000, 11434, 20005, 32400, 49152, 62078
33]
34
35const NO_HTTP_PROBE = new Set([21, 22, 23, 25, 53, 110, 111, 135, 139, 143, 445, 515, 1883, 2049, 3306, 3389, 5060, 5432, 5900, 6379, 9100])
36
37async function pool (items, limit, fn) {
38 const out = new Array(items.length)
39 let next = 0
40 const worker = async () => {
41 while (next < items.length) {
42 const i = next++
43 out[i] = await fn(items[i], i)
44 }
45 }
46 await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))
47 return out
48}
49
50export class LanScanner extends EventEmitter {
51 constructor ({ configDir, intervalMs = 30 * 60 * 1000, ports = SCAN_PORTS } = {}) {
52 super()
53 this.file = configDir ? path.join(configDir, 'lan.json') : null
54 this.intervalMs = intervalMs
55 this.ports = ports
56 this.result = null
57 this.scanning = false
58 this.progress = ''
59 this._timer = null
60 this._load()
61 }
62
63 _load () {
64 if (!this.file) return
65 try {
66 this.result = JSON.parse(fs.readFileSync(this.file).toString())
67 } catch {}
68 }
69
70 _save () {
71 if (!this.file) return
72 try {
73 fs.mkdirSync(path.dirname(this.file), { recursive: true })
74 fs.writeFileSync(this.file + '.tmp', JSON.stringify(this.result))
75 fs.renameSync(this.file + '.tmp', this.file)
76 } catch (err) {
77 console.error(JSON.stringify({ msg: 'lan save failed', error: err.message }))
78 }
79 }
80
81 start () {
82 const age = this.result ? Date.now() - Date.parse(this.result.at) : Infinity
83 const first = Math.max(0, Math.min(this.intervalMs - age, this.intervalMs))
84 this._timer = setTimeout(() => this._tick(), first)
85 }
86
87 stop () {
88 clearTimeout(this._timer)
89 this._timer = null
90 }
91
92 _tick () {
93 this.scan().finally(() => {
94 if (this._timer !== null) this._timer = setTimeout(() => this._tick(), this.intervalMs)
95 })
96 }
97
98 state () {
99 return { scanning: this.scanning, progress: this.progress, result: this.result }
100 }
101
102 async scan () {
103 if (this.scanning) return this.result
104 this.scanning = true
105 const started = Date.now()
106 try {
107 this.progress = 'finding the LAN'
108 const own = parseIpAddr((await run('ip', ['-4', '-o', 'addr', 'show', 'scope', 'global'])).stdout)
109 .filter((a) => !/^(tun|tunnel|wg|docker|br-|veth|virbr)/.test(a.dev) && a.prefix >= 16 && a.prefix <= 30)
110 const subnets = [...new Set(own.map((a) => a.ip.split('.').slice(0, 3).join('.')))]
111
112 this.progress = 'sweeping ' + subnets.map((s) => s + '.0/24').join(', ')
113 const sweep = []
114 for (const s of subnets) for (let i = 1; i < 255; i++) for (const p of SWEEP_PORTS) sweep.push([s + '.' + i, p])
115 await pool(sweep, 256, ([h, p]) => tcpOpen(h, p, 500))
116
117 const arp = parseArp(fs.readFileSync('/proc/net/arp').toString())
118 .filter((a) => subnets.some((s) => a.ip.startsWith(s + '.')))
119 const hosts = new Map()
120 for (const a of own) hosts.set(a.ip, { ip: a.ip, mac: null, self: true, names: [], mdns: [], ports: [] })
121 for (const a of arp) if (!hosts.has(a.ip)) hosts.set(a.ip, { ip: a.ip, mac: a.mac, self: false, names: [], mdns: [], ports: [] })
122
123 this.progress = 'mDNS names and services'
124 const [browse] = await Promise.all([
125 run('avahi-browse', ['-a', '-t', '-r', '-p', '--no-db-lookup'], { timeoutMs: 12000 }),
126 pool([...hosts.values()], 16, async (h) => {
127 const r = await run('avahi-resolve', ['-a', h.ip], { timeoutMs: 3000 })
128 const name = r.stdout.trim().split(/\s+/)[1]
129 if (name) h.names.push(name)
130 })
131 ])
132 for (const svc of parseAvahiBrowse(browse.stdout)) {
133 const h = hosts.get(svc.ip)
134 if (!h) continue
135 if (!h.names.includes(svc.host)) h.names.push(svc.host)
136 if (!h.mdns.some((m) => m.type === svc.type && m.port === svc.port && m.name === svc.name)) {
137 h.mdns.push({ name: svc.name, type: svc.type, port: svc.port })
138 }
139 }
140
141 const list = [...hosts.values()]
142 this.progress = 'port scan of ' + list.length + ' devices'
143 const jobs = []
144 for (const h of list) for (const p of this.ports) jobs.push([h, p])
145 const open = await pool(jobs, 192, async ([h, p]) => ((await tcpOpen(h.ip, p, 800)) ? [h, p] : null))
146
147 this.progress = 'reading what answers'
148 await pool(open.filter(Boolean), 32, async ([h, p]) => {
149 if (NO_HTTP_PROBE.has(p)) return h.ports.push({ port: p, http: null, banner: null })
150 const r = await probeHttp(h.ip, p, { timeoutMs: 3000 })
151 h.ports.push({ port: p, http: r.http, banner: r.banner })
152 })
153 for (const h of list) h.ports.sort((a, b) => a.port - b.port)
154 list.sort((a, b) => Number(a.ip.split('.')[3]) - Number(b.ip.split('.')[3]))
155
156 this.result = { at: new Date().toISOString(), durationMs: Date.now() - started, subnets, ports: this.ports.length, hosts: list }
157 this._save()
158 this.emit('scanned', this.result)
159 return this.result
160 } catch (err) {
161 console.error(JSON.stringify({ msg: 'lan scan failed', error: err.stack || String(err) }))
162 return this.result
163 } finally {
164 this.scanning = false
165 this.progress = ''
166 }
167 }
168}
lib/parse.js +190 -0
……@@ -0,0 +1,190 @@
1// Pure parsers for what the host tells us: /proc/net, systemctl, pgh, avahi,
2// HTTP. No I/O here, so every one of them is tested against captured output.
3
4// /proc/net/tcp and tcp6: LISTEN rows only (st 0A). Addresses are hex in host
5// byte order per 32-bit word, so 0100007F is 127.0.0.1.
6export function parseProcNetTcp (text, family = 4) {
7 const out = []
8 for (const line of text.split('\n').slice(1)) {
9 const f = line.trim().split(/\s+/)
10 if (f.length < 10 || f[3] !== '0A') continue
11 const [addrHex, portHex] = f[1].split(':')
12 out.push({
13 family,
14 addr: family === 4 ? ipv4FromHex(addrHex) : ipv6FromHex(addrHex),
15 port: parseInt(portHex, 16),
16 uid: Number(f[7]),
17 inode: Number(f[9])
18 })
19 }
20 return out
21}
22
23function ipv4FromHex (hex) {
24 const n = parseInt(hex, 16)
25 return [n & 255, (n >>> 8) & 255, (n >>> 16) & 255, (n >>> 24) & 255].join('.')
26}
27
28function ipv6FromHex (hex) {
29 // Four little-endian 32-bit words.
30 const bytes = []
31 for (let w = 0; w < 4; w++) {
32 const word = hex.slice(w * 8, w * 8 + 8)
33 for (let b = 3; b >= 0; b--) bytes.push(word.slice(b * 2, b * 2 + 2))
34 }
35 const groups = []
36 for (let i = 0; i < 16; i += 2) groups.push(parseInt(bytes[i] + bytes[i + 1], 16).toString(16))
37 const full = groups.join(':')
38 if (full === '0:0:0:0:0:0:0:0') return '::'
39 if (full === '0:0:0:0:0:0:0:1') return '::1'
40 if (full.startsWith('0:0:0:0:0:ffff:')) {
41 const v4 = bytes.slice(12).map((h) => parseInt(h, 16)).join('.')
42 return '::ffff:' + v4
43 }
44 return full
45}
46
47// Listening on every address, loopback only, or one specific address.
48export function bindScope (addr) {
49 if (addr === '0.0.0.0' || addr === '::') return 'all'
50 if (addr.startsWith('127.') || addr === '::1') return 'local'
51 return 'addr'
52}
53
54// `systemctl show -p A,B,...` for many units: blank-line-separated blocks of
55// Key=Value.
56export function parseSystemctlShow (text) {
57 const units = []
58 let cur = {}
59 for (const line of text.split('\n')) {
60 if (line.trim() === '') {
61 if (cur.Id) units.push(cur)
62 cur = {}
63 continue
64 }
65 const i = line.indexOf('=')
66 if (i > 0) cur[line.slice(0, i)] = line.slice(i + 1)
67 }
68 if (cur.Id) units.push(cur)
69 return units
70}
71
72// A unit the distribution shipped (under /usr/lib or /lib) vs one somebody
73// installed by hand (/etc/systemd, ~/.config/systemd). Hand-made ones are
74// always services worth listing; shipped ones only when they do something
75// visible (own a port) or are named in labels.json.
76export function isVendorUnit (u) {
77 const p = u.FragmentPath || ''
78 return p.startsWith('/usr/lib/') || p.startsWith('/lib/') || p.startsWith('/usr/share/')
79}
80
81// `pgh app status` prints a fixed-width table: header, a dashes row, then one
82// row per app. Column edges come from the dashes row, so values with spaces
83// (none today) would still split correctly.
84export function parsePghStatus (text) {
85 const lines = text.split('\n')
86 const di = lines.findIndex((l) => /^-+(\s+-+)+\s*$/.test(l))
87 if (di < 1) return []
88 const header = lines[di - 1]
89 const cols = []
90 const re = /-+/g
91 let m
92 while ((m = re.exec(lines[di])) !== null) cols.push([m.index, m.index + m[0].length])
93 const names = cols.map(([a, b], i) => header.slice(a, i + 1 < cols.length ? cols[i + 1][0] : undefined).trim())
94 const apps = []
95 for (const line of lines.slice(di + 1)) {
96 if (!line.trim()) continue
97 const row = {}
98 cols.forEach(([a], i) => {
99 const end = i + 1 < cols.length ? cols[i + 1][0] : undefined
100 row[names[i]] = line.slice(a, end).trim()
101 })
102 if (!row.name) continue
103 apps.push({
104 name: row.name,
105 running: row.running === 'true',
106 pid: Number(row.childPid) || null,
107 version: row.version || '',
108 startedAt: row.startedAt || null,
109 lastExit: row.lastExit || null,
110 lastExitCode: row.lastExitCode === '' ? null : Number(row.lastExitCode),
111 autostart: row.autostart === 'true'
112 })
113 }
114 return apps
115}
116
117// The first lines of a raw HTTP/1.x response: status code, Location, <title>.
118// null when the bytes are not HTTP at all (an SSH banner, a binary protocol).
119export function parseHttpHead (text) {
120 const m = /^HTTP\/\d(?:\.\d)?\s+(\d{3})/.exec(text)
121 if (!m) return null
122 const loc = /\r?\nlocation:\s*([^\r\n]+)/i.exec(text)
123 const server = /\r?\nserver:\s*([^\r\n]+)/i.exec(text)
124 return {
125 code: Number(m[1]),
126 location: loc ? loc[1].trim() : null,
127 server: server ? server[1].trim() : null,
128 title: extractTitle(text)
129 }
130}
131
132export function extractTitle (html) {
133 const m = /<title[^>]*>([^<]*)/i.exec(html)
134 if (!m) return null
135 const t = decodeEntities(m[1]).replace(/\s+/g, ' ').trim()
136 return t || null
137}
138
139function decodeEntities (s) {
140 return s.replace(/&(amp|lt|gt|quot|#39|#x27|middot|nbsp);/g, (_, e) => ({
141 amp: '&', lt: '<', gt: '>', quot: '"', '#39': "'", '#x27': "'", middot: '·', nbsp: ' '
142 })[e])
143}
144
145// /proc/net/arp: complete entries only (flags 0x2); 0x0 rows are hosts that
146// never answered.
147export function parseArp (text) {
148 const out = []
149 for (const line of text.split('\n').slice(1)) {
150 const f = line.trim().split(/\s+/)
151 if (f.length < 6) continue
152 if (f[2] !== '0x2' && f[2] !== '0x6') continue
153 if (f[3] === '00:00:00:00:00:00') continue
154 out.push({ ip: f[0], mac: f[3].toLowerCase(), dev: f[5] })
155 }
156 return out
157}
158
159// avahi's parsable output escapes bytes as \DDD (decimal) and specials as \c.
160function avahiUnescape (s) {
161 return s.replace(/\\(\d{3}|.)/g, (_, e) => (e.length === 3 ? String.fromCharCode(Number(e)) : e))
162}
163
164// `avahi-browse -atrp`: resolved ('=') IPv4 rows -> one service each.
165export function parseAvahiBrowse (text) {
166 const out = []
167 for (const line of text.split('\n')) {
168 const f = line.split(';')
169 if (f[0] !== '=' || f.length < 10 || f[2] !== 'IPv4') continue
170 out.push({
171 name: avahiUnescape(f[3]),
172 type: f[4],
173 host: avahiUnescape(f[6]),
174 ip: f[7],
175 port: Number(f[8]),
176 txt: f.slice(9).join(';')
177 })
178 }
179 return out
180}
181
182// `ip -4 -o addr show scope global`: the host's own LAN addresses + prefix.
183export function parseIpAddr (text) {
184 const out = []
185 for (const line of text.split('\n')) {
186 const m = /^\d+:\s+(\S+)\s+inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/.exec(line)
187 if (m) out.push({ dev: m[1], ip: m[2], prefix: Number(m[3]) })
188 }
189 return out
190}
lib/probe.js +64 -0
……@@ -0,0 +1,64 @@
1// What answers on a TCP port: open or not, and if it speaks HTTP, the status,
2// redirect and page title.
3//
4// A raw socket and a hand-written GET rather than an HTTP client: the port may
5// speak anything (SSH, SIP, a binary protocol), and all we want is the first
6// few KB, read with one hard deadline.
7
8import net from 'bare-net'
9import { parseHttpHead } from './parse.js'
10
11const READ_LIMIT = 64 * 1024
12
13// Is anything listening? Resolves true/false, never rejects.
14export function tcpOpen (host, port, timeoutMs = 700) {
15 return new Promise((resolve) => {
16 let done = false
17 const sock = net.createConnection(port, host)
18 const finish = (ok) => {
19 if (done) return
20 done = true
21 clearTimeout(timer)
22 sock.destroy()
23 resolve(ok)
24 }
25 const timer = setTimeout(() => finish(false), timeoutMs)
26 sock.on('connect', () => finish(true))
27 sock.on('error', () => finish(false))
28 })
29}
30
31// { open, http: { code, location, server, title } | null, banner }
32export function probeHttp (host, port, { timeoutMs = 2500, hostHeader = host } = {}) {
33 return new Promise((resolve) => {
34 let done = false
35 let open = false
36 const chunks = []
37 let size = 0
38 const sock = net.createConnection(port, host)
39 const finish = () => {
40 if (done) return
41 done = true
42 clearTimeout(timer)
43 sock.destroy()
44 const text = Buffer.concat(chunks).toString('latin1')
45 const http = parseHttpHead(text)
46 const banner = http ? null : text.split(/\r?\n/)[0].slice(0, 120) || null
47 resolve({ open, http, banner })
48 }
49 const timer = setTimeout(finish, timeoutMs)
50 sock.on('connect', () => {
51 open = true
52 sock.write('GET / HTTP/1.0\r\nHost: ' + hostHeader + ':' + port + '\r\nUser-Agent: host-services\r\nAccept: text/html\r\nConnection: close\r\n\r\n')
53 })
54 sock.on('data', (d) => {
55 chunks.push(d)
56 size += d.length
57 // Enough to see the head and a <title>; stop reading big pages.
58 if (size >= READ_LIMIT || /<\/title>/i.test(d.toString('latin1'))) finish()
59 })
60 sock.on('end', finish)
61 sock.on('close', finish)
62 sock.on('error', finish)
63 })
64}
lib/run.js +53 -0
……@@ -0,0 +1,53 @@
1// Run a command, collect stdout, never hang.
2//
3// Launched via `sh -c 'exec "$0" "$@"'` (as cthuloid-web's discovery does): in
4// bare-subprocess 6.1.0 a spawn of a missing binary throws ENOENT AFTER
5// creating its stdio pipes and never closes them. sh always exists; a missing
6// command becomes exit 127 instead.
7
8import { spawn } from 'bare-subprocess'
9
10export function run (cmd, args = [], { timeoutMs = 15000, maxBytes = 4 * 1024 * 1024, env = null } = {}) {
11 return new Promise((resolve) => {
12 let proc
13 try {
14 const opts = { stdio: ['ignore', 'pipe', 'pipe'] }
15 if (env) opts.env = env
16 proc = spawn('/bin/sh', ['-c', 'exec "$0" "$@"', cmd, ...args], opts)
17 } catch (err) {
18 return resolve({ code: -1, stdout: '', stderr: err.message || String(err) })
19 }
20 const out = []
21 const errs = []
22 let size = 0
23 let done = false
24 const finish = (code) => {
25 if (done) return
26 done = true
27 clearTimeout(timer)
28 resolve({ code, stdout: Buffer.concat(out).toString(), stderr: Buffer.concat(errs).toString() })
29 }
30 const timer = setTimeout(() => {
31 try { proc.kill() } catch {}
32 finish(-2)
33 }, timeoutMs)
34 // 'exit' can arrive before the last stdout chunk: finish only when both
35 // the process has exited and its stdout has ended.
36 let exitCode = null
37 let ended = false
38 proc.stdout.on('data', (d) => {
39 size += d.length
40 if (size <= maxBytes) out.push(d)
41 })
42 proc.stdout.on('end', () => {
43 ended = true
44 if (exitCode !== null) finish(exitCode)
45 })
46 proc.stderr.on('data', (d) => errs.push(d))
47 proc.on('error', () => finish(-1))
48 proc.on('exit', (code) => {
49 exitCode = code
50 if (ended) finish(code)
51 })
52 })
53}
lib/server.js +63 -0
……@@ -0,0 +1,63 @@
1// HTTP surface: the page, and a read-only JSON API.
2//
3// GET /api/host this host's services (cached ~15 s)
4// GET /api/lan the last LAN scan + whether one is running
5// POST /api/lan/scan start a LAN scan now
6//
7// ⛔ No login: LAN-only, like the services it lists. The one POST must carry
8// `x-host-services: 1` -- a cross-site page cannot add a custom header without
9// a CORS preflight, and this server never answers one.
10
11import http from 'bare-http1'
12import fs from 'bare-fs'
13import path from 'bare-path'
14
15const STATIC_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'static')
16const ASSETS = {
17 '/': ['index.html', 'text/html; charset=utf-8'],
18 '/index.html': ['index.html', 'text/html; charset=utf-8'],
19 '/app.js': ['app.js', 'text/javascript; charset=utf-8'],
20 '/app.css': ['app.css', 'text/css; charset=utf-8']
21}
22
23function sendJSON (res, status, obj) {
24 const body = JSON.stringify(obj)
25 res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'cache-control': 'no-store' })
26 res.end(body)
27}
28
29export function createServer ({ host, lan }) {
30 return http.createServer((req, res) => {
31 const url = req.url.split('?')[0]
32
33 if (url === '/api/host' && req.method === 'GET') {
34 host.snapshot().then((s) => sendJSON(res, 200, s), (err) => {
35 console.error('host snapshot failed: ' + (err.stack || err))
36 sendJSON(res, 500, { error: err.message })
37 })
38 return
39 }
40 if (url === '/api/lan' && req.method === 'GET') return sendJSON(res, 200, lan.state())
41 if (url === '/api/lan/scan' && req.method === 'POST') {
42 if (req.headers['x-host-services'] !== '1') return sendJSON(res, 403, { error: 'missing x-host-services header' })
43 lan.scan()
44 return sendJSON(res, 202, { ok: true, scanning: true })
45 }
46 if (url.startsWith('/api/')) return sendJSON(res, 404, { error: 'not found' })
47
48 const asset = ASSETS[url]
49 if (!asset || req.method !== 'GET') {
50 res.writeHead(404, { 'content-type': 'text/plain' })
51 return res.end('not found')
52 }
53 let body
54 try {
55 body = fs.readFileSync(path.join(STATIC_DIR, asset[0]))
56 } catch {
57 res.writeHead(500, { 'content-type': 'text/plain' })
58 return res.end('asset missing: ' + asset[0])
59 }
60 res.writeHead(200, { 'content-type': asset[1], 'content-length': body.length, 'cache-control': 'no-cache' })
61 res.end(body)
62 })
63}
package-lock.json +1021 -0
……@@ -0,0 +1,1021 @@
1{
2 "name": "host-services",
3 "version": "0.1.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "host-services",
9 "version": "0.1.0",
10 "dependencies": {
11 "bare-events": "2.9.2",
12 "bare-fs": "4.8.2",
13 "bare-http1": "4.6.2",
14 "bare-net": "2.3.3",
15 "bare-os": "3.9.3",
16 "bare-path": "3.1.2",
17 "bare-process": "4.5.1",
18 "bare-subprocess": "6.1.0"
19 },
20 "bin": {
21 "host-services": "index.js"
22 },
23 "devDependencies": {
24 "brittle": "4.1.1"
25 }
26 },
27 "node_modules/@jridgewell/resolve-uri": {
28 "version": "3.1.2",
29 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
30 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
31 "dev": true,
32 "license": "MIT",
33 "engines": {
34 "node": ">=6.0.0"
35 }
36 },
37 "node_modules/@jridgewell/sourcemap-codec": {
38 "version": "1.6.0",
39 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
40 "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
41 "dev": true,
42 "license": "MIT"
43 },
44 "node_modules/@jridgewell/trace-mapping": {
45 "version": "0.3.31",
46 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
47 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
48 "dev": true,
49 "license": "MIT",
50 "dependencies": {
51 "@jridgewell/resolve-uri": "^3.1.0",
52 "@jridgewell/sourcemap-codec": "^1.4.14"
53 }
54 },
55 "node_modules/@types/istanbul-lib-coverage": {
56 "version": "2.0.6",
57 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
58 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
59 "dev": true,
60 "license": "MIT"
61 },
62 "node_modules/b4a": {
63 "version": "1.9.0",
64 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz",
65 "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==",
66 "license": "Apache-2.0",
67 "peerDependencies": {
68 "react-native-b4a": "*"
69 },
70 "peerDependenciesMeta": {
71 "react-native-b4a": {
72 "optional": true
73 }
74 }
75 },
76 "node_modules/bare-abort": {
77 "version": "2.0.13",
78 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
79 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
80 "license": "Apache-2.0"
81 },
82 "node_modules/bare-addon-resolve": {
83 "version": "1.10.1",
84 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.1.tgz",
85 "integrity": "sha512-F/SD2du8keuYSb4xipnGz5j2E6yhNdHA8ZVxtHae6h2uOrpBIjjbhXvjzKZbr5XUOzqBzh/i8GVFycj2DlFQIA==",
86 "dev": true,
87 "license": "Apache-2.0",
88 "dependencies": {
89 "bare-module-resolve": "^1.10.0",
90 "bare-semver": "^1.0.0"
91 },
92 "peerDependencies": {
93 "bare-url": "*"
94 },
95 "peerDependenciesMeta": {
96 "bare-url": {
97 "optional": true
98 }
99 }
100 },
101 "node_modules/bare-ansi-escapes": {
102 "version": "2.2.3",
103 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
104 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
105 "dev": true,
106 "license": "Apache-2.0",
107 "dependencies": {
108 "bare-stream": "^2.6.5"
109 },
110 "peerDependencies": {
111 "bare-buffer": "*"
112 },
113 "peerDependenciesMeta": {
114 "bare-buffer": {
115 "optional": true
116 }
117 }
118 },
119 "node_modules/bare-buffer": {
120 "version": "3.7.1",
121 "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.7.1.tgz",
122 "integrity": "sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg==",
123 "license": "Apache-2.0",
124 "peer": true,
125 "engines": {
126 "bare": ">=1.20.0"
127 }
128 },
129 "node_modules/bare-bundle": {
130 "version": "1.11.0",
131 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.11.0.tgz",
132 "integrity": "sha512-msaGQLaojz+EszDhf0qvCTmVWgJFSqbC5bUmEb9AKyVeglmJpx1EbCGqQTiXycCV7oA++MYqTagh/YdIZ//bmQ==",
133 "dev": true,
134 "license": "Apache-2.0",
135 "peerDependencies": {
136 "bare-buffer": "*",
137 "bare-url": "*"
138 },
139 "peerDependenciesMeta": {
140 "bare-buffer": {
141 "optional": true
142 },
143 "bare-url": {
144 "optional": true
145 }
146 }
147 },
148 "node_modules/bare-channel": {
149 "version": "5.3.0",
150 "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.3.0.tgz",
151 "integrity": "sha512-W42Pkl/IjfkIVObQnixidRThST6ZcqY0u6AMTLb2+Jc/enBVsXPxzmyhEDoQjNNYi4WBGkOG7YHR4X46Ah9xfA==",
152 "dev": true,
153 "license": "Apache-2.0",
154 "dependencies": {
155 "bare-events": "^2.0.0",
156 "bare-stream": "^2.7.0",
157 "bare-structured-clone": "^2.0.0"
158 },
159 "engines": {
160 "bare": ">=1.7.0"
161 }
162 },
163 "node_modules/bare-cov": {
164 "version": "1.2.3",
165 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.3.tgz",
166 "integrity": "sha512-M9XAEUxk3KKMMD2g8WwbrR0grc25RhkS95XnW6+PksSQdN1C43gYSOa0fQeun5Vo6rvTv501FEYyERKA43EzWg==",
167 "dev": true,
168 "license": "Apache-2.0",
169 "dependencies": {
170 "bare-fs": "^4.8.1",
171 "bare-inspector": "^6.1.0",
172 "bare-path": "^3.1.2",
173 "bare-process": "^4.5.1",
174 "bare-url": "^2.5.4",
175 "bare-v8-to-istanbul": "^1.0.4",
176 "picomatch": "^4.0.7",
177 "which-runtime": "^1.4.0"
178 }
179 },
180 "node_modules/bare-crypto": {
181 "version": "1.15.4",
182 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.4.tgz",
183 "integrity": "sha512-4luuiLnxIJvM7ZyyW6pXbaaGoQDZcIkfcVZrPzJ3oIpdi2UIP37dROmzBWqI2lnrzfKoQlKWM8ryHmyG2svViw==",
184 "dev": true,
185 "license": "Apache-2.0",
186 "dependencies": {
187 "bare-assert": "^1.2.0",
188 "bare-stream": "^2.6.3"
189 },
190 "peerDependencies": {
191 "bare-buffer": "*"
192 },
193 "peerDependenciesMeta": {
194 "bare-buffer": {
195 "optional": true
196 }
197 }
198 },
199 "node_modules/bare-crypto/node_modules/bare-assert": {
200 "version": "1.2.0",
201 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
202 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
203 "dev": true,
204 "license": "Apache-2.0",
205 "dependencies": {
206 "bare-inspect": "^3.1.2"
207 }
208 },
209 "node_modules/bare-debug-log": {
210 "version": "2.0.0",
211 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
212 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
213 "dev": true,
214 "license": "Apache-2.0",
215 "dependencies": {
216 "bare-os": "^3.0.1"
217 }
218 },
219 "node_modules/bare-dns": {
220 "version": "2.2.1",
221 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.2.1.tgz",
222 "integrity": "sha512-76kta7f4YMiXwhdlK1q8AFnVvSlZ5yv4FL4AH83KoTRErgj0Ttm6Jo9993+gWEWQfl7VZTHtihPozstlknXszw==",
223 "license": "Apache-2.0",
224 "engines": {
225 "bare": ">=1.7.0"
226 },
227 "peerDependencies": {
228 "bare-buffer": "*"
229 },
230 "peerDependenciesMeta": {
231 "bare-buffer": {
232 "optional": true
233 }
234 }
235 },
236 "node_modules/bare-encoding": {
237 "version": "1.0.3",
238 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
239 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
240 "dev": true,
241 "license": "Apache-2.0",
242 "peerDependencies": {
243 "bare-buffer": "*"
244 },
245 "peerDependenciesMeta": {
246 "bare-buffer": {
247 "optional": true
248 }
249 }
250 },
251 "node_modules/bare-env": {
252 "version": "3.0.1",
253 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.1.tgz",
254 "integrity": "sha512-BdoLvnzaWR0YyyJreEx4zG2Od0AC/s9JSZ+EXyMZKunpn8zEgR/dGv37hdFUaY1bjDkcRKvcKkFyJIwlJ0CaYA==",
255 "license": "Apache-2.0",
256 "dependencies": {
257 "bare-os": "^3.0.1"
258 }
259 },
260 "node_modules/bare-events": {
261 "version": "2.9.2",
262 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz",
263 "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==",
264 "license": "Apache-2.0",
265 "peerDependencies": {
266 "bare-abort-controller": "*"
267 },
268 "peerDependenciesMeta": {
269 "bare-abort-controller": {
270 "optional": true
271 }
272 }
273 },
274 "node_modules/bare-format": {
275 "version": "1.0.2",
276 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
277 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
278 "dev": true,
279 "license": "Apache-2.0",
280 "dependencies": {
281 "bare-inspect": "^3.0.0"
282 }
283 },
284 "node_modules/bare-fs": {
285 "version": "4.8.2",
286 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.2.tgz",
287 "integrity": "sha512-+ZI68KHMUvosXfKbg/UOHK0tbCdRnegbvPEdEcZ3Nd6TetieQsJPRXBRXPdLyy8+3VSEbPXtsumTpEtt78xv9w==",
288 "license": "Apache-2.0",
289 "dependencies": {
290 "bare-events": "^2.5.4",
291 "bare-path": "^3.0.0",
292 "bare-stream": "^2.6.4",
293 "bare-url": "^2.2.2",
294 "fast-fifo": "^1.3.2"
295 },
296 "engines": {
297 "bare": ">=1.28.0"
298 },
299 "peerDependencies": {
300 "bare-buffer": "*"
301 },
302 "peerDependenciesMeta": {
303 "bare-buffer": {
304 "optional": true
305 }
306 }
307 },
308 "node_modules/bare-hrtime": {
309 "version": "2.1.2",
310 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.2.tgz",
311 "integrity": "sha512-ePu0axonbewvuUv9TFqjfvnZTaw+mL4JNx1q79dw8T3DIQEjqHs+pXyj92ghepa8K14KLtX3GdUuAVB+faaGDw==",
312 "license": "Apache-2.0"
313 },
314 "node_modules/bare-http-parser": {
315 "version": "2.1.4",
316 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-2.1.4.tgz",
317 "integrity": "sha512-6CRjxqMdrEoKKBCS6FbdZUuVsRtyqq18BUae9l9N5okSV75S9lUAF9f5aFSKTNybBS/hcnlxFNocdpXbd2Ohaw==",
318 "license": "Apache-2.0"
319 },
320 "node_modules/bare-http1": {
321 "version": "4.6.2",
322 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.6.2.tgz",
323 "integrity": "sha512-fnvBPNUH6KfdcEG7sKKKtvc3/9BEn3vkmJPgdtSLqGl6i2J9Csnl14mVv78ODPn8p3Zu2ZfmlG5XQzQQ45fwLw==",
324 "license": "Apache-2.0",
325 "dependencies": {
326 "bare-events": "^2.6.0",
327 "bare-hrtime": "^2.1.1",
328 "bare-http-parser": "^2.1.0",
329 "bare-stream": "^2.10.0",
330 "bare-tcp": "^2.2.0"
331 },
332 "peerDependencies": {
333 "bare-buffer": "*",
334 "bare-url": "*"
335 },
336 "peerDependenciesMeta": {
337 "bare-buffer": {
338 "optional": true
339 },
340 "bare-url": {
341 "optional": true
342 }
343 }
344 },
345 "node_modules/bare-https": {
346 "version": "3.1.0",
347 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-3.1.0.tgz",
348 "integrity": "sha512-HNAS0q7CBAXGDFRy/xaYJEsAaVcvyDRXKU1b/KxS/zdcfBWsQVB0TntSzz3IlqZ5ZWHiaOOrMYAO/oIDgpTLkQ==",
349 "dev": true,
350 "license": "Apache-2.0",
351 "dependencies": {
352 "bare-http1": "^4.4.0",
353 "bare-tcp": "^2.2.0",
354 "bare-tls": "^3.0.0"
355 }
356 },
357 "node_modules/bare-inspect": {
358 "version": "3.1.10",
359 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.10.tgz",
360 "integrity": "sha512-Qu01XZNTIT2/bS9k7c6A3OXuAzBnWwDCejGri76BUltdFqVe6Et/IxVOSSbcDpreSSWwqQ5pAu/bxFE6hPDWRQ==",
361 "dev": true,
362 "license": "Apache-2.0",
363 "dependencies": {
364 "bare-ansi-escapes": "^2.1.0",
365 "bare-type": "^1.0.0"
366 },
367 "engines": {
368 "bare": ">=1.18.0"
369 }
370 },
371 "node_modules/bare-inspector": {
372 "version": "6.1.0",
373 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz",
374 "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==",
375 "dev": true,
376 "license": "Apache-2.0",
377 "dependencies": {
378 "bare-events": "^2.1.0",
379 "bare-http1": "^4.0.0",
380 "bare-stream": "^2.0.0",
381 "bare-url": "^2.0.0",
382 "bare-ws": "^3.0.0"
383 },
384 "engines": {
385 "bare": ">=1.29.0"
386 },
387 "peerDependencies": {
388 "bare-tcp": "*"
389 },
390 "peerDependenciesMeta": {
391 "bare-tcp": {
392 "optional": true
393 }
394 }
395 },
396 "node_modules/bare-module": {
397 "version": "6.4.0",
398 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz",
399 "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==",
400 "dev": true,
401 "license": "Apache-2.0",
402 "dependencies": {
403 "bare-bundle": "^1.3.0",
404 "bare-module-lexer": "^1.0.0",
405 "bare-module-resolve": "^1.8.0",
406 "bare-path": "^3.0.0",
407 "bare-type-stripper": "^0.1.2",
408 "bare-url": "^2.0.1"
409 },
410 "engines": {
411 "bare": ">=1.29.4"
412 },
413 "peerDependencies": {
414 "bare-buffer": "*"
415 },
416 "peerDependenciesMeta": {
417 "bare-buffer": {
418 "optional": true
419 }
420 }
421 },
422 "node_modules/bare-module-lexer": {
423 "version": "1.7.0",
424 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.7.0.tgz",
425 "integrity": "sha512-LD5eiVhDyiMRfMtxK3E+0SeL95nkLNl37YUccXWEaErmfkJi596OECtUoH3X+YPomthwW2oQEch9c7aOOJ4ePA==",
426 "dev": true,
427 "license": "Apache-2.0",
428 "dependencies": {
429 "require-addon": "^1.0.2"
430 },
431 "engines": {
432 "bare": ">=1.0.0",
433 "node": "^22.21.0 || >=24.9.0"
434 },
435 "peerDependencies": {
436 "bare-buffer": "*"
437 },
438 "peerDependenciesMeta": {
439 "bare-buffer": {
440 "optional": true
441 }
442 }
443 },
444 "node_modules/bare-module-resolve": {
445 "version": "1.12.5",
446 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.5.tgz",
447 "integrity": "sha512-VOncxVvVk8SQVw9vhcBnoTJD/74aR5DgdRPCm0gQ7uB5MsWpBJnoCeJrwEHKiz09O83ndf3NTjsz3LEuFxAq5A==",
448 "dev": true,
449 "license": "Apache-2.0",
450 "dependencies": {
451 "bare-semver": "^1.0.0"
452 },
453 "peerDependencies": {
454 "bare-url": "*"
455 },
456 "peerDependenciesMeta": {
457 "bare-url": {
458 "optional": true
459 }
460 }
461 },
462 "node_modules/bare-net": {
463 "version": "2.3.3",
464 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.3.tgz",
465 "integrity": "sha512-q1noXFJKn+eNi6vXYv2Y+5FTnn+o7qpli4m2lUZTdK8cBjVs2zGaykuzTUIa29v70eOsQI9OAIyYwN3ODHtFaQ==",
466 "license": "Apache-2.0",
467 "dependencies": {
468 "bare-events": "^2.2.2",
469 "bare-pipe": "^4.0.0",
470 "bare-stream": "^2.0.0",
471 "bare-tcp": "^2.0.0"
472 }
473 },
474 "node_modules/bare-os": {
475 "version": "3.9.3",
476 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz",
477 "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==",
478 "license": "Apache-2.0",
479 "engines": {
480 "bare": ">=1.14.0"
481 }
482 },
483 "node_modules/bare-path": {
484 "version": "3.1.2",
485 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz",
486 "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==",
487 "license": "Apache-2.0"
488 },
489 "node_modules/bare-pipe": {
490 "version": "4.3.1",
491 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.3.1.tgz",
492 "integrity": "sha512-3P4MYTgOys1Bbz5dgWzwfroNOOl8x1zWJHTHVlwsojThhV/uJPRf+qFrYYBOnI0vQiJJLJtHlw8a4Osj/kcsJQ==",
493 "license": "Apache-2.0",
494 "dependencies": {
495 "bare-events": "^2.5.4",
496 "bare-stream": "^2.6.4"
497 },
498 "engines": {
499 "bare": ">=1.16.0"
500 },
501 "peerDependencies": {
502 "bare-buffer": "*"
503 },
504 "peerDependenciesMeta": {
505 "bare-buffer": {
506 "optional": true
507 }
508 }
509 },
510 "node_modules/bare-posix": {
511 "version": "1.0.1",
512 "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz",
513 "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==",
514 "license": "Apache-2.0"
515 },
516 "node_modules/bare-process": {
517 "version": "4.5.1",
518 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz",
519 "integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==",
520 "license": "Apache-2.0",
521 "dependencies": {
522 "bare-abort": "^2.0.13",
523 "bare-env": "^3.0.0",
524 "bare-events": "^2.3.1",
525 "bare-hrtime": "^2.0.0",
526 "bare-os": "^3.7.1",
527 "bare-posix": "^1.0.1",
528 "bare-signals": "^5.0.0",
529 "bare-stdio": "^1.0.1"
530 }
531 },
532 "node_modules/bare-semver": {
533 "version": "1.1.0",
534 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz",
535 "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==",
536 "dev": true,
537 "license": "Apache-2.0"
538 },
539 "node_modules/bare-signals": {
540 "version": "5.0.0",
541 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-5.0.0.tgz",
542 "integrity": "sha512-8Gn8bBFUh2AUCJ9wWWFjSGWIo1HIUSIQnXRJGSm/f7GCDMqsuJhRmR1dT+HtDaDBkeu2l8Koxt2JLurfJ5yHVw==",
543 "license": "Apache-2.0",
544 "dependencies": {
545 "bare-events": "^2.5.3"
546 },
547 "engines": {
548 "bare": ">=1.7.0"
549 }
550 },
551 "node_modules/bare-stdio": {
552 "version": "1.0.3",
553 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.3.tgz",
554 "integrity": "sha512-8BRMx7RWMWCbBmKhyHgBco62J+Pgss7+EPI21L3R9+w0UpwDYWUUcbE0YTOesGyt7M6N7VtoshYbzwrP0pI52Q==",
555 "license": "Apache-2.0",
556 "dependencies": {
557 "bare-fs": "^4.5.2",
558 "bare-pipe": "^4.1.5",
559 "bare-tty": "^5.0.3"
560 }
561 },
562 "node_modules/bare-stream": {
563 "version": "2.13.4",
564 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz",
565 "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==",
566 "license": "Apache-2.0",
567 "dependencies": {
568 "b4a": "^1.8.1",
569 "streamx": "^2.25.0",
570 "teex": "^1.0.1"
571 },
572 "peerDependencies": {
573 "bare-abort-controller": "*",
574 "bare-buffer": "*",
575 "bare-events": "*"
576 },
577 "peerDependenciesMeta": {
578 "bare-abort-controller": {
579 "optional": true
580 },
581 "bare-buffer": {
582 "optional": true
583 },
584 "bare-events": {
585 "optional": true
586 }
587 }
588 },
589 "node_modules/bare-structured-clone": {
590 "version": "1.6.0",
591 "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.6.0.tgz",
592 "integrity": "sha512-AZjEERyqF7kAuudlmgrweT2KwwWM0LsGG4Gx/bWyFwJrKU7E3wNG8c09z2DIrdw93p3vDtfOX8fyHOcXfy5m+g==",
593 "license": "Apache-2.0",
594 "dependencies": {
595 "bare-buffer": "^3.6.0",
596 "bare-type": "^1.1.0",
597 "bare-url": "^2.4.0",
598 "compact-encoding": "^3.0.1"
599 },
600 "engines": {
601 "bare": ">=1.2.0"
602 }
603 },
604 "node_modules/bare-stylize": {
605 "version": "0.0.1",
606 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
607 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
608 "dev": true,
609 "license": "Apache-2.0",
610 "dependencies": {
611 "bare-ansi-escapes": "^2.2.3",
612 "bare-process": "^4.2.1"
613 }
614 },
615 "node_modules/bare-subprocess": {
616 "version": "6.1.0",
617 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-6.1.0.tgz",
618 "integrity": "sha512-8L6KtbmreDy4Fc1BdDqaDo9fg0nbedUmTSFQxYPV0uHIM2BBAX8+E0LyMDKgkk0I+mlKto80WYzOnIhT8VDdOg==",
619 "license": "Apache-2.0",
620 "dependencies": {
621 "bare-env": "^3.0.0",
622 "bare-events": "^2.5.4",
623 "bare-os": "^3.0.1",
624 "bare-pipe": "^4.2.0",
625 "bare-structured-clone": "^1.5.2",
626 "bare-tcp": "^2.4.1",
627 "bare-url": "^2.2.2"
628 },
629 "engines": {
630 "bare": ">=1.7.0"
631 },
632 "peerDependencies": {
633 "bare-buffer": "*"
634 },
635 "peerDependenciesMeta": {
636 "bare-buffer": {
637 "optional": true
638 }
639 }
640 },
641 "node_modules/bare-tcp": {
642 "version": "2.6.1",
643 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.6.1.tgz",
644 "integrity": "sha512-8f8UFP7O27gIGaVWJPysQ2am8LSD6YCuVSbnTbOrQjUD0RkWEZexvi2Q66P99MLxSephlyjoU0mLxWmozRJMnA==",
645 "license": "Apache-2.0",
646 "dependencies": {
647 "bare-dns": "^2.0.4",
648 "bare-events": "^2.5.4",
649 "bare-stream": "^2.6.4"
650 },
651 "engines": {
652 "bare": ">=1.16.0"
653 },
654 "peerDependencies": {
655 "bare-pipe": "*"
656 },
657 "peerDependenciesMeta": {
658 "bare-pipe": {
659 "optional": true
660 }
661 }
662 },
663 "node_modules/bare-tls": {
664 "version": "3.1.11",
665 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.11.tgz",
666 "integrity": "sha512-lCQMepLYPPmdunO4MtkVe6l619Kdrt47/0HDGOgJY6zVvEeFACZD1hkh1kRw6zJk2NQHLcnebggYQZ/z6J/cPg==",
667 "dev": true,
668 "license": "Apache-2.0",
669 "dependencies": {
670 "bare-net": "^2.0.1",
671 "bare-stream": "^2.6.4"
672 },
673 "engines": {
674 "bare": ">=1.7.0"
675 }
676 },
677 "node_modules/bare-tty": {
678 "version": "5.2.1",
679 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.2.1.tgz",
680 "integrity": "sha512-vH9ggdu6q+HilCKcYCfSzwuN2QaR4+gGP+JiYN5zKMhMI6tKeKnZMCuE/AJoF9lbSo0FriPdZoKA5bO2b0Mk3w==",
681 "license": "Apache-2.0",
682 "dependencies": {
683 "bare-events": "^2.2.0",
684 "bare-signals": "^5.0.0",
685 "bare-stream": "^2.0.0"
686 },
687 "engines": {
688 "bare": ">=1.16.0"
689 }
690 },
691 "node_modules/bare-type": {
692 "version": "1.1.1",
693 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.1.tgz",
694 "integrity": "sha512-5HwbjDbnYr+Lwu5I59IqwOFcRcVo4jxo408t42gKhKnikw0ojwyPXPlx58AQP4dR2VvGw57ppX/qxYCn60Wujw==",
695 "license": "Apache-2.0",
696 "engines": {
697 "bare": ">=1.2.0"
698 }
699 },
700 "node_modules/bare-type-stripper": {
701 "version": "0.1.7",
702 "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.7.tgz",
703 "integrity": "sha512-kiYTY1PqJnCQmaGT4Q23gskKCTtxqinPdA0SKMO80rjyLMdZhkT+mALf+/UryvovV6ysXtYXGSNPZR4wFhCI/g==",
704 "dev": true,
705 "license": "Apache-2.0",
706 "dependencies": {
707 "require-addon": "^1.0.2"
708 },
709 "engines": {
710 "bare": ">=1.0.0",
711 "node": "^22.21.0 || >=24.9.0"
712 },
713 "peerDependencies": {
714 "bare-buffer": "*"
715 },
716 "peerDependenciesMeta": {
717 "bare-buffer": {
718 "optional": true
719 }
720 }
721 },
722 "node_modules/bare-url": {
723 "version": "2.5.4",
724 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz",
725 "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==",
726 "license": "Apache-2.0",
727 "peer": true,
728 "dependencies": {
729 "bare-path": "^3.0.0"
730 }
731 },
732 "node_modules/bare-utils": {
733 "version": "1.6.0",
734 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
735 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
736 "dev": true,
737 "license": "Apache-2.0",
738 "dependencies": {
739 "bare-debug-log": "^2.0.0",
740 "bare-encoding": "^1.0.0",
741 "bare-format": "^1.0.0",
742 "bare-inspect": "^3.0.0",
743 "bare-stylize": "^0.0.1",
744 "bare-type": "^1.0.6"
745 }
746 },
747 "node_modules/bare-v8-to-istanbul": {
748 "version": "1.0.4",
749 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.4.tgz",
750 "integrity": "sha512-aYdse9SdVep4NT18jyMehBShfZgvyQ36Tm+oha2nQIHGcxvdBk0xZLbcYsDPzCTqGdJ5SfXa3VvZUSWyoa3L4w==",
751 "dev": true,
752 "license": "Apache-2.0",
753 "dependencies": {
754 "bare-assert": "^1.2.0",
755 "bare-fs": "^4.8.1",
756 "bare-module": "^6.4.0",
757 "bare-path": "^3.1.2",
758 "bare-process": "^4.5.1",
759 "bare-url": "^2.5.4",
760 "bare-utils": "^1.6.0",
761 "v8-to-istanbul": "^9.3.0",
762 "which-runtime": "^1.4.0"
763 }
764 },
765 "node_modules/bare-v8-to-istanbul/node_modules/bare-assert": {
766 "version": "1.2.0",
767 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
768 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
769 "dev": true,
770 "license": "Apache-2.0",
771 "dependencies": {
772 "bare-inspect": "^3.1.2"
773 }
774 },
775 "node_modules/bare-ws": {
776 "version": "3.2.0",
777 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.2.0.tgz",
778 "integrity": "sha512-9PPJX5JrhuObWZGyhYCrQ1DbhjY1BLVALkZF56r0SYnfyN18ok84yUETcQ377aC2W6w8IqBewjQ1GlovtiXA1Q==",
779 "dev": true,
780 "license": "Apache-2.0",
781 "dependencies": {
782 "bare-crypto": "^1.2.0",
783 "bare-events": "^2.3.1",
784 "bare-http1": "^4.0.0",
785 "bare-https": "^3.0.0",
786 "bare-stream": "^2.1.2"
787 },
788 "peerDependencies": {
789 "bare-buffer": "*",
790 "bare-url": "*"
791 },
792 "peerDependenciesMeta": {
793 "bare-buffer": {
794 "optional": true
795 },
796 "bare-url": {
797 "optional": true
798 }
799 }
800 },
801 "node_modules/brittle": {
802 "version": "4.1.1",
803 "resolved": "https://registry.npmjs.org/brittle/-/brittle-4.1.1.tgz",
804 "integrity": "sha512-FHzlSZ5KqB+2uXN1GYboQDzUkR+Z1n6MOXCnrSal5t41IJ9TM/EB+zCyIaOrEYgBpToRQn6U6NAqTYe45CqE5g==",
805 "dev": true,
806 "license": "Apache-2.0",
807 "dependencies": {
808 "b4a": "^1.8.0",
809 "bare-assert": "^1.2.0",
810 "bare-buffer": "^3.6.0",
811 "bare-channel": "^5.2.3",
812 "bare-cov": "^1.2.1",
813 "bare-fs": "^4.5.5",
814 "bare-os": "^3.7.1",
815 "bare-path": "^3.0.0",
816 "bare-process": "^4.3.0",
817 "bare-subprocess": "^6.1.0",
818 "bare-url": "^2.3.2",
819 "bare-utils": "^1.6.0",
820 "error-stack-parser": "^2.1.4",
821 "globbie": "^1.0.3",
822 "paparam": "^1.10.0",
823 "same-object": "^1.0.2",
824 "streamx": "^2.25.0",
825 "test-tmp": "^1.4.0",
826 "tmatch": "^5.0.0",
827 "which-runtime": "^1.3.2"
828 },
829 "bin": {
830 "brittle": "advise.js",
831 "brittle-bare": "brittle-bare.js",
832 "brittle-make-test": "make-test.js",
833 "brittle-node": "brittle-node.js"
834 }
835 },
836 "node_modules/brittle/node_modules/bare-assert": {
837 "version": "1.2.0",
838 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
839 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
840 "dev": true,
841 "license": "Apache-2.0",
842 "dependencies": {
843 "bare-inspect": "^3.1.2"
844 }
845 },
846 "node_modules/compact-encoding": {
847 "version": "3.5.2",
848 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.2.tgz",
849 "integrity": "sha512-rgGcWdfxyuLwJHMUXI0OrYGpJe3VN4RZyovbxWVlxPYIsUZutOTfP3shmDyDi/iaybwcxMsTJDDuxQa4xRG0RQ==",
850 "license": "Apache-2.0",
851 "dependencies": {
852 "b4a": "^1.3.0"
853 }
854 },
855 "node_modules/convert-source-map": {
856 "version": "2.0.0",
857 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
858 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
859 "dev": true,
860 "license": "MIT"
861 },
862 "node_modules/error-stack-parser": {
863 "version": "2.1.4",
864 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
865 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
866 "dev": true,
867 "license": "MIT",
868 "dependencies": {
869 "stackframe": "^1.3.4"
870 }
871 },
872 "node_modules/events-universal": {
873 "version": "1.0.1",
874 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
875 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
876 "license": "Apache-2.0",
877 "dependencies": {
878 "bare-events": "^2.7.0"
879 }
880 },
881 "node_modules/fast-fifo": {
882 "version": "1.3.2",
883 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
884 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
885 "license": "MIT"
886 },
887 "node_modules/globbie": {
888 "version": "1.0.6",
889 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.6.tgz",
890 "integrity": "sha512-GcxSSfWinZNZNW3+RsPBi2nW4jJcUJsOKiJvuXoeO3Y8pdd045NVPILCAoNCzYUsPw5XAUA/GXwRXF+ABcAeOQ==",
891 "dev": true,
892 "license": "Apache-2.0",
893 "dependencies": {
894 "bare-fs": "^4.7.4",
895 "bare-path": "^3.1.1",
896 "bare-process": "^4.5.1",
897 "picomatch": "^4.0.5"
898 }
899 },
900 "node_modules/paparam": {
901 "version": "1.13.0",
902 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.13.0.tgz",
903 "integrity": "sha512-WV8vCnEBPKcSSEIDSaAetppO+C0+OUJ6gjO1BGbhvIp2jc8zfV9C/8NARjEGgnUo5DDwdNsGOrFTZKLFTTeB0A==",
904 "dev": true,
905 "license": "Apache-2.0"
906 },
907 "node_modules/picomatch": {
908 "version": "4.0.7",
909 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
910 "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
911 "dev": true,
912 "license": "MIT",
913 "engines": {
914 "node": ">=12"
915 },
916 "funding": {
917 "url": "https://github.com/sponsors/jonschlinkert"
918 }
919 },
920 "node_modules/require-addon": {
921 "version": "1.3.0",
922 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.3.0.tgz",
923 "integrity": "sha512-j8pifaPNZI7ShKY3ihXZwF0qaEjua8zmlqPvDil0a8SBnyRvjCbAiEQuzO0UcoXH1sqU5U/ilXSjYd1W5NYwAA==",
924 "dev": true,
925 "license": "Apache-2.0",
926 "dependencies": {
927 "bare-addon-resolve": "^1.3.0"
928 },
929 "engines": {
930 "bare": ">=1.10.0"
931 }
932 },
933 "node_modules/same-object": {
934 "version": "1.0.2",
935 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
936 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
937 "dev": true,
938 "license": "MIT"
939 },
940 "node_modules/stackframe": {
941 "version": "1.3.4",
942 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
943 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
944 "dev": true,
945 "license": "MIT"
946 },
947 "node_modules/streamx": {
948 "version": "2.28.1",
949 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz",
950 "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==",
951 "license": "MIT",
952 "dependencies": {
953 "events-universal": "^1.0.0",
954 "fast-fifo": "^1.3.2",
955 "text-decoder": "^1.1.0"
956 }
957 },
958 "node_modules/teex": {
959 "version": "1.0.1",
960 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
961 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
962 "license": "MIT",
963 "dependencies": {
964 "streamx": "^2.12.5"
965 }
966 },
967 "node_modules/test-tmp": {
968 "version": "1.4.0",
969 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
970 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
971 "dev": true,
972 "license": "MIT",
973 "dependencies": {
974 "bare-fs": "^4.0.1",
975 "bare-os": "^3.3.0",
976 "bare-path": "^3.0.0"
977 }
978 },
979 "node_modules/text-decoder": {
980 "version": "1.2.7",
981 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
982 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
983 "license": "Apache-2.0",
984 "dependencies": {
985 "b4a": "^1.6.4"
986 }
987 },
988 "node_modules/tmatch": {
989 "version": "5.0.0",
990 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
991 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
992 "dev": true,
993 "license": "ISC",
994 "engines": {
995 "node": ">=8"
996 }
997 },
998 "node_modules/v8-to-istanbul": {
999 "version": "9.3.0",
1000 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
1001 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
1002 "dev": true,
1003 "license": "ISC",
1004 "dependencies": {
1005 "@jridgewell/trace-mapping": "^0.3.12",
1006 "@types/istanbul-lib-coverage": "^2.0.1",
1007 "convert-source-map": "^2.0.0"
1008 },
1009 "engines": {
1010 "node": ">=10.12.0"
1011 }
1012 },
1013 "node_modules/which-runtime": {
1014 "version": "1.4.0",
1015 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
1016 "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==",
1017 "dev": true,
1018 "license": "Apache-2.0"
1019 }
1020 }
1021}
package.json +51 -0
……@@ -0,0 +1,51 @@
1{
2 "name": "host-services",
3 "version": "0.1.0",
4 "description": "One page listing every service a host runs (pgh apps, systemd units, listening ports) with active / dormant state, plus the LAN's other devices and what they serve.",
5 "type": "module",
6 "main": "index.js",
7 "bin": {
8 "host-services": "index.js"
9 },
10 "scripts": {
11 "test": "brittle-bare test/*.test.js"
12 },
13 "dependencies": {
14 "bare-events": "2.9.2",
15 "bare-fs": "4.8.2",
16 "bare-http1": "4.6.2",
17 "bare-net": "2.3.3",
18 "bare-os": "3.9.3",
19 "bare-path": "3.1.2",
20 "bare-process": "4.5.1",
21 "bare-subprocess": "6.1.0"
22 },
23 "devDependencies": {
24 "brittle": "4.1.1"
25 },
26 "pgh": {
27 "bin": "host-services",
28 "entrypoint": "index.js",
29 "type": "daemon",
30 "runtime": "bare",
31 "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/host-services",
32 "autostart": false,
33 "depends": [
34 "pgh-sidecar"
35 ],
36 "defaults": {
37 "env": {
38 "HOST_SERVICES_PORT": "4060",
39 "HOST_SERVICES_HOST": "0.0.0.0"
40 }
41 },
42 "service": {
43 "requiresNetwork": true
44 }
45 },
46 "overrides": {
47 "bare-type": "1.1.1",
48 "bare-structured-clone": "1.6.0",
49 "bare-assert": "1.2.0"
50 }
51}
static/app.css +87 -0
……@@ -0,0 +1,87 @@
1:root {
2 --bg: #f6f7f9;
3 --card: #ffffff;
4 --text: #1c2129;
5 --muted: #667085;
6 --line: #e3e6eb;
7 --accent: #2f6fde;
8 --active: #1f9d55;
9 --degraded: #d97706;
10 --failed: #d6453d;
11 --dormant: #8a94a6;
12 --chip: #eef1f5;
13}
14@media (prefers-color-scheme: dark) {
15 :root {
16 --bg: #12151a;
17 --card: #1b2028;
18 --text: #e6e9ee;
19 --muted: #8d97a8;
20 --line: #2a313c;
21 --accent: #6c9cf0;
22 --chip: #232a34;
23 }
24}
25* { box-sizing: border-box; }
26body {
27 margin: 0;
28 background: var(--bg);
29 color: var(--text);
30 font: 14px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
31 padding: 16px;
32 max-width: 1200px;
33 margin-inline: auto;
34}
35header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: end; gap: 12px; margin-bottom: 12px; }
36h1 { margin: 0; font-size: 22px; }
37.sub { color: var(--muted); font-size: 13px; }
38.counts { display: flex; gap: 8px; flex-wrap: wrap; }
39.count { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; display: flex; gap: 6px; align-items: center; }
40.count b { font-size: 16px; }
41.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none; }
42.dot.active { background: var(--active); }
43.dot.degraded { background: var(--degraded); }
44.dot.failed { background: var(--failed); }
45.dot.dormant { background: var(--dormant); }
46.errors { background: color-mix(in srgb, var(--failed) 12%, var(--card)); border: 1px solid var(--failed); border-radius: 8px; padding: 8px 12px; margin-bottom: 12px; }
47.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 12px; }
48.tabs button { background: none; border: 0; border-bottom: 2px solid transparent; padding: 8px 12px; color: var(--muted); font: inherit; cursor: pointer; }
49.tabs button.on { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
50.toolbar { display: flex; flex-wrap: wrap; gap: 8px; justify-content: space-between; align-items: center; margin-bottom: 10px; }
51.chips { display: flex; gap: 6px; flex-wrap: wrap; }
52.chips button, button.primary {
53 border: 1px solid var(--line); background: var(--card); color: var(--text);
54 border-radius: 999px; padding: 5px 12px; font: inherit; cursor: pointer;
55}
56.chips button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
57button.primary { background: var(--accent); border-color: var(--accent); color: #fff; border-radius: 8px; }
58button.primary:disabled { opacity: .6; cursor: default; }
59input[type=search] { flex: 1 1 220px; max-width: 360px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--text); font: inherit; }
60.list { display: grid; gap: 8px; }
61.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; display: grid; gap: 4px; }
62.card.dormant { opacity: .72; }
63.row1 { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
64.name { font-weight: 600; font-size: 15px; }
65.badge { font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--chip); color: var(--muted); }
66.state { font-size: 12px; font-weight: 600; display: inline-flex; gap: 5px; align-items: center; }
67.state.active { color: var(--active); }
68.state.degraded { color: var(--degraded); }
69.state.failed { color: var(--failed); }
70.state.dormant { color: var(--dormant); }
71.meta { color: var(--muted); font-size: 12.5px; display: flex; flex-wrap: wrap; gap: 4px 14px; }
72.ports { display: flex; flex-wrap: wrap; gap: 6px; }
73.port { border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; font-size: 12.5px; display: inline-flex; gap: 6px; align-items: center; max-width: 100%; }
74.port a { color: var(--accent); text-decoration: none; font-weight: 600; }
75.port a:hover { text-decoration: underline; }
76.port .t { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
77.port.closed { border-style: dashed; color: var(--muted); }
78.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
79.system { margin-top: 16px; }
80.system summary { cursor: pointer; color: var(--muted); }
81.syslist { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 2px 16px; margin-top: 8px; font-size: 12.5px; }
82.syslist span { display: flex; gap: 6px; align-items: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
83.empty { color: var(--muted); padding: 16px; text-align: center; }
84@media (max-width: 600px) {
85 body { padding: 12px; }
86 .port .t { max-width: 160px; }
87}
static/app.js +195 -0
……@@ -0,0 +1,195 @@
1// host-services page: polls /api/host and /api/lan and renders them.
2
3const $ = (id) => document.getElementById(id)
4const STATES = ['active', 'degraded', 'failed', 'dormant']
5const STATE_LABEL = { active: 'Active', degraded: 'Degraded', failed: 'Failed', dormant: 'Dormant' }
6const KIND_LABEL = { pgh: 'pgh app', unit: 'systemd', 'user-unit': 'user unit', process: 'process', port: 'port' }
7const ORDER = { failed: 0, degraded: 1, active: 2, dormant: 3 }
8
9let host = null
10let lan = null
11let filter = 'all'
12let query = ''
13
14function esc (s) {
15 return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c])
16}
17
18function ago (iso) {
19 if (!iso) return ''
20 const t = Date.parse(iso)
21 if (isNaN(t)) return iso
22 const s = Math.round((Date.now() - t) / 1000)
23 if (s < 60) return s + 's ago'
24 if (s < 3600) return Math.round(s / 60) + 'm ago'
25 if (s < 86400) return Math.round(s / 3600) + 'h ago'
26 return Math.round(s / 86400) + 'd ago'
27}
28
29// Where a browser on the LAN reaches a port of host `h`.
30function portUrl (h, p) {
31 const tls = !p.http && (p.port === 443 || p.port === 8443 || p.port === 8920)
32 return (tls ? 'https://' : 'http://') + h + (p.port === (tls ? 443 : 80) ? '' : ':' + p.port) + '/'
33}
34
35function portChip (p, hostname) {
36 const label = p.label && p.label.name
37 const title = p.http && p.http.title
38 const what = [title, label && label !== title ? label : null].filter(Boolean).join(' · ')
39 const status = p.http ? p.http.code : (p.banner ? 'tcp' : '')
40 const reachable = p.scope !== 'local'
41 const head = reachable && (p.http || p.open)
42 ? '<a href="' + esc(portUrl(hostname, p)) + '" target="_blank" rel="noopener">:' + p.port + '</a>'
43 : '<span class="mono">:' + p.port + '</span>'
44 const notes = [
45 p.scope === 'local' ? 'localhost only' : null,
46 p.scope === 'addr' ? p.addr : null,
47 p.open === false ? 'not answering' : null,
48 status ? String(status) : null
49 ].filter(Boolean).join(' · ')
50 return '<span class="port' + (p.open === false ? ' closed' : '') + '" title="' + esc([what, notes, p.banner].filter(Boolean).join('\n')) + '">' +
51 head + (what ? '<span class="t">' + esc(what) + '</span>' : '') +
52 (notes ? '<span class="t">' + esc(notes) + '</span>' : '') + '</span>'
53}
54
55function displayName (s) {
56 return (s.label && s.label.name) || s.name
57}
58
59function matches (s) {
60 if (filter === 'active' && s.state !== 'active') return false
61 if (filter === 'dormant' && s.state !== 'dormant') return false
62 if (filter === 'problems' && s.state !== 'degraded' && s.state !== 'failed') return false
63 if (!query) return true
64 const hay = [displayName(s), s.name, s.description, s.user, s.kind, (s.label && s.label.description),
65 ...s.ports.map((p) => p.port + ' ' + ((p.http && p.http.title) || '') + ' ' + ((p.label && p.label.name) || ''))].join(' ').toLowerCase()
66 return hay.includes(query)
67}
68
69function renderHost () {
70 if (!host) return
71 $('host').textContent = host.host
72 document.title = host.host + ' · services'
73 $('updated').textContent = 'updated ' + ago(host.at)
74 const n = Object.fromEntries(STATES.map((k) => [k, 0]))
75 for (const s of host.services) n[s.state]++
76 $('counts').innerHTML = STATES.map((k) => '<span class="count"><span class="dot ' + k + '"></span><b>' + n[k] + '</b> ' + STATE_LABEL[k] + '</span>').join('')
77
78 const errs = host.errors || []
79 $('errors').hidden = errs.length === 0
80 $('errors').innerHTML = errs.map(esc).join('<br>')
81
82 const hostname = location.hostname
83 const list = host.services.filter(matches).sort((a, b) =>
84 (ORDER[a.state] - ORDER[b.state]) || displayName(a).localeCompare(displayName(b)))
85 $('services').innerHTML = list.length
86 ? list.map((s) => {
87 const meta = [
88 s.description || (s.label && s.label.description),
89 s.version ? 'v' + s.version : null,
90 s.kind === 'pgh' ? 'autostart ' + (s.autostart ? 'on' : 'off') : null,
91 s.enabled && s.kind !== 'pgh' ? s.enabled : null,
92 s.user ? 'user ' + s.user : null,
93 s.owner && s.owner.user ? 'user ' + s.owner.user : null,
94 s.owner && s.owner.cmd ? s.owner.cmd : null,
95 s.since ? (s.running ? 'up since ' : 'stopped ') + ago(s.since) : null,
96 s.kind === 'pgh' && !s.running && s.lastExitCode != null ? 'last exit ' + s.lastExitCode : null,
97 s.activeState && s.state !== 'active' && s.state !== 'dormant' ? s.activeState + '/' + s.subState : null
98 ].filter(Boolean)
99 return '<div class="card ' + s.state + '">' +
100 '<div class="row1"><span class="state ' + s.state + '"><span class="dot ' + s.state + '"></span>' + STATE_LABEL[s.state] + '</span>' +
101 '<span class="name">' + esc(displayName(s)) + '</span>' +
102 (displayName(s) !== s.name ? '<span class="mono sub">' + esc(s.name) + '</span>' : '') +
103 '<span class="badge">' + esc(KIND_LABEL[s.kind] || s.kind) + '</span></div>' +
104 (meta.length ? '<div class="meta">' + meta.map((m) => '<span>' + esc(m) + '</span>').join('') + '</div>' : '') +
105 (s.ports.length ? '<div class="ports">' + s.ports.map((p) => portChip(p, hostname)).join('') + '</div>' : '') +
106 '</div>'
107 }).join('')
108 : '<div class="empty">Nothing matches.</div>'
109
110 const sys = host.system || []
111 $('system-sum').textContent = 'Operating-system units (' + sys.length + ')'
112 $('system').innerHTML = sys.sort((a, b) => a.name.localeCompare(b.name)).map((u) => {
113 const st = u.state === 'active' ? 'active' : u.state === 'failed' ? 'failed' : 'dormant'
114 return '<span title="' + esc(u.state + '/' + u.sub + ' · ' + u.enabled) + '"><span class="dot ' + st + '"></span>' + esc(u.name) + '</span>'
115 }).join('')
116}
117
118function renderLan () {
119 if (!lan) return
120 const r = lan.result
121 $('scan').disabled = lan.scanning
122 $('scan').textContent = lan.scanning ? 'Scanning…' : 'Scan now'
123 $('lan-meta').textContent = lan.scanning
124 ? 'Scanning: ' + lan.progress
125 : r ? r.hosts.length + ' devices on ' + r.subnets.map((s) => s + '.0/24').join(', ') + ' · ' + r.ports + ' ports checked · scanned ' + ago(r.at) + ' in ' + Math.round(r.durationMs / 1000) + 's'
126 : 'No scan yet.'
127 if (!r) {
128 $('lan').innerHTML = '<div class="empty">' + (lan.scanning ? 'First scan running…' : 'No scan yet.') + '</div>'
129 return
130 }
131 $('lan').innerHTML = r.hosts.map((h) => {
132 const name = h.names[0] || h.ip
133 const linkHost = h.names[0] || h.ip
134 const ports = h.ports.map((p) => portChip({ ...p, scope: 'all', open: true }, linkHost)).join('')
135 const mdns = h.mdns.map((m) => m.name + ' (' + m.type.replace(/^_|\._tcp$|\._udp$/g, '') + (m.port ? ':' + m.port : '') + ')')
136 return '<div class="card">' +
137 '<div class="row1"><span class="name">' + esc(name) + '</span>' +
138 (name !== h.ip ? '<span class="mono sub">' + esc(h.ip) + '</span>' : '') +
139 (h.self ? '<span class="badge">this host</span>' : '') +
140 (h.ports.length ? '' : '<span class="badge">no open ports found</span>') + '</div>' +
141 '<div class="meta">' + [h.mac ? '<span class="mono">' + esc(h.mac) + '</span>' : null, ...h.names.slice(1).map(esc)].filter(Boolean).map((m) => '<span>' + m + '</span>').join('') + '</div>' +
142 (ports ? '<div class="ports">' + ports + '</div>' : '') +
143 (mdns.length ? '<div class="meta"><span>mDNS: ' + esc(mdns.join(', ')) + '</span></div>' : '') +
144 '</div>'
145 }).join('')
146}
147
148async function loadHost () {
149 try {
150 const r = await fetch('/api/host')
151 host = await r.json()
152 renderHost()
153 } catch (err) {
154 $('errors').hidden = false
155 $('errors').textContent = 'Could not load /api/host: ' + err.message
156 }
157}
158
159async function loadLan () {
160 try {
161 lan = await (await fetch('/api/lan')).json()
162 renderLan()
163 } catch {}
164 setTimeout(loadLan, lan && lan.scanning ? 3000 : 30000)
165}
166
167$('filters').addEventListener('click', (e) => {
168 const f = e.target.dataset.f
169 if (!f) return
170 filter = f
171 for (const b of $('filters').children) b.classList.toggle('on', b.dataset.f === f)
172 renderHost()
173})
174$('q').addEventListener('input', (e) => {
175 query = e.target.value.trim().toLowerCase()
176 renderHost()
177})
178document.querySelector('.tabs').addEventListener('click', (e) => {
179 const t = e.target.dataset.tab
180 if (!t) return
181 for (const b of document.querySelectorAll('.tabs button')) b.classList.toggle('on', b.dataset.tab === t)
182 $('tab-services').hidden = t !== 'services'
183 $('tab-lan').hidden = t !== 'lan'
184})
185$('scan').addEventListener('click', async () => {
186 await fetch('/api/lan/scan', { method: 'POST', headers: { 'x-host-services': '1' } })
187 lan = { ...(lan || {}), scanning: true, progress: 'starting' }
188 renderLan()
189 setTimeout(loadLan, 1500)
190})
191
192loadHost()
193setInterval(loadHost, 20000)
194setInterval(renderHost, 10000)
195loadLan()
static/index.html +52 -0
……@@ -0,0 +1,52 @@
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Host services</title>
7 <link rel="stylesheet" href="/app.css">
8</head>
9<body>
10 <header>
11 <div class="title">
12 <h1 id="host">…</h1>
13 <span class="sub" id="updated"></span>
14 </div>
15 <div class="counts" id="counts"></div>
16 </header>
17
18 <div id="errors" class="errors" hidden></div>
19
20 <nav class="tabs">
21 <button data-tab="services" class="on">Services</button>
22 <button data-tab="lan">Network</button>
23 </nav>
24
25 <section id="tab-services">
26 <div class="toolbar">
27 <div class="chips" id="filters">
28 <button data-f="all" class="on">All</button>
29 <button data-f="active">Active</button>
30 <button data-f="problems">Degraded / failed</button>
31 <button data-f="dormant">Dormant</button>
32 </div>
33 <input id="q" type="search" placeholder="Filter by name, port, user…">
34 </div>
35 <div id="services" class="list"></div>
36 <details class="system">
37 <summary id="system-sum">Operating-system units</summary>
38 <div id="system" class="syslist"></div>
39 </details>
40 </section>
41
42 <section id="tab-lan" hidden>
43 <div class="toolbar">
44 <span id="lan-meta" class="sub"></span>
45 <button id="scan" class="primary">Scan now</button>
46 </div>
47 <div id="lan" class="list"></div>
48 </section>
49
50 <script src="/app.js"></script>
51</body>
52</html>
test/collect.test.js +127 -0
……@@ -0,0 +1,127 @@
1// buildServices / stateOf: attribution and states, without a host.
2import test from 'brittle'
3import { buildServices, stateOf, readListeners, pghAppOf } from '../lib/collect.js'
4import fs from 'bare-fs'
5import os from 'bare-os'
6import path from 'bare-path'
7
8const unit = (Id, extra = {}) => ({
9 Id, Description: Id, LoadState: 'loaded', ActiveState: 'active', SubState: 'running',
10 UnitFileState: 'enabled', FragmentPath: '/usr/lib/systemd/system/' + Id, User: '', ...extra
11})
12
13function build (over = {}) {
14 return buildServices({
15 listeners: [],
16 owners: new Map(),
17 pidInfo: new Map(),
18 users: new Map([[1000, 'fleeky'], [122, 'jellyfin']]),
19 units: [],
20 userUnits: [],
21 pghApps: [],
22 probes: new Map(),
23 ...over
24 })
25}
26
27test('a port owned by our own pgh app attaches to that app', (t) => {
28 const { services } = build({
29 listeners: [{ port: 4050, addr: '0.0.0.0', scope: 'all', uid: 1000, inodes: [11] }],
30 owners: new Map([[11, 500]]),
31 pidInfo: new Map([[500, { pid: 500, cmd: 'bare x', pghApp: 'cthuloid-web', unit: null, userUnit: false }]]),
32 pghApps: [{ name: 'cthuloid-web', running: true, version: '0.2.1', autostart: true, lastExitCode: 0 }],
33 probes: new Map([['4050/0.0.0.0', { open: true, http: { code: 200, title: 'Cthuloid Lights' }, banner: null }]])
34 })
35 t.is(services.length, 1)
36 t.is(services[0].id, 'pgh:cthuloid-web')
37 t.is(services[0].ports[0].http.title, 'Cthuloid Lights')
38 t.is(services[0].state, 'active')
39})
40
41test('another user\'s socket attaches to the running unit with that User=', (t) => {
42 const { services } = build({
43 listeners: [{ port: 8096, addr: '0.0.0.0', scope: 'all', uid: 122, inodes: [9] }],
44 units: [unit('jellyfin.service', { User: 'jellyfin' })],
45 probes: new Map([['8096/0.0.0.0', { open: true, http: { code: 302 }, banner: null }]])
46 })
47 const j = services.find((s) => s.id === 'unit:jellyfin.service')
48 t.ok(j)
49 t.is(j.ports[0].port, 8096)
50 t.is(j.state, 'active')
51})
52
53test('a socket nobody can be tied to is its own "port" service', (t) => {
54 const { services } = build({
55 listeners: [{ port: 80, addr: '::', scope: 'all', uid: 0, inodes: [1] }],
56 probes: new Map([['80/::', { open: true, http: { code: 308 }, banner: null }]])
57 })
58 t.is(services[0].kind, 'port')
59 t.is(services[0].ports[0].owner.user, '0')
60})
61
62test('vendor units without ports go to the system list; hand-made ones stay', (t) => {
63 const { services, system } = build({
64 units: [unit('cron.service'), unit('easynews-indexer.service', { FragmentPath: '/etc/systemd/system/easynews-indexer.service' })],
65 userUnits: [unit('kodi-repo.service', { FragmentPath: '/home/u/.config/systemd/user/kodi-repo.service' })]
66 })
67 t.alike(services.map((s) => s.id).sort(), ['unit:easynews-indexer.service', 'user:kodi-repo.service'])
68 t.alike(system.map((s) => s.name), ['cron.service'])
69})
70
71test('a failed vendor unit is a service, not hidden in the system list', (t) => {
72 const { services } = build({ units: [unit('xdg-desktop-portal.service', { ActiveState: 'failed', SubState: 'failed' })] })
73 t.is(services[0].state, 'failed')
74})
75
76test('pghAppOf: the app is the script, not pgh\'s runtime', (t) => {
77 t.is(pghAppOf(['/h/.local/share/store/apps/pgh/runtime/bare', '/h/.local/share/store/apps/favela-web/index.js']), 'favela-web')
78 t.is(pghAppOf(['/h/.local/share/store/apps/pgh/runtime/bare', '/h/.local/share/store/apps/pgh/lib/supervisor.js']), 'pgh')
79 t.is(pghAppOf(['/usr/bin/python3', '-m', 'http.server']), null)
80})
81
82test('a plain process\'s ports are one service named after it', (t) => {
83 const { services } = build({
84 listeners: [17001, 18080].map((port, i) => ({ port, addr: '::', scope: 'all', uid: 1000, inodes: [i + 1] })),
85 owners: new Map([[1, 7684], [2, 7684]]),
86 pidInfo: new Map([[7684, { pid: 7684, cmd: 'cogserver', exe: 'cogserver', pghApp: null, unit: null, userUnit: false }]])
87 })
88 t.is(services.length, 1)
89 t.is(services[0].kind, 'process')
90 t.is(services[0].name, 'cogserver')
91 t.is(services[0].ports.length, 2)
92})
93
94test('a root socket goes to the unit labels.json names for its port', (t) => {
95 const { services } = build({
96 listeners: [{ port: 22, addr: '0.0.0.0', scope: 'all', uid: 0, inodes: [1] }],
97 units: [unit('ssh.service')],
98 labels: { ports: { 22: { name: 'SSH', unit: 'ssh.service' } } }
99 })
100 t.is(services.length, 1)
101 t.is(services[0].id, 'unit:ssh.service')
102 t.is(services[0].ports[0].port, 22)
103})
104
105test('states', (t) => {
106 t.is(stateOf({ kind: 'pgh', running: false, lastExitCode: 0, ports: [] }), 'dormant')
107 t.is(stateOf({ kind: 'pgh', running: false, lastExitCode: 75, ports: [] }), 'dormant', '75 = OTA restart')
108 t.is(stateOf({ kind: 'pgh', running: false, lastExitCode: 1, ports: [] }), 'failed')
109 t.is(stateOf({ kind: 'pgh', running: true, ports: [{ open: false }] }), 'degraded')
110 t.is(stateOf({ kind: 'unit', running: false, activeState: 'inactive', ports: [] }), 'dormant')
111 t.is(stateOf({ kind: 'unit', running: true, activeState: 'active', ports: [{ open: true }, { open: false }] }), 'active')
112})
113
114test('readListeners: v4+v6 on the same port and scope are one entry; loopback twin folds into "all"', (t) => {
115 const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hs-'))
116 fs.mkdirSync(path.join(dir, 'net'))
117 const head = ' sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n'
118 fs.writeFileSync(path.join(dir, 'net/tcp'), head +
119 ' 0: 00000000:1FA0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 122 0 101 1\n' +
120 ' 1: 0100007F:1FA0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 122 0 102 1\n')
121 fs.writeFileSync(path.join(dir, 'net/tcp6'), head +
122 ' 0: 00000000000000000000000000000000:1FA0 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 122 0 103 1\n')
123 const ls = readListeners(dir)
124 t.is(ls.length, 1, '0.0.0.0, :: and 127.0.0.1 on 8096 are one service')
125 t.is(ls[0].scope, 'all')
126 t.alike(ls[0].inodes.sort(), [101, 102, 103])
127})
test/fixtures/avahi-browse.txt +70 -0
……@@ -0,0 +1,70 @@
1+;enp2s0;IPv6;I0U4R078n14AAA;_FC9F5ED42C8A._tcp;local
2+;enp2s0;IPv4;I0U4R078n14AAA;_FC9F5ED42C8A._tcp;local
3+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_privet._tcp;local
4=;enp2s0;IPv6;I0U4R078n14AAA;_FC9F5ED42C8A._tcp;local;Android_RI46Y41R.local;192.168.50.148;53601;"IPv4=192.168.50.148" "n=MsKh27XLSlAUGc4Xs8qzVYU" "f=5180"
5+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_scanner._tcp;local
6=;enp2s0;IPv4;I0U4R078n14AAA;_FC9F5ED42C8A._tcp;local;Android_RI46Y41R.local;192.168.50.148;53601;"IPv4=192.168.50.148" "n=MsKh27XLSlAUGc4Xs8qzVYU" "f=5180"
7+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_ipp._tcp;local
8+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_printer._tcp;local
9+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_pdl-datastream._tcp;local
10+;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_http._tcp;local
11+;enp2s0;IPv4;BC102F980CE7;_spotify-connect._tcp;local
12=;enp2s0;IPv4;BC102F980CE7;_spotify-connect._tcp;local;BC102F980CE7.local;192.168.50.111;17654;"Stack=SP" "VERSION=1.0" "CPath=/zc/0"
13+;enp2s0;IPv6;SABnzbd\032on\032stortron\0588085;_http._tcp;local
14+;enp2s0;IPv4;SABnzbd\032on\032stortron\0588085;_http._tcp;local
15+;lo;IPv4;SABnzbd\032on\032stortron\0588085;_http._tcp;local
16=;enp2s0;IPv6;SABnzbd\032on\032stortron\0588085;_http._tcp;local;stortron.local;fe80::fb64:bd7f:42fd:ff8c;8085;"https=0" "path="
17=;enp2s0;IPv4;SABnzbd\032on\032stortron\0588085;_http._tcp;local;stortron.local;192.168.50.215;8085;"https=0" "path="
18=;lo;IPv4;SABnzbd\032on\032stortron\0588085;_http._tcp;local;stortron.local;127.0.0.1;8085;"https=0" "path="
19+;enp2s0;IPv4;Kodi\032\040lelectron\041;_http._tcp;local
20=;enp2s0;IPv4;Kodi\032\040lelectron\041;_http._tcp;local;lelectron.local;192.168.50.192;8080;"uuid=401ef5d4-9f34-4606-85cf-c476d3b99393" "txtvers=1"
21+;enp2s0;IPv4;DCA63223F32C\064Kodi\032\040lelectron\041;_raop._tcp;local
22+;enp2s0;IPv4;LELECTRON;_smb._tcp;local
23+;enp2s0;IPv4;lelectron;_sftp-ssh._tcp;local
24+;enp2s0;IPv4;studiolight;_cthuloid._tcp;local
25=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_pdl-datastream._tcp;local;BRW28565A39CC8D.local;192.168.50.232;9100;"UUID=e3248000-80ce-11db-8000-30055cf6a7c8" "TBCP=T" "Transparent=F" "Binary=T" "PaperCustom=T" "Scan=T" "Fax=T" "Duplex=T" "Copies=T" "Color=F" "usb_CMD=PJL,HBP,URF" "usb_MDL=MFC-L2700DW series" "usb_MFG=Brother" "priority=75" "adminurl=http://BRW28565A39CC8D.local./" "product=(Brother MFC-L2700DW series)" "ty=Brother MFC-L2700DW series" "note=" "pdl=application/octet-stream,image/urf,image/pwg-raster" "qtotal=1" "txtvers=1"
26=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_printer._tcp;local;BRW28565A39CC8D.local;192.168.50.232;515;"UUID=e3248000-80ce-11db-8000-30055cf6a7c8" "TBCP=F" "Transparent=T" "Binary=T" "PaperCustom=T" "Scan=T" "Fax=T" "Duplex=T" "Copies=T" "Color=F" "usb_CMD=PJL,HBP,URF" "usb_MDL=MFC-L2700DW series" "usb_MFG=Brother" "priority=50" "adminurl=http://BRW28565A39CC8D.local./" "product=(Brother MFC-L2700DW series)" "ty=Brother MFC-L2700DW series" "note=" "rp=duerqxesz5090" "pdl=application/octet-stream,image/urf,image/pwg-raster" "qtotal=1" "txtvers=1"
27=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_ipp._tcp;local;BRW28565A39CC8D.local;192.168.50.232;631;"print_wfds=T" "UUID=e3248000-80ce-11db-8000-30055cf6a7c8" "PaperMax=legal-A4" "kind=document,envelope,label" "URF=W8,CP1,IS4-1,MT1-3-4-5-8,OB10,PQ4,RS200-300-600,V1.3,DM1" "rfo=ipp/faxout" "TBCP=F" "Transparent=T" "Binary=T" "PaperCustom=T" "Scan=T" "Fax=T" "Duplex=T" "Copies=T" "Color=F" "usb_CMD=PJL,HBP,URF" "usb_MDL=MFC-L2700DW series" "usb_MFG=Brother" "priority=25" "adminurl=http://BRW28565A39CC8D.local./net/net/airprint.html" "product=(Brother MFC-L2700DW series)" "ty=Brother MFC-L2700DW series" "note=" "rp=ipp/print" "pdl=application/octet-stream,image/urf,image/pwg-raster" "qtotal=1" "txtvers=1"
28=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_scanner._tcp;local;BRW28565A39CC8D.local;192.168.50.232;54921;"flatbed=T" "feeder=T" "button=T" "mdl=MFC-L2700DW series" "mfg=Brother" "ty=Brother MFC-L2700DW series" "adminurl=http://BRW28565A39CC8D.local./" "note=" "txtvers=1"
29=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_http._tcp;local;BRW28565A39CC8D.local;192.168.50.232;80;
30=;enp2s0;IPv4;Brother\032MFC-L2700DW\032series;_privet._tcp;local;BRW28565A39CC8D.local;192.168.50.232;80;"cs=offline" "id=" "type=printer" "url=https://www.google.com/cloudprint" "note=Brother MFC-L2700DW series [30055cf6a7c8]" "ty=Brother MFC-L2700DW series [28565A39CC8D]" "txtvers=1"
31+;enp2s0;IPv4;SpZc-DB67EF;_spotify-connect._tcp;local
32=;enp2s0;IPv4;SpZc-DB67EF;_spotify-connect._tcp;local;PS5-DB67EF.local;192.168.50.211;41800;"CPath=/spConn"
33+;enp2s0;IPv6;Jason\226\128\153s\032MacBook\032Pro;_airplay._tcp;local
34+;enp2s0;IPv4;Jason\226\128\153s\032MacBook\032Pro;_airplay._tcp;local
35+;enp2s0;IPv6;Jason\226\128\153s\032MacBook\032Pro;_companion-link._tcp;local
36+;enp2s0;IPv4;Jason\226\128\153s\032MacBook\032Pro;_companion-link._tcp;local
37+;enp2s0;IPv4;studiolight;_telnet._tcp;local
38+;enp2s0;IPv4;studiolight;_arduino._tcp;local
39=;enp2s0;IPv4;studiolight;_cthuloid._tcp;local;studiolight.local;192.168.50.89;1337;"proto=3"
40=;enp2s0;IPv4;studiolight;_telnet._tcp;local;studiolight.local;192.168.50.89;23;
41=;enp2s0;IPv4;studiolight;_arduino._tcp;local;studiolight.local;192.168.50.89;3232;"board=esp32" "tcp_check=no" "ssh_upload=no" "auth_upload=yes"
42+;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-jsonrpc-h._tcp;local
43+;enp2s0;IPv4;lelectron;_ssh._tcp;local
44+;enp2s0;IPv4;root\064lelectron;_pulse-server._tcp;local
45+;enp2s0;IPv4;root\064lelectron\058\032Dummy\032Output;_pulse-sink._tcp;local
46+;enp2s0;IPv4;LELECTRON;_device-info._tcp;local
47+;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-events._udp;local
48+;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-jsonrpc._tcp;local
49=;enp2s0;IPv4;lelectron;_sftp-ssh._tcp;local;lelectron.local;192.168.50.192;22;"u=root" "path=/storage"
50=;enp2s0;IPv4;LELECTRON;_smb._tcp;local;lelectron.local;192.168.50.192;445;
51=;enp2s0;IPv4;DCA63223F32C\064Kodi\032\040lelectron\041;_raop._tcp;local;lelectron.local;192.168.50.192;36666;"vs=130.14" "am=Kodi,1" "md=0,1,2" "da=true" "vn=3" "pw=false" "sr=44100" "ss=16" "sm=false" "tp=UDP" "sv=false" "et=0,1" "ek=1" "ch=2" "cn=0,1" "txtvers=1"
52=;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-jsonrpc-h._tcp;local;lelectron.local;192.168.50.192;8080;"uuid=401ef5d4-9f34-4606-85cf-c476d3b99393" "txtvers=1"
53=;enp2s0;IPv4;lelectron;_ssh._tcp;local;lelectron.local;192.168.50.192;22;
54=;enp2s0;IPv4;root\064lelectron;_pulse-server._tcp;local;lelectron.local;192.168.50.192;4713;"cookie=0x738cb2a9" "fqdn=lelectron" "uname=Linux aarch64 6.12.56 #1 SMP Sat Nov 1 08:49:19 UTC 2025" "machine-id=8d987161ce07b8c5b70220cd685d085d" "user-name=root" "server-version=pulseaudio 17.0"
55=;enp2s0;IPv4;root\064lelectron\058\032Dummy\032Output;_pulse-sink._tcp;local;lelectron.local;192.168.50.192;4713;"icon-name=computer" "class=abstract" "description=Dummy Output" "subtype=virtual" "channel_map=front-left,front-right" "format=s16le" "channels=2" "rate=44100" "device=auto_null" "cookie=0x738cb2a9" "fqdn=lelectron" "uname=Linux aarch64 6.12.56 #1 SMP Sat Nov 1 08:49:19 UTC 2025" "machine-id=8d987161ce07b8c5b70220cd685d085d" "user-name=root" "server-version=pulseaudio 17.0"
56=;enp2s0;IPv4;LELECTRON;_device-info._tcp;local;lelectron.local;192.168.50.192;0;"model=Xserve"
57=;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-events._udp;local;lelectron.local;192.168.50.192;9777;
58=;enp2s0;IPv4;Kodi\032\040lelectron\041;_xbmc-jsonrpc._tcp;local;lelectron.local;192.168.50.192;9090;"uuid=401ef5d4-9f34-4606-85cf-c476d3b99393" "txtvers=1"
59+;enp2s0;IPv4;6A164B0C4947\064Jason\226\128\153s\032MacBook\032Pro;_raop._tcp;local
60=;enp2s0;IPv4;Jason\226\128\153s\032MacBook\032Pro;_airplay._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;7000;"srcvers=960.13.1" "pk=521aea2e165bb6383bbb0240515b1778c96e62325c9d21beade3b0d44f21cc2c" "psi=68164B0C-4947-4659-9E58-78F7523E30F9" "pi=81520dc4-6388-4832-a587-9ed8dc2f6a3b" "protovers=1.1" "at=4" "model=MacBookPro16,2" "gcgl=0" "igl=0" "gid=85145821-17D5-436D-A50E-32F01136CDB6" "flags=0x204" "rsf=0x8" "features=0x4A7FCFD5,0x38174FDE" "fex=1c9/St5PFzg2IQxA" "deviceid=6A:16:4B:0C:49:47" "acl=0" "act=2"
61=;enp2s0;IPv4;Jason\226\128\153s\032MacBook\032Pro;_companion-link._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;61624;"rpVr=715.2" "rpMac=0" "rpHN=cac65d3cb6e1" "rpFl=0x20000" "rpAD=3fef83b4fe7d" "rpBA=95:96:4C:06:36:06"
62+;enp2s0;IPv6;6A164B0C4947\064Jason\226\128\153s\032MacBook\032Pro;_raop._tcp;local
63=;enp2s0;IPv6;Jason\226\128\153s\032MacBook\032Pro;_airplay._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;7000;"srcvers=960.13.1" "pk=521aea2e165bb6383bbb0240515b1778c96e62325c9d21beade3b0d44f21cc2c" "psi=68164B0C-4947-4659-9E58-78F7523E30F9" "pi=81520dc4-6388-4832-a587-9ed8dc2f6a3b" "protovers=1.1" "at=4" "model=MacBookPro16,2" "gcgl=0" "igl=0" "gid=85145821-17D5-436D-A50E-32F01136CDB6" "flags=0x204" "rsf=0x8" "features=0x4A7FCFD5,0x38174FDE" "fex=1c9/St5PFzg2IQxA" "deviceid=6A:16:4B:0C:49:47" "acl=0" "act=2"
64=;enp2s0;IPv6;Jason\226\128\153s\032MacBook\032Pro;_companion-link._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;61624;"rpVr=715.2" "rpMac=0" "rpHN=cac65d3cb6e1" "rpFl=0x20000" "rpAD=3fef83b4fe7d" "rpBA=95:96:4C:06:36:06"
65=;enp2s0;IPv4;6A164B0C4947\064Jason\226\128\153s\032MacBook\032Pro;_raop._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;7000;"vv=0" "vs=960.13.1" "vn=65537" "tp=UDP" "pk=521aea2e165bb6383bbb0240515b1778c96e62325c9d21beade3b0d44f21cc2c" "am=MacBookPro16,2" "md=0,1,2" "sf=0x204" "ft=0x4A7FCFD5,0x38174FDE" "et=0,3,5" "da=true" "cn=0,1,2,3"
66=;enp2s0;IPv6;6A164B0C4947\064Jason\226\128\153s\032MacBook\032Pro;_raop._tcp;local;Jasons-MacBook-Pro.local;192.168.50.52;7000;"vv=0" "vs=960.13.1" "vn=65537" "tp=UDP" "pk=521aea2e165bb6383bbb0240515b1778c96e62325c9d21beade3b0d44f21cc2c" "am=MacBookPro16,2" "md=0,1,2" "sf=0x204" "ft=0x4A7FCFD5,0x38174FDE" "et=0,3,5" "da=true" "cn=0,1,2,3"
67+;enp2s0;IPv4;seedlings;_http._tcp;local
68=;enp2s0;IPv4;seedlings;_http._tcp;local;seedlings.local;192.168.50.176;80;
69+;enp2s0;IPv4;seedlings;_wled._tcp;local
70=;enp2s0;IPv4;seedlings;_wled._tcp;local;seedlings.local;192.168.50.176;80;"mac=483fda540665"
test/fixtures/ip-addr.txt +2 -0
……@@ -0,0 +1,2 @@
12: enp2s0 inet 192.168.50.215/24 brd 192.168.50.255 scope global dynamic noprefixroute enp2s0\ valid_lft 44734sec preferred_lft 44734sec
24: tunnel0 inet 10.22.0.2/16 scope global tunnel0\ valid_lft forever preferred_lft forever
test/fixtures/pgh-app-status.txt +20 -0
……@@ -0,0 +1,20 @@
1name running supervisorPid childPid version startedAt lastExit lastExitCode autostart log installedAt processStartedAt uptimeSeconds skewSeconds stale staleReason
2------------------- ------- ------------- -------- -------- ------------------------ ------------------------ ------------ --------- ---------------------------------------------- ------------------------ ------------------------ ------------- ----------- ----- -----------
3hyper-proto-peer false 0.3.135 2026-10-14T15:01:59.311Z 2026-10-14T15:14:44.731Z 0 false /run/user/1000/pgh/hyper-proto-peer/app.log 2026-10-14T18:35:45.068Z false not running
4atproto-bridge false 0.9.56 2026-10-13T02:52:32.703Z 2026-10-13T22:12:48.816Z 0 false /run/user/1000/pgh/atproto-bridge/app.log 2026-10-14T14:54:07.792Z false not running
5cthuloid-web true 1945131 1945148 0.2.1 2026-10-02T15:26:57.008Z 2026-10-02T15:26:52.544Z 0 true /run/user/1000/pgh/cthuloid-web/app.log 2026-10-02T15:26:56.028Z 2026-10-11T21:53:26.201Z 701207 -800790 false
6hyper-tps false 0.1618.0 false /run/user/1000/pgh/hyper-tps/app.log 2026-10-20T00:24:15.424Z false not running
7favela-web false 0.3.92 2026-10-20T00:21:47.988Z 2026-10-20T00:23:40.438Z 0 false /run/user/1000/pgh/favela-web/app.log 2026-09-30T00:13:41.860Z false not running
8favela-gateway true 2366963 2508944 0.6.6 2026-10-14T00:45:23.524Z 2026-10-15T00:56:46.461Z 75 true /run/user/1000/pgh/favela-gateway/app.log 2026-10-15T00:56:31.180Z 2026-10-15T00:56:47.050Z 431006 -16 false
9favela-peer false 0.0.67 2026-10-13T01:36:07.494Z 2026-10-14T15:14:42.629Z 0 false /run/user/1000/pgh/favela-peer/app.log 2026-10-15T00:56:24.249Z false not running
10atproto-pds false 0.1.0 false /run/user/1000/pgh/atproto-pds/app.log 2026-10-13T23:27:23.072Z false not running
11mirall-cli false 0.1.5 false /run/user/1000/pgh/mirall-cli/app.log 2026-07-25T00:27:36.372Z false not running
12pgh-hello false 0.1.2 false /run/user/1000/pgh/pgh-hello/app.log 2026-05-12T15:58:45.312Z false not running
13atproto-blind-relay false 0.9.10 false /run/user/1000/pgh/atproto-blind-relay/app.log 2026-07-13T17:03:26.812Z false not running
14pgh-sidecar true 2358645 2358660 0.2.69 2026-10-13T23:24:35.123Z 2026-10-13T23:24:34.854Z 0 true /run/user/1000/pgh/pgh-sidecar/app.log 2026-10-13T23:20:07.412Z 2026-10-13T23:24:36.961Z 522936 -270 false
15pear-git-v3 true 2244397 2244407 0.1.254 2026-10-13T02:53:32.945Z 2026-10-13T02:53:31.804Z 1 true /run/user/1000/pgh/pear-git-v3/app.log 2026-10-11T22:33:35.845Z 2026-10-13T02:53:32.959Z 596800 -101997 false
16git-remote-punch false 0.1.8 false /run/user/1000/pgh/git-remote-punch/app.log 2026-07-29T01:38:54.946Z false not running
17favela-host true 2448356 2508970 0.1.17 2026-10-14T15:14:45.618Z 2026-10-15T00:56:51.301Z 75 true /run/user/1000/pgh/favela-host/app.log 2026-10-15T00:56:45.943Z 2026-10-19T01:31:39.225Z 83314 -347693 false
18favela-surface false 0.0.69 2026-10-13T02:53:15.831Z 2026-10-14T15:14:40.057Z false /run/user/1000/pgh/favela-surface/app.log 2026-10-15T00:54:15.002Z false not running
19ap-bridge false 0.10.22 2026-10-13T02:52:02.295Z 2026-10-13T22:12:52.158Z 0 false /run/user/1000/pgh/ap-bridge/app.log 2026-10-15T00:56:08.090Z false not running
20pgh false 0.1.162 false /run/user/1000/pgh/pgh/app.log 2026-10-14T00:44:56.470Z false not running
test/fixtures/proc-net-arp.txt +18 -0
……@@ -0,0 +1,18 @@
1IP address HW type Flags HW address Mask Device
2192.168.50.228 0x1 0x2 98:22:ef:58:0f:97 * enp2s0
3192.168.50.232 0x1 0x2 28:56:5a:39:cc:8d * enp2s0
4192.168.50.114 0x1 0x2 10:7b:44:25:f9:37 * enp2s0
5192.168.50.218 0x1 0x2 bc:a5:11:f9:1a:5f * enp2s0
6192.168.50.153 0x1 0x2 9e:35:af:2b:e2:16 * enp2s0
7192.168.50.161 0x1 0x0 00:00:00:00:00:00 * enp2s0
8192.168.50.211 0x1 0x2 1c:98:c1:38:8c:76 * enp2s0
9192.168.50.165 0x1 0x2 3a:af:f9:36:83:d0 * enp2s0
10192.168.50.16 0x1 0x2 3a:ab:ee:a0:53:3c * enp2s0
11192.168.50.192 0x1 0x2 dc:a6:32:23:f3:2c * enp2s0
12192.168.50.1 0x1 0x2 d4:13:f8:38:88:80 * enp2s0
13192.168.50.89 0x1 0x2 3c:71:bf:84:d9:c8 * enp2s0
14192.168.50.162 0x1 0x0 00:00:00:00:00:00 * enp2s0
15192.168.50.185 0x1 0x2 e8:38:a0:31:cc:e6 * enp2s0
16192.168.50.216 0x1 0x2 b4:2e:99:31:b6:d3 * enp2s0
17192.168.50.52 0x1 0x2 c6:1d:03:b3:66:52 * enp2s0
18192.168.50.224 0x1 0x2 f2:14:00:88:4e:f6 * enp2s0
test/fixtures/proc-net-tcp.txt +236 -0
……@@ -0,0 +1,236 @@
1 sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
2 0: 00000000:0D47 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 920516479 1 0000000000000000 100 0 0 10 0
3 1: 00000000:0D48 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 920519412 1 0000000000000000 100 0 0 10 0
4 2: 00000000:0D49 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 920516493 1 0000000000000000 100 0 0 10 0
5 3: 0100007F:0FCA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 920516487 1 0000000000000000 100 0 0 10 0
6 4: 00000000:0FD2 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 705544823 1 0000000000000000 100 0 0 10 0
7 5: 0100007F:07E3 00000000:0000 0A 00000000:00000000 00:00000000 00000000 995 0 14414 1 0000000000000000 100 0 0 10 0
8 6: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 926668073 1 0000000000000000 100 0 0 10 0
9 7: 0100007F:82A5 00000000:0000 0A 00000000:00000000 00:00000000 00000000 997 0 12119 1 0000000000000000 100 0 0 10 0
10 8: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 9910 1 0000000000000000 100 0 0 10 0
11 9: 00000000:1C34 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 926521468 1 0000000000000000 100 0 0 10 0
12 10: 00000000:1C35 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 13945 1 0000000000000000 100 0 0 10 0
13 11: 00000000:1FA0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 122 0 19578 1 0000000000000000 100 0 0 10 0
14 12: 00000000:1F95 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 17459 1 0000000000000000 100 0 0 10 0
15 13: 00000000:1F91 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 10939 1 0000000000000000 100 0 0 10 0
16 14: 00000000:138D 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 917890145 1 0000000000000000 100 0 0 10 0
17 15: 0100007F:7E91 00000000:0000 0A 00000000:00000000 00:00000000 00000000 997 0 13643 1 0000000000000000 100 0 0 10 0
18 16: 0100007F:7F58 00000000:0000 0A 00000000:00000000 00:00000000 00000000 997 0 14628 1 0000000000000000 100 0 0 10 0
19 17: 3600007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 991 0 7946 1 0000000000000000 100 0 0 10 5
20 18: 3500007F:0035 00000000:0000 0A 00000000:00001001 00:00000000 00000000 991 0 7944 4098 0000000000000000 100 0 0 10 5
21 19: 0200160A:138D 0300160A:D3DE 01 00000000:00000000 00:00000000 00000000 1000 0 923211138 1 0000000000000000 31 4 31 10 -1
22 20: 0200160A:0D47 0300160A:9C1E 01 00000000:00000000 00:00000000 00000000 1000 0 922972370 1 0000000000000000 30 4 31 2 2
23 21: D732A8C0:1FA0 C032A8C0:B118 01 00000000:00000000 00:00000000 00000000 122 0 926451775 1 0000000000000000 20 4 31 10 -1
24 22: 0200160A:138D 0300160A:9796 01 00000000:00000000 00:00000000 00000000 1000 0 925273326 1 0000000000000000 30 4 31 10 109
25 23: 0200160A:138D 0300160A:AD4A 01 00000000:00000000 00:00000000 00000000 1000 0 922899846 1 0000000000000000 30 4 31 10 -1
26 24: 0200160A:138D 0300160A:D008 01 00000000:00000000 00:00000000 00000000 1000 0 928060227 1 0000000000000000 30 4 30 12 30
27 25: 0200160A:138D 0300160A:9922 01 00000000:00000000 00:00000000 00000000 1000 0 926631616 1 0000000000000000 30 4 31 10 30
28 26: 0200160A:138D 0300160A:BCA6 01 00000000:00000000 00:00000000 00000000 1000 0 926661520 1 0000000000000000 35 4 31 16 31
29 27: 0200160A:138D 0300160A:9D68 01 00000000:00000000 00:00000000 00000000 1000 0 927724839 1 0000000000000000 38 4 31 10 -1
30 28: 0200160A:138D 0300160A:BC90 01 00000000:00000000 00:00000000 00000000 1000 0 926965704 1 0000000000000000 30 4 31 10 51
31 29: D732A8C0:D166 7DD868AC:01BB 01 00000000:000C1396 00:00000000 00000000 997 0 918165505 1 0000000000000000 23 4 30 10 -1
32 30: 2632A8C0:A4AA 2129D22C:01BB 08 00000000:00000001 00:00000000 00000000 997 0 12619 1 0000000000000000 25 4 20 10 -1
33 31: 0200160A:138D 0300160A:BC82 01 00000000:00000000 00:00000000 00000000 1000 0 926965703 1 0000000000000000 30 4 31 10 30
34 32: 0200160A:0D47 0300160A:9174 06 00000000:00000000 03:00000FFD 00000000 0 0 0 3 0000000000000000
35 33: 0200160A:138D 0300160A:B890 01 00000000:00000000 00:00000000 00000000 1000 0 927999493 1 0000000000000000 69 4 31 10 28
36 34: 0200160A:138D 0300160A:D574 01 00000000:00000000 00:00000000 00000000 1000 0 923182209 1 0000000000000000 42 4 31 10 207
37 35: 0200160A:138D 0300160A:D9CA 01 00000000:00000000 00:00000000 00000000 1000 0 927442955 1 0000000000000000 30 4 31 10 40
38 36: 0100007F:EB28 0100007F:1FA0 06 00000000:00000000 03:00001713 00000000 0 0 0 3 0000000000000000
39 37: 0200160A:138D 0300160A:A552 01 00000000:00000000 00:00000000 00000000 1000 0 922952758 1 0000000000000000 30 4 30 14 -1
40 38: 0200160A:138D 0300160A:AD4C 01 00000000:00000000 00:00000000 00000000 1000 0 925346889 1 0000000000000000 30 4 31 10 31
41 39: D732A8C0:941A 47858A9B:01BB 01 00000000:00000000 02:0001C1A0 00000000 1000 0 927370987 2 0000000000000000 25 4 31 10 -1
42 40: 0200160A:138D 0300160A:A3B0 01 00000000:00000000 00:00000000 00000000 1000 0 920749151 1 0000000000000000 46 4 31 10 -1
43 41: 0200160A:138D 0300160A:DCCC 01 00000000:00000000 00:00000000 00000000 1000 0 922998471 1 0000000000000000 30 4 30 14 54
44 42: 0200160A:138D 0300160A:BC74 01 00000000:00000000 00:00000000 00000000 1000 0 926965702 1 0000000000000000 31 4 31 16 -1
45 43: 0200160A:138D 0300160A:D152 01 00000000:00000000 00:00000000 00000000 1000 0 926061103 1 0000000000000000 31 4 30 21 -1
46 44: 2632A8C0:A4B8 2129D22C:01BB 08 00000000:00000001 00:00000000 00000000 997 0 12629 1 0000000000000000 25 4 28 10 -1
47 45: 0200160A:0D47 0300160A:E122 01 00000000:00000000 00:00000000 00000000 1000 0 927306458 1 0000000000000000 29 4 31 10 -1
48 46: 0200160A:138D 0300160A:C9EC 01 00000000:00000000 00:00000000 00000000 1000 0 927695418 1 0000000000000000 50 4 30 12 30
49 47: 0200160A:138D 0300160A:8456 01 00000000:00000000 00:00000000 00000000 1000 0 927247221 1 0000000000000000 30 4 33 10 49
50 48: 0200160A:138D 0300160A:99F4 01 00000000:00000000 00:00000000 00000000 1000 0 926572287 1 0000000000000000 30 4 31 10 13
51 49: 0200160A:0D47 0300160A:D792 01 00000000:00000000 00:00000000 00000000 1000 0 926590753 1 0000000000000000 29 4 31 7 7
52 50: 0100007F:EB48 0100007F:1FA0 06 00000000:00000000 03:0000156C 00000000 0 0 0 3 0000000000000000
53 51: 0200160A:138D 0300160A:BA5C 01 00000000:00000000 00:00000000 00000000 1000 0 923294888 1 0000000000000000 29 4 31 10 -1
54 52: 0200160A:138D 0300160A:BA54 01 00000000:00000000 00:00000000 00000000 1000 0 923294886 1 0000000000000000 29 4 31 10 -1
55 53: 0100007F:EB3A 0100007F:1FA0 06 00000000:00000000 03:00001713 00000000 0 0 0 3 0000000000000000
56 54: 0100007F:EB32 0100007F:1FA0 06 00000000:00000000 03:00001714 00000000 0 0 0 3 0000000000000000
57 55: 0200160A:138D 0300160A:9CF6 01 00000000:00000000 00:00000000 00000000 1000 0 925046713 1 0000000000000000 30 4 30 14 -1
58 56: 0200160A:138D 0300160A:BA28 01 00000000:00000000 00:00000000 00000000 1000 0 926571733 1 0000000000000000 30 4 30 7 6
59 57: 0200160A:138D 0300160A:B278 01 00000000:00000000 00:00000000 00000000 1000 0 922878906 1 0000000000000000 30 4 31 10 -1
60 58: 0200160A:0D47 0300160A:A440 01 00000000:00000000 00:00000000 00000000 1000 0 922961276 1 0000000000000000 29 4 31 2 2
61 59: 0200160A:138D 0300160A:D8AA 01 00000000:00000000 00:00000000 00000000 1000 0 926731436 1 0000000000000000 30 4 33 10 56
62 60: 0200160A:0D47 0300160A:DC6A 06 00000000:00000000 03:0000130D 00000000 0 0 0 3 0000000000000000
63 61: 0200160A:138D 0300160A:C9DC 01 00000000:00000000 00:00000000 00000000 1000 0 927695416 1 0000000000000000 33 4 31 10 -1
64 62: 0200160A:138D 0300160A:D4C2 01 00000000:00000000 00:00000000 00000000 1000 0 927675113 1 0000000000000000 30 4 31 10 58
65 63: 0200160A:138D 0300160A:9C4E 01 00000000:00000000 00:00000000 00000000 1000 0 927604078 1 0000000000000000 31 4 33 10 -1
66 64: 0200160A:0D47 0300160A:A70A 01 00000000:00000000 00:00000000 00000000 1000 0 923200164 1 0000000000000000 29 4 31 2 2
67 65: 2632A8C0:A876 CD9740AC:01BB 08 00000000:00000001 00:00000000 00000000 997 0 13887 1 0000000000000000 23 4 24 10 -1
68 66: 0200160A:138D 0300160A:DBE4 01 00000000:00000000 00:00000000 00000000 1000 0 926731382 1 0000000000000000 30 4 31 10 121
69 67: 0200160A:138D 0300160A:E6C4 01 00000000:00000000 00:00000000 00000000 1000 0 923288670 1 0000000000000000 30 4 31 10 -1
70 68: 0200160A:138D 0300160A:9892 01 00000000:00000000 00:00000000 00000000 1000 0 926575108 1 0000000000000000 29 4 31 10 -1
71 69: 0100007F:1F95 0100007F:CF8E 06 00000000:00000000 03:00000D4D 00000000 0 0 0 3 0000000000000000
72 70: 0200160A:138D 0300160A:A10E 01 00000000:00000000 00:00000000 00000000 1000 0 927893733 1 0000000000000000 39 4 32 12 30
73 71: 0100007F:EB0A 0100007F:1FA0 06 00000000:00000000 03:0000156C 00000000 0 0 0 3 0000000000000000
74 72: 0200160A:138D 0300160A:8012 01 00000000:00000000 00:00000000 00000000 1000 0 926552620 1 0000000000000000 31 4 31 7 7
75 73: 0200160A:0D47 0300160A:D75A 01 00000000:00000000 00:00000000 00000000 1000 0 926591241 1 0000000000000000 30 4 31 7 7
76 74: 0200160A:138D 0300160A:CA8E 01 00000000:00000000 00:00000000 00000000 1000 0 926575809 1 0000000000000000 30 4 31 10 62
77 75: 0200160A:138D 0300160A:A5A4 01 00000000:00000000 00:00000000 00000000 1000 0 920946762 1 0000000000000000 30 4 30 14 -1
78 76: 2632A8C0:A866 CD9740AC:01BB 08 00000000:00000001 00:00000000 00000000 997 0 13886 1 0000000000000000 23 4 20 10 -1
79 77: 0200160A:138D 0300160A:C7CE 01 00000000:00000000 00:00000000 00000000 1000 0 926592181 1 0000000000000000 30 4 33 10 -1
80 78: 0200160A:0D47 0300160A:8D54 01 00000000:00000000 00:00000000 00000000 1000 0 927661090 1 0000000000000000 30 4 31 10 -1
81 79: 0200160A:0D47 0300160A:A91C 06 00000000:00000000 03:0000027E 00000000 0 0 0 3 0000000000000000
82 80: 0200160A:138D 0300160A:BC6E 01 00000000:00000000 00:00000000 00000000 1000 0 926965696 1 0000000000000000 30 4 31 10 31
83 81: 0200160A:138D 0300160A:8366 01 00000000:00000000 00:00000000 00000000 1000 0 927441517 1 0000000000000000 39 4 31 16 30
84 82: 0100007F:EB1A 0100007F:1FA0 06 00000000:00000000 03:00001714 00000000 0 0 0 3 0000000000000000
85 83: 0200160A:0D47 0300160A:D8E0 06 00000000:00000000 03:00001530 00000000 0 0 0 3 0000000000000000
86 84: 0200160A:138D 0300160A:B322 01 00000000:00000000 00:00000000 00000000 1000 0 922908199 1 0000000000000000 30 4 31 10 -1
87 85: 0200160A:138D 0300160A:AA8A 01 00000000:00000000 00:00000000 00000000 1000 0 926849102 1 0000000000000000 30 4 30 10 -1
88 86: 0200160A:138D 0300160A:DBDE 01 00000000:00000000 00:00000000 00000000 1000 0 926731381 1 0000000000000000 32 4 30 10 48
89 87: 0200160A:0D47 0300160A:D76A 01 00000000:00000000 00:00000000 00000000 1000 0 926591245 1 0000000000000000 29 4 31 10 -1
90 88: 0200160A:138D 0300160A:9A2A 01 00000000:00000000 00:00000000 00000000 1000 0 923317852 1 0000000000000000 30 4 30 14 -1
91 89: 0200160A:0D47 0300160A:A436 01 00000000:00000000 00:00000000 00000000 1000 0 922961275 1 0000000000000000 30 4 31 2 2
92 90: 0200160A:138D 0300160A:D3FA 01 00000000:00000000 00:00000000 00000000 1000 0 923211141 1 0000000000000000 47 4 29 16 -1
93 91: 0200160A:138D 0300160A:DBC0 01 00000000:00000000 00:00000000 00000000 1000 0 926731377 1 0000000000000000 30 4 30 10 -1
94 92: 0200160A:0D47 0300160A:D786 01 00000000:00000000 00:00000000 00000000 1000 0 926592074 1 0000000000000000 30 4 31 10 -1
95 93: 0200160A:138D 0300160A:80D6 01 00000000:00000000 00:00000000 00000000 1000 0 927611412 1 0000000000000000 31 4 31 10 56
96 94: 0200160A:0D47 0300160A:8D56 01 00000000:00000000 00:00000000 00000000 1000 0 927661091 1 0000000000000000 30 4 31 10 -1
97 95: 0200160A:138D 0300160A:C080 01 00000000:00000000 00:00000000 00000000 1000 0 920553878 1 0000000000000000 32 4 31 10 56
98 96: 0200160A:138D 0300160A:8368 01 00000000:00000000 00:00000000 00000000 1000 0 927441518 1 0000000000000000 30 4 30 14 30
99 97: 0100007F:EB62 0100007F:1FA0 06 00000000:00000000 03:00001713 00000000 0 0 0 3 0000000000000000
100 98: 0200160A:138D 0300160A:8D7C 01 00000000:00000000 00:00000000 00000000 1000 0 927999470 1 0000000000000000 30 4 30 21 -1
101 99: 0200160A:0D47 0300160A:BE48 01 00000000:00000000 00:00000000 00000000 1000 0 927816490 1 0000000000000000 29 4 31 10 -1
102 100: 0200160A:0D47 0300160A:A74A 01 00000000:00000000 00:00000000 00000000 1000 0 926557590 1 0000000000000000 29 4 31 10 -1
103 101: 0200160A:138D 0300160A:B906 01 00000000:00000000 00:00000000 00000000 1000 0 926621500 1 0000000000000000 30 4 31 10 63
104 102: 0200160A:138D 0300160A:9D80 01 00000000:00000000 00:00000000 00000000 1000 0 927724841 1 0000000000000000 37 4 31 10 -1
105 103: 0100007F:82A5 0100007F:8A32 01 00000000:00000000 00:00000000 00000000 997 0 12121 1 0000000000000000 20 4 30 10 -1
106 104: D732A8C0:CEF8 5932A8C0:0539 01 00000000:00000000 00:00000000 00000000 1000 0 927818754 1 0000000000000000 20 4 28 10 -1
107 105: 0200160A:138D 0300160A:E082 01 00000000:00000000 00:00000000 00000000 1000 0 923227703 1 0000000000000000 32 4 31 16 -1
108 106: 0200160A:138D 0300160A:B866 01 00000000:00000000 00:00000000 00000000 1000 0 922978286 1 0000000000000000 35 4 31 12 28
109 107: 0200160A:138D 0300160A:B31E 01 00000000:00000000 00:00000000 00000000 1000 0 922908198 1 0000000000000000 30 4 31 10 -1
110 108: 0200160A:138D 0300160A:AD5A 01 00000000:00000000 00:00000000 00000000 1000 0 925346923 1 0000000000000000 31 4 30 10 37
111 109: 0200160A:0D47 0300160A:A42C 01 00000000:00000000 00:00000000 00000000 1000 0 922968183 1 0000000000000000 29 4 31 2 2
112 110: 0200160A:138D 0300160A:8B7A 01 00000000:00000000 00:00000000 00000000 1000 0 927610420 1 0000000000000000 32 4 31 10 30
113 111: 0200160A:0D47 0300160A:CB72 01 00000000:00000000 00:00000000 00000000 1000 0 926720577 1 0000000000000000 29 4 31 10 -1
114 112: 0200160A:138D 0300160A:D3FC 01 00000000:00000000 00:00000000 00000000 1000 0 923211142 1 0000000000000000 33 4 32 12 -1
115 113: D732A8C0:0016 D832A8C0:CC9C 01 00000000:00000000 02:000AFC65 00000000 0 0 928086354 3 0000000000000000 20 4 13 14 -1
116 114: 0200160A:138D 0300160A:DBE8 01 00000000:00000000 00:00000000 00000000 1000 0 926731383 1 0000000000000000 30 4 30 16 58
117 115: 0200160A:138D 0300160A:DBCE 01 00000000:00000000 00:00000000 00000000 1000 0 926731378 1 0000000000000000 30 4 30 10 -1
118 116: 0200160A:138D 0300160A:9D84 01 00000000:00000000 00:00000000 00000000 1000 0 927724843 1 0000000000000000 30 4 31 10 30
119 117: 0100007F:1F91 0100007F:E900 06 00000000:00000000 03:0000017F 00000000 0 0 0 3 0000000000000000
120 118: 0200160A:138D 0300160A:BC64 01 00000000:00000000 00:00000000 00000000 1000 0 926965695 1 0000000000000000 30 4 31 10 -1
121 119: 0200160A:138D 0300160A:B472 01 00000000:00000000 00:00000000 00000000 1000 0 926795332 1 0000000000000000 30 4 30 10 -1
122 120: 0200160A:0D47 0300160A:A41E 01 00000000:00000000 00:00000000 00000000 1000 0 922961272 1 0000000000000000 30 4 31 2 2
123 121: 0200160A:138D 0300160A:DC36 01 00000000:00000000 00:00000000 00000000 1000 0 926040175 1 0000000000000000 38 4 31 10 30
124 122: 0200160A:138D 0300160A:DB12 01 00000000:00000000 00:00000000 00000000 1000 0 920460096 1 0000000000000000 34 4 31 16 -1
125 123: 0200160A:138D 0300160A:B32C 01 00000000:00000000 00:00000000 00000000 1000 0 922908200 1 0000000000000000 30 4 31 10 -1
126 124: 0200160A:0D47 0300160A:8D52 01 00000000:00000000 00:00000000 00000000 1000 0 927319211 1 0000000000000000 30 4 31 2 7
127 125: 0200160A:138D 0300160A:A81C 01 00000000:00000000 00:00000000 00000000 1000 0 922971964 1 0000000000000000 170 4 30 25 -1
128 126: 0200160A:138D 0300160A:D0D0 01 00000000:00000000 00:00000000 00000000 1000 0 925051361 1 0000000000000000 30 4 31 16 -1
129 127: 0200160A:138D 0300160A:9CDE 01 00000000:00000000 00:00000000 00000000 1000 0 925046711 1 0000000000000000 30 4 31 10 -1
130 128: 0200160A:138D 0300160A:8158 01 00000000:00000000 00:00000000 00000000 1000 0 925981377 1 0000000000000000 30 4 31 10 -1
131 129: 0200160A:138D 0300160A:9884 01 00000000:00000000 00:00000000 00000000 1000 0 926575107 1 0000000000000000 29 4 31 10 -1
132 130: 0100007F:1F95 0100007F:CF80 06 00000000:00000000 03:00000D4D 00000000 0 0 0 3 0000000000000000
133 131: D732A8C0:9E6C 65DA68AC:01BB 01 00000000:00000000 00:00000000 00000000 997 0 927740601 1 0000000000000000 22 4 30 10 -1
134 132: 0200160A:138D 0300160A:9CD2 01 00000000:00000000 00:00000000 00000000 1000 0 925046710 1 0000000000000000 30 4 31 10 -1
135 133: 0200160A:0D47 0300160A:BD30 01 00000000:00000000 00:00000000 00000000 1000 0 926517011 1 0000000000000000 30 4 31 2 2
136 134: 0200160A:138D 0300160A:8EFA 01 00000000:00000000 00:00000000 00000000 1000 0 927011454 1 0000000000000000 30 4 31 10 -1
137 135: 0200160A:138D 0300160A:D204 01 00000000:00000000 00:00000000 00000000 1000 0 926976562 1 0000000000000000 30 4 30 14 33
138 136: 0200160A:0D47 0300160A:D7A2 01 00000000:00000000 00:00000000 00000000 1000 0 926591269 1 0000000000000000 29 4 31 10 -1
139 137: 0200160A:138D 0300160A:C992 01 00000000:00000000 00:00000000 00000000 1000 0 926593220 1 0000000000000000 32 4 30 10 56
140 138: 0200160A:0D47 0300160A:9416 06 00000000:00000000 03:00001656 00000000 0 0 0 3 0000000000000000
141 139: 0200160A:0D47 0300160A:9902 01 00000000:00000000 00:00000000 00000000 1000 0 927816520 1 0000000000000000 29 4 31 10 -1
142 140: 0200160A:0D47 0300160A:EC32 01 00000000:00000000 00:00000000 00000000 1000 0 928086353 1 0000000000000000 29 4 31 10 -1
143 141: D732A8C0:8890 4EAB9C36:01BB 08 00000000:00000019 00:00000000 00000000 997 0 928078099 1 0000000000000000 23 4 28 10 -1
144 142: 0200160A:138D 0300160A:9698 01 00000000:00000000 00:00000000 00000000 1000 0 928011042 1 0000000000000000 52 4 31 10 -1
145 143: 0200160A:0D47 0300160A:E4D0 01 00000000:00000000 00:00000000 00000000 1000 0 920558397 1 0000000000000000 32 4 30 2 2
146 144: 0200160A:138D 0300160A:8CA8 01 00000000:00000000 00:00000000 00000000 1000 0 926458598 1 0000000000000000 31 4 32 8 4
147 145: 0200160A:0D47 0300160A:D794 01 00000000:00000000 00:00000000 00000000 1000 0 926591266 1 0000000000000000 30 4 31 10 -1
148 146: 0200160A:138D 0300160A:B9E6 01 00000000:00000000 00:00000000 00000000 1000 0 927095249 1 0000000000000000 31 4 31 10 -1
149 147: 0200160A:138D 0300160A:CF7A 01 00000000:00000000 00:00000000 00000000 1000 0 927422025 1 0000000000000000 29 4 31 10 30
150 148: 0200160A:0D47 0300160A:91AC 01 00000000:00000000 00:00000000 00000000 1000 0 928009958 1 0000000000000000 29 4 31 10 -1
151 149: 0200160A:0D47 0300160A:E648 01 00000000:00000000 00:00000000 00000000 1000 0 924997857 1 0000000000000000 31 4 31 2 2
152 150: 0200160A:138D 0300160A:9D8A 01 00000000:00000000 00:00000000 00000000 1000 0 927724842 1 0000000000000000 32 4 31 10 -1
153 151: 0200160A:138D 0300160A:B63A 01 00000000:00000000 00:00000000 00000000 1000 0 926403278 1 0000000000000000 30 4 30 7 7
154 152: 0200160A:138D 0300160A:8160 01 00000000:00000000 00:00000000 00000000 1000 0 925981378 1 0000000000000000 30 4 31 10 -1
155 153: 0200160A:138D 0300160A:A898 01 00000000:00000000 00:00000000 00000000 1000 0 924935463 1 0000000000000000 30 4 31 10 -1
156 154: 0200160A:0D47 0300160A:D774 01 00000000:00000000 00:00000000 00000000 1000 0 926592068 1 0000000000000000 30 4 31 10 -1
157 155: 0200160A:0D47 0300160A:EC2C 01 00000000:00000000 00:00000000 00000000 1000 0 928086349 1 0000000000000000 29 4 31 10 -1
158 156: 0200160A:138D 0300160A:B3CE 01 00000000:00000000 00:00000000 00000000 1000 0 926713060 1 0000000000000000 30 4 31 10 89
159 157: 0200160A:138D 0300160A:A3AC 01 00000000:00000000 00:00000000 00000000 1000 0 920749150 1 0000000000000000 30 4 31 10 -1
160 158: 0200160A:138D 0300160A:D82E 01 00000000:00000000 00:00000000 00000000 1000 0 927345394 1 0000000000000000 30 4 32 12 -1
161 159: 0200160A:138D 0300160A:99A8 01 00000000:00000000 00:00000000 00000000 1000 0 923316882 1 0000000000000000 30 4 30 12 -1
162 160: 0100007F:EB42 0100007F:1FA0 06 00000000:00000000 03:00001714 00000000 0 0 0 3 0000000000000000
163 161: 0200160A:138D 0300160A:845E 01 00000000:00000000 00:00000000 00000000 1000 0 927260274 1 0000000000000000 30 4 31 10 -1
164 162: 0200160A:138D 0300160A:9CFC 01 00000000:00000000 00:00000000 00000000 1000 0 925046714 1 0000000000000000 30 4 30 12 -1
165 163: 0200160A:138D 0300160A:E142 01 00000000:00000000 00:00000000 00000000 1000 0 927665976 1 0000000000000000 33 4 31 10 -1
166 164: 0200160A:138D 0300160A:C41E 01 00000000:00000000 00:00000000 00000000 1000 0 927429511 1 0000000000000000 33 4 28 10 -1
167 165: 0200160A:138D 0300160A:A5AA 01 00000000:00000000 00:00000000 00000000 1000 0 920946764 1 0000000000000000 30 4 31 10 -1
168 166: 0200160A:0D47 0300160A:A44C 01 00000000:00000000 00:00000000 00000000 1000 0 922966952 1 0000000000000000 30 4 31 2 2
169 167: 0200160A:138D 0300160A:DB7C 01 00000000:00000000 00:00000000 00000000 1000 0 923281134 1 0000000000000000 60 4 30 80 92
170 168: 0200160A:138D 0300160A:E352 01 00000000:00000000 00:00000000 00000000 1000 0 927369730 1 0000000000000000 30 4 30 14 -1
171 169: 0200160A:138D 0300160A:A860 01 00000000:00000000 00:00000000 00000000 1000 0 928079450 1 0000000000000000 30 4 30 12 -1
172 170: 0200160A:138D 0300160A:BA64 01 00000000:00000000 00:00000000 00000000 1000 0 923294890 1 0000000000000000 30 4 30 12 -1
173 171: 0200160A:138D 0300160A:A3A4 01 00000000:00000000 00:00000000 00000000 1000 0 920749149 1 0000000000000000 30 4 31 10 94
174 172: 0200160A:138D 0300160A:8A12 01 00000000:00000000 00:00000000 00000000 1000 0 926845443 1 0000000000000000 30 4 31 10 -1
175 173: 0200160A:138D 0300160A:BA4A 01 00000000:00000000 00:00000000 00000000 1000 0 923294885 1 0000000000000000 29 4 31 10 -1
176 174: 0200160A:138D 0300160A:9582 01 00000000:00000000 00:00000000 00000000 1000 0 927413156 1 0000000000000000 29 4 31 10 -1
177 175: 0200160A:0D47 0300160A:E438 01 00000000:00000000 00:00000000 00000000 1000 0 925984811 1 0000000000000000 29 4 31 3 2
178 176: 0200160A:138D 0300160A:99EA 01 00000000:00000000 00:00000000 00000000 1000 0 926575532 1 0000000000000000 35 4 30 10 -1
179 177: 0200160A:138D 0300160A:D7BE 01 00000000:00000000 00:00000000 00000000 1000 0 925898839 1 0000000000000000 31 4 32 10 -1
180 178: 0200160A:138D 0300160A:8CC2 01 00000000:00000000 00:00000000 00000000 1000 0 923220864 1 0000000000000000 38 4 31 10 -1
181 179: 0200160A:138D 0300160A:9CEC 01 00000000:00000000 00:00000000 00000000 1000 0 925046712 1 0000000000000000 30 4 31 10 -1
182 180: 0100007F:EB5A 0100007F:1FA0 06 00000000:00000000 03:00001712 00000000 0 0 0 3 0000000000000000
183 181: 0200160A:138D 0300160A:C1A6 01 00000000:00000000 00:00000000 00000000 1000 0 920474893 1 0000000000000000 30 4 31 10 168
184 182: 0100007F:EB60 0100007F:1FA0 06 00000000:00000000 03:00001712 00000000 0 0 0 3 0000000000000000
185 183: 0200160A:138D 0300160A:87E8 01 00000000:00000000 00:00000000 00000000 1000 0 927847826 1 0000000000000000 30 4 32 72 332
186 184: 0200160A:138D 0300160A:B942 01 00000000:00000000 00:00000000 00000000 1000 0 926567880 1 0000000000000000 30 4 31 10 54
187 185: 0200160A:138D 0300160A:BEE4 01 00000000:00000000 00:00000000 00000000 1000 0 928075938 1 0000000000000000 31 4 30 12 -1
188 186: 0200160A:138D 0300160A:A5A8 01 00000000:00000000 00:00000000 00000000 1000 0 920946763 1 0000000000000000 30 4 31 10 -1
189 187: 0200160A:0D47 0300160A:B980 01 00000000:00000000 00:00000000 00000000 1000 0 926523365 1 0000000000000000 29 4 31 2 4
190 188: 0200160A:138D 0300160A:B62A 01 00000000:00000000 00:00000000 00000000 1000 0 926403276 1 0000000000000000 30 4 30 10 7
191 189: 0200160A:138D 0300160A:9CC4 01 00000000:00000000 00:00000000 00000000 1000 0 925046709 1 0000000000000000 39 4 31 10 -1
192 190: 0200160A:138D 0300160A:C2D2 01 00000000:00000000 00:00000000 00000000 1000 0 923113230 1 0000000000000000 30 4 31 10 -1
193 191: 0200160A:138D 0300160A:D656 01 00000000:00000000 00:00000000 00000000 1000 0 926574213 1 0000000000000000 30 4 33 10 22
194 192: 0100007F:E2FC 0100007F:25E0 01 00000000:00000000 00:00000000 00000000 1000 0 928077766 1 0000000000000000 20 4 30 10 -1
195 193: 0200160A:138D 0300160A:ACB6 01 00000000:00000000 00:00000000 00000000 1000 0 926354438 1 0000000000000000 30 4 33 10 28
196 194: 0200160A:138D 0300160A:941C 01 00000000:00000000 00:00000000 00000000 1000 0 925061600 1 0000000000000000 29 4 31 10 -1
197 195: 0200160A:0D47 0300160A:D76C 01 00000000:00000000 00:00000000 00000000 1000 0 926592067 1 0000000000000000 29 4 31 10 -1
198 196: 0200160A:0D47 0300160A:D1D0 01 00000000:00000000 00:00000000 00000000 1000 0 927996134 1 0000000000000000 29 4 31 10 -1
199 197: 0200160A:0D47 0300160A:B51E 06 00000000:00000000 03:0000067B 00000000 0 0 0 3 0000000000000000
200 198: 0200160A:138D 0300160A:BA56 01 00000000:00000000 00:00000000 00000000 1000 0 923294887 1 0000000000000000 30 4 31 10 -1
201 199: 0200160A:0D47 0300160A:DC7A 05 00000000:00000000 03:00001264 00000000 0 0 0 3 0000000000000000
202 200: D732A8C0:1FA0 C032A8C0:C81C 01 00000000:00000000 00:00000000 00000000 122 0 926450704 1 0000000000000000 20 4 31 7 7
203 201: 0200160A:138D 0300160A:87A8 01 00000000:00000000 00:00000000 00000000 1000 0 923211765 1 0000000000000000 34 4 30 10 -1
204 202: 0200160A:138D 0300160A:C75C 01 00000000:00000000 00:00000000 00000000 1000 0 923263574 1 0000000000000000 93 4 31 10 88
205 203: 0200160A:138D 0300160A:D270 01 00000000:00000000 00:00000000 00000000 1000 0 927755395 1 0000000000000000 42 4 31 10 30
206 204: 0200160A:0D47 0300160A:D764 01 00000000:00000000 00:00000000 00000000 1000 0 926591240 1 0000000000000000 29 4 31 7 7
207 205: 0200160A:138D 0300160A:A050 01 00000000:00000000 00:00000000 00000000 1000 0 927679668 1 0000000000000000 30 4 31 10 -1
208 206: 0200160A:0D47 0300160A:D778 01 00000000:00000000 00:00000000 00000000 1000 0 926592072 1 0000000000000000 33 4 31 10 -1
209 207: 0200160A:138D 0300160A:BCA0 01 00000000:00000000 00:00000000 00000000 1000 0 926965705 1 0000000000000000 30 4 31 10 40
210 208: 0200160A:138D 0300160A:DCE6 01 00000000:00000000 00:00000000 00000000 1000 0 928006648 1 0000000000000000 33 4 30 14 -1
211 209: 0200160A:138D 0300160A:D1FE 01 00000000:00000000 00:00000000 00000000 1000 0 926976561 1 0000000000000000 30 4 31 10 30
212 210: 0200160A:138D 0300160A:C6CE 01 00000000:00000000 00:00000000 00000000 1000 0 926605114 1 0000000000000000 30 4 31 10 -1
213 211: 0200160A:138D 0300160A:C860 01 00000000:00000000 00:00000000 00000000 1000 0 923193725 1 0000000000000000 66 4 29 23 -1
214 212: 0100007F:EB52 0100007F:1FA0 06 00000000:00000000 03:00001712 00000000 0 0 0 3 0000000000000000
215 213: 0200160A:138D 0300160A:B75A 01 00000000:00000000 00:00000000 00000000 1000 0 927975718 1 0000000000000000 39 4 33 10 28
216 214: 0200160A:138D 0300160A:B33A 01 00000000:00000000 00:00000000 00000000 1000 0 922908202 1 0000000000000000 32 4 31 10 -1
217 215: 0200160A:138D 0300160A:A596 01 00000000:00000000 00:00000000 00000000 1000 0 920946760 1 0000000000000000 29 4 31 10 -1
218 216: 0200160A:138D 0300160A:AC36 01 00000000:00000000 00:00000000 00000000 1000 0 928011730 1 0000000000000000 63 4 31 10 28
219 217: 0200160A:138D 0300160A:A59C 01 00000000:00000000 00:00000000 00000000 1000 0 920946761 1 0000000000000000 29 4 31 10 -1
220 218: 0100007F:8A32 0100007F:82A5 01 00000000:00000000 00:00000000 00000000 997 0 12618 1 0000000000000000 20 4 4 10 -1
221 219: 0200160A:138D 0300160A:D834 01 00000000:00000000 00:00000000 00000000 1000 0 927345395 1 0000000000000000 29 4 31 10 -1
222 220: 0200160A:138D 0300160A:8E6C 01 00000000:00000000 00:00000000 00000000 1000 0 923282287 1 0000000000000000 30 4 29 10 28
223 221: 0200160A:138D 0300160A:D3EE 01 00000000:00000000 00:00000000 00000000 1000 0 923211139 1 0000000000000000 30 4 31 10 -1
224 222: 0200160A:138D 0300160A:8098 01 00000000:00000000 00:00000000 00000000 1000 0 926857696 1 0000000000000000 30 4 33 16 -1
225 223: 0200160A:138D 0300160A:DAD8 01 00000000:00000000 00:00000000 00000000 1000 0 926359570 1 0000000000000000 30 4 30 8 7
226 224: 0200160A:0D47 0300160A:D7A6 01 00000000:00000000 00:00000000 00000000 1000 0 926591270 1 0000000000000000 30 4 31 10 -1
227 225: 0200160A:138D 0300160A:B644 01 00000000:00000000 00:00000000 00000000 1000 0 926403279 1 0000000000000000 34 4 31 10 -1
228 226: 0200160A:138D 0300160A:B626 01 00000000:00000000 00:00000000 00000000 1000 0 926403275 1 0000000000000000 37 4 30 10 -1
229 227: 0200160A:138D 0300160A:C9F4 01 00000000:00000000 00:00000000 00000000 1000 0 927695419 1 0000000000000000 30 4 31 10 -1
230 228: 0200160A:138D 0300160A:96A6 01 00000000:00000000 00:00000000 00000000 1000 0 928011043 1 0000000000000000 46 4 31 10 -1
231 229: 0200160A:138D 0300160A:D74A 01 00000000:00000000 00:00000000 00000000 1000 0 926864638 1 0000000000000000 30 4 31 10 -1
232 230: 0200160A:0D47 0300160A:B2D4 01 00000000:00000000 00:00000000 00000000 1000 0 926592066 1 0000000000000000 30 4 31 10 -1
233 231: 0200160A:0D47 0300160A:B978 01 00000000:00000000 00:00000000 00000000 1000 0 926534806 1 0000000000000000 29 4 31 2 2
234 232: 0200160A:0D47 0300160A:B2E0 01 00000000:00000000 00:00000000 00000000 1000 0 926592071 1 0000000000000000 34 4 31 10 -1
235 233: 0200160A:0D47 0300160A:A430 01 00000000:00000000 00:00000000 00000000 1000 0 922961274 1 0000000000000000 29 4 31 2 2
236 234: 0100007F:EB6E 0100007F:1FA0 06 00000000:00000000 03:00001712 00000000 0 0 0 3 0000000000000000
test/fixtures/proc-net-tcp6.txt +12 -0
……@@ -0,0 +1,12 @@
1 sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
2 0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 926668072 1 0000000000000000 100 0 0 10 0
3 1: 00000000000000000000000000000000:01BB 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 995 0 12061 1 0000000000000000 100 0 0 10 0
4 2: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 7123 1 0000000000000000 100 0 0 10 0
5 3: 00000000000000000000000000000000:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 995 0 12060 1 0000000000000000 100 0 0 10 0
6 4: 00000000000000000000000000000000:1EC6 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 4037 1 0000000000000000 100 0 0 10 0
7 5: 00000000000000000000000000000000:13BF 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 14803 1 0000000000000000 100 0 0 10 0
8 6: 00000000000000000000000000000000:25E0 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 14658 1 0000000000000000 100 0 0 10 0
9 7: 00000000000000000000000000000000:231D 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12781 1 0000000000000000 100 0 0 10 0
10 8: 00000000000000000000000000000000:7E90 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 997 0 13641 1 0000000000000000 100 0 0 10 0
11 9: 0000000000000000FFFF00000100007F:25E0 0000000000000000FFFF00000100007F:E2FC 01 00000000:00000000 00:00000000 00000000 1000 0 928078795 1 0000000000000000 20 4 30 10 -1
12 10: 000080FE000000007FBD64FB8CFFFD42:0016 000080FE00000000A8D77C0417F0D5AC:F341 01 00000000:00000000 02:0007C3D5 00000000 0 0 927507861 2 0000000000000000 22 4 30 17 13
test/fixtures/systemctl-show.txt +2078 -0
……@@ -0,0 +1,2078 @@
1MainPID=0
2User=
3Id=accounts-daemon.service
4Description=Accounts Service
5LoadState=loaded
6ActiveState=inactive
7SubState=dead
8FragmentPath=/usr/lib/systemd/system/accounts-daemon.service
9UnitFileState=enabled
10ActiveEnterTimestamp=
11
12MainPID=0
13User=
14Id=alsa-restore.service
15Description=Save/Restore Sound Card State
16LoadState=loaded
17ActiveState=active
18SubState=exited
19FragmentPath=/usr/lib/systemd/system/alsa-restore.service
20UnitFileState=static
21ActiveEnterTimestamp=Sat 2026-08-08 20:25:08 EDT
22
23MainPID=0
24User=
25Id=alsa-state.service
26Description=Manage Sound Card State (restore and store)
27LoadState=loaded
28ActiveState=inactive
29SubState=dead
30FragmentPath=/usr/lib/systemd/system/alsa-state.service
31UnitFileState=static
32ActiveEnterTimestamp=
33
34MainPID=0
35User=
36Id=anacron.service
37Description=Run anacron jobs
38LoadState=loaded
39ActiveState=inactive
40SubState=dead
41FragmentPath=/usr/lib/systemd/system/anacron.service
42UnitFileState=enabled
43ActiveEnterTimestamp=Mon 2026-10-19 20:34:26 EDT
44
45MainPID=0
46User=
47Id=apparmor.service
48Description=Load AppArmor profiles
49LoadState=loaded
50ActiveState=active
51SubState=exited
52FragmentPath=/usr/lib/systemd/system/apparmor.service
53UnitFileState=enabled
54ActiveEnterTimestamp=Sat 2026-08-08 20:25:02 EDT
55
56MainPID=0
57User=
58Id=apt-daily-upgrade.service
59Description=Daily apt upgrade and clean activities
60LoadState=loaded
61ActiveState=inactive
62SubState=dead
63FragmentPath=/usr/lib/systemd/system/apt-daily-upgrade.service
64UnitFileState=static
65ActiveEnterTimestamp=
66
67MainPID=0
68User=
69Id=apt-daily.service
70Description=Daily apt download activities
71LoadState=loaded
72ActiveState=inactive
73SubState=dead
74FragmentPath=/usr/lib/systemd/system/apt-daily.service
75UnitFileState=static
76ActiveEnterTimestamp=
77
78MainPID=0
79User=
80Id=auditd.service
81Description=auditd.service
82LoadState=not-found
83ActiveState=inactive
84SubState=dead
85FragmentPath=
86UnitFileState=
87ActiveEnterTimestamp=
88
89MainPID=0
90User=
91Id=auto-cpufreq.service
92Description=auto-cpufreq.service
93LoadState=not-found
94ActiveState=inactive
95SubState=dead
96FragmentPath=
97UnitFileState=
98ActiveEnterTimestamp=
99
100MainPID=772
101User=
102Id=avahi-daemon.service
103Description=Avahi mDNS/DNS-SD Stack
104LoadState=loaded
105ActiveState=active
106SubState=running
107FragmentPath=/usr/lib/systemd/system/avahi-daemon.service
108UnitFileState=enabled
109ActiveEnterTimestamp=Sat 2026-08-08 20:25:12 EDT
110
111MainPID=0
112User=
113Id=avahi.service
114Description=avahi.service
115LoadState=not-found
116ActiveState=inactive
117SubState=dead
118FragmentPath=
119UnitFileState=
120ActiveEnterTimestamp=
121
122MainPID=0
123User=
124Id=blk-availability.service
125Description=Availability of block devices
126LoadState=loaded
127ActiveState=active
128SubState=exited
129FragmentPath=/usr/lib/systemd/system/blk-availability.service
130UnitFileState=enabled
131ActiveEnterTimestamp=Sat 2026-08-08 20:24:46 EDT
132
133MainPID=0
134User=
135Id=blueman-mechanism.service
136Description=Bluetooth management mechanism
137LoadState=loaded
138ActiveState=inactive
139SubState=dead
140FragmentPath=/usr/lib/systemd/system/blueman-mechanism.service
141UnitFileState=enabled
142ActiveEnterTimestamp=Sat 2026-08-08 20:25:36 EDT
143
144MainPID=774
145User=
146Id=bluetooth.service
147Description=Bluetooth service
148LoadState=loaded
149ActiveState=active
150SubState=running
151FragmentPath=/usr/lib/systemd/system/bluetooth.service
152UnitFileState=enabled
153ActiveEnterTimestamp=Sat 2026-08-08 20:25:12 EDT
154
155MainPID=1059
156User=caddy
157Id=caddy.service
158Description=Caddy
159LoadState=loaded
160ActiveState=active
161SubState=running
162FragmentPath=/usr/lib/systemd/system/caddy.service
163UnitFileState=enabled
164ActiveEnterTimestamp=Sat 2026-08-08 20:25:56 EDT
165
166MainPID=0
167User=
168Id=casper-md5check.service
169Description=casper-md5check.service
170LoadState=masked
171ActiveState=inactive
172SubState=dead
173FragmentPath=/etc/systemd/system/casper-md5check.service
174UnitFileState=masked
175ActiveEnterTimestamp=
176
177MainPID=0
178User=
179Id=casper.service
180Description=Shuts down the "live" preinstalled system cleanly
181LoadState=loaded
182ActiveState=inactive
183SubState=dead
184FragmentPath=/usr/lib/systemd/system/casper.service
185UnitFileState=enabled
186ActiveEnterTimestamp=
187
188MainPID=1113
189User=colord
190Id=colord.service
191Description=Manage, Install and Generate Color Profiles
192LoadState=loaded
193ActiveState=active
194SubState=running
195FragmentPath=/usr/lib/systemd/system/colord.service
196UnitFileState=static
197ActiveEnterTimestamp=Sat 2026-08-08 20:25:42 EDT
198
199MainPID=0
200User=
201Id=connman.service
202Description=connman.service
203LoadState=not-found
204ActiveState=inactive
205SubState=dead
206FragmentPath=
207UnitFileState=
208ActiveEnterTimestamp=
209
210MainPID=0
211User=
212Id=console-screen.service
213Description=console-screen.service
214LoadState=not-found
215ActiveState=inactive
216SubState=dead
217FragmentPath=
218UnitFileState=
219ActiveEnterTimestamp=
220
221MainPID=0
222User=
223Id=console-setup.service
224Description=Set console font and keymap
225LoadState=loaded
226ActiveState=active
227SubState=exited
228FragmentPath=/usr/lib/systemd/system/console-setup.service
229UnitFileState=enabled
230ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
231
232MainPID=775
233User=
234Id=cron.service
235Description=Regular background program processing daemon
236LoadState=loaded
237ActiveState=active
238SubState=running
239FragmentPath=/usr/lib/systemd/system/cron.service
240UnitFileState=enabled
241ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
242
243MainPID=3095285
244User=cups-browsed
245Id=cups-browsed.service
246Description=Make remote CUPS printers available locally
247LoadState=loaded
248ActiveState=active
249SubState=running
250FragmentPath=/usr/lib/systemd/system/cups-browsed.service
251UnitFileState=enabled
252ActiveEnterTimestamp=Mon 2026-10-19 00:00:13 EDT
253
254MainPID=3095283
255User=
256Id=cups.service
257Description=CUPS Scheduler
258LoadState=loaded
259ActiveState=active
260SubState=running
261FragmentPath=/usr/lib/systemd/system/cups.service
262UnitFileState=enabled
263ActiveEnterTimestamp=Mon 2026-10-19 00:00:13 EDT
264
265MainPID=776
266User=
267Id=dbus.service
268Description=D-Bus System Message Bus
269LoadState=loaded
270ActiveState=active
271SubState=running
272FragmentPath=/usr/lib/systemd/system/dbus.service
273UnitFileState=static
274ActiveEnterTimestamp=Sat 2026-08-08 20:25:12 EDT
275
276MainPID=0
277User=
278Id=dm-event.service
279Description=Device-mapper event daemon
280LoadState=loaded
281ActiveState=inactive
282SubState=dead
283FragmentPath=/usr/lib/systemd/system/dm-event.service
284UnitFileState=static
285ActiveEnterTimestamp=
286
287MainPID=0
288User=
289Id=dmesg.service
290Description=Save initial kernel messages after boot
291LoadState=loaded
292ActiveState=inactive
293SubState=dead
294FragmentPath=/usr/lib/systemd/system/dmesg.service
295UnitFileState=enabled
296ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
297
298MainPID=0
299User=
300Id=dpkg-db-backup.service
301Description=Daily dpkg database backup service
302LoadState=loaded
303ActiveState=inactive
304SubState=dead
305FragmentPath=/usr/lib/systemd/system/dpkg-db-backup.service
306UnitFileState=static
307ActiveEnterTimestamp=
308
309MainPID=0
310User=
311Id=e2scrub_all.service
312Description=Online ext4 Metadata Check for All Filesystems
313LoadState=loaded
314ActiveState=inactive
315SubState=dead
316FragmentPath=/usr/lib/systemd/system/e2scrub_all.service
317UnitFileState=static
318ActiveEnterTimestamp=
319
320MainPID=0
321User=root
322Id=e2scrub_reap.service
323Description=Remove Stale Online ext4 Metadata Check Snapshots
324LoadState=loaded
325ActiveState=inactive
326SubState=dead
327FragmentPath=/usr/lib/systemd/system/e2scrub_reap.service
328UnitFileState=enabled
329ActiveEnterTimestamp=
330
331MainPID=1011
332User=fleeky
333Id=easynews-indexer.service
334Description=Easynews Indexer Bridge
335LoadState=loaded
336ActiveState=active
337SubState=running
338FragmentPath=/etc/systemd/system/easynews-indexer.service
339UnitFileState=enabled
340ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
341
342MainPID=0
343User=
344Id=emergency.service
345Description=Emergency Shell
346LoadState=loaded
347ActiveState=inactive
348SubState=dead
349FragmentPath=/usr/lib/systemd/system/emergency.service
350UnitFileState=static
351ActiveEnterTimestamp=
352
353MainPID=0
354User=
355Id=fcoe.service
356Description=fcoe.service
357LoadState=not-found
358ActiveState=inactive
359SubState=dead
360FragmentPath=
361UnitFileState=
362ActiveEnterTimestamp=
363
364MainPID=0
365User=
366Id=finalrd.service
367Description=Create final runtime dir for shutdown pivot root
368LoadState=loaded
369ActiveState=active
370SubState=exited
371FragmentPath=/usr/lib/systemd/system/finalrd.service
372UnitFileState=enabled
373ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
374
375MainPID=0
376User=
377Id=fstrim.service
378Description=Discard unused blocks on filesystems from /etc/fstab
379LoadState=loaded
380ActiveState=inactive
381SubState=dead
382FragmentPath=/usr/lib/systemd/system/fstrim.service
383UnitFileState=static
384ActiveEnterTimestamp=
385
386MainPID=0
387User=fwupd-refresh
388Id=fwupd-refresh.service
389Description=Refresh fwupd metadata and update motd
390LoadState=loaded
391ActiveState=inactive
392SubState=dead
393FragmentPath=/usr/lib/systemd/system/fwupd-refresh.service
394UnitFileState=static
395ActiveEnterTimestamp=
396
397MainPID=11784
398User=
399Id=fwupd.service
400Description=Firmware update daemon
401LoadState=loaded
402ActiveState=active
403SubState=running
404FragmentPath=/usr/lib/systemd/system/fwupd.service
405UnitFileState=static
406ActiveEnterTimestamp=Sat 2026-08-08 21:05:35 EDT
407
408MainPID=0
409User=
410Id=getty-static.service
411Description=getty on tty2-tty6 if dbus and logind are not available
412LoadState=loaded
413ActiveState=inactive
414SubState=dead
415FragmentPath=/usr/lib/systemd/system/getty-static.service
416UnitFileState=static
417ActiveEnterTimestamp=
418
419MainPID=1032
420User=
421Id=getty@tty1.service
422Description=Getty on tty1
423LoadState=loaded
424ActiveState=active
425SubState=running
426FragmentPath=/usr/lib/systemd/system/getty@.service
427UnitFileState=enabled
428ActiveEnterTimestamp=Sat 2026-08-08 20:25:25 EDT
429
430MainPID=1033
431User=
432Id=getty@tty7.service
433Description=Getty on tty7
434LoadState=loaded
435ActiveState=active
436SubState=running
437FragmentPath=/usr/lib/systemd/system/getty@.service
438UnitFileState=enabled
439ActiveEnterTimestamp=Sat 2026-08-08 20:25:25 EDT
440
441MainPID=0
442User=
443Id=gpu-manager.service
444Description=Detect the available GPUs and deal with any system changes
445LoadState=loaded
446ActiveState=inactive
447SubState=dead
448FragmentPath=/usr/lib/systemd/system/gpu-manager.service
449UnitFileState=enabled
450ActiveEnterTimestamp=
451
452MainPID=0
453User=
454Id=grub-common.service
455Description=Record successful boot for GRUB
456LoadState=loaded
457ActiveState=inactive
458SubState=dead
459FragmentPath=/usr/lib/systemd/system/grub-common.service
460UnitFileState=enabled
461ActiveEnterTimestamp=
462
463MainPID=0
464User=
465Id=grub-initrd-fallback.service
466Description=GRUB failed boot detection
467LoadState=loaded
468ActiveState=inactive
469SubState=dead
470FragmentPath=/usr/lib/systemd/system/grub-initrd-fallback.service
471UnitFileState=enabled
472ActiveEnterTimestamp=
473
474MainPID=1060
475User=fleeky
476Id=hyperident-gateway.service
477Description=Hyperident P2P Gateway
478LoadState=loaded
479ActiveState=active
480SubState=running
481FragmentPath=/etc/systemd/system/hyperident-gateway.service
482UnitFileState=enabled
483ActiveEnterTimestamp=Sat 2026-08-08 20:25:30 EDT
484
485MainPID=0
486User=
487Id=initrd-cleanup.service
488Description=Cleaning Up and Shutting Down Daemons
489LoadState=loaded
490ActiveState=inactive
491SubState=dead
492FragmentPath=/usr/lib/systemd/system/initrd-cleanup.service
493UnitFileState=static
494ActiveEnterTimestamp=
495
496MainPID=0
497User=
498Id=initrd-parse-etc.service
499Description=Mountpoints Configured in the Real Root
500LoadState=loaded
501ActiveState=inactive
502SubState=dead
503FragmentPath=/usr/lib/systemd/system/initrd-parse-etc.service
504UnitFileState=static
505ActiveEnterTimestamp=
506
507MainPID=0
508User=
509Id=initrd-switch-root.service
510Description=Switch Root
511LoadState=loaded
512ActiveState=inactive
513SubState=dead
514FragmentPath=/usr/lib/systemd/system/initrd-switch-root.service
515UnitFileState=static
516ActiveEnterTimestamp=
517
518MainPID=0
519User=
520Id=initrd-udevadm-cleanup-db.service
521Description=Cleanup udev Database
522LoadState=loaded
523ActiveState=inactive
524SubState=dead
525FragmentPath=/usr/lib/systemd/system/initrd-udevadm-cleanup-db.service
526UnitFileState=static
527ActiveEnterTimestamp=
528
529MainPID=781
530User=
531Id=irqbalance.service
532Description=irqbalance daemon
533LoadState=loaded
534ActiveState=active
535SubState=running
536FragmentPath=/usr/lib/systemd/system/irqbalance.service
537UnitFileState=enabled
538ActiveEnterTimestamp=Sat 2026-08-08 20:25:08 EDT
539
540MainPID=0
541User=
542Id=iscsi-shutdown.service
543Description=iscsi-shutdown.service
544LoadState=not-found
545ActiveState=inactive
546SubState=dead
547FragmentPath=
548UnitFileState=
549ActiveEnterTimestamp=
550
551MainPID=0
552User=
553Id=iscsi.service
554Description=iscsi.service
555LoadState=not-found
556ActiveState=inactive
557SubState=dead
558FragmentPath=
559UnitFileState=
560ActiveEnterTimestamp=
561
562MainPID=0
563User=
564Id=iscsid.service
565Description=iscsid.service
566LoadState=not-found
567ActiveState=inactive
568SubState=dead
569FragmentPath=
570UnitFileState=
571ActiveEnterTimestamp=
572
573MainPID=1071
574User=jellyfin
575Id=jellyfin.service
576Description=Jellyfin Media Server
577LoadState=loaded
578ActiveState=active
579SubState=running
580FragmentPath=/usr/lib/systemd/system/jellyfin.service
581UnitFileState=enabled
582ActiveEnterTimestamp=Sat 2026-08-08 20:25:31 EDT
583
584MainPID=1012
585User=fleeky
586Id=jellyseerr.service
587Description=Jellyseerr
588LoadState=loaded
589ActiveState=active
590SubState=running
591FragmentPath=/etc/systemd/system/jellyseerr.service
592UnitFileState=enabled
593ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
594
595MainPID=0
596User=
597Id=kbd.service
598Description=kbd.service
599LoadState=not-found
600ActiveState=inactive
601SubState=dead
602FragmentPath=
603UnitFileState=
604ActiveEnterTimestamp=
605
606MainPID=0
607User=kernoops
608Id=kerneloops.service
609Description=Tool to automatically collect and submit kernel crash signatures
610LoadState=loaded
611ActiveState=active
612SubState=running
613FragmentPath=/usr/lib/systemd/system/kerneloops.service
614UnitFileState=enabled
615ActiveEnterTimestamp=Sat 2026-08-08 20:25:31 EDT
616
617MainPID=0
618User=
619Id=keyboard-setup.service
620Description=Set the console keyboard layout
621LoadState=loaded
622ActiveState=active
623SubState=exited
624FragmentPath=/usr/lib/systemd/system/keyboard-setup.service
625UnitFileState=enabled
626ActiveEnterTimestamp=Sat 2026-08-08 20:24:47 EDT
627
628MainPID=0
629User=
630Id=kmod-static-nodes.service
631Description=Create List of Static Device Nodes
632LoadState=loaded
633ActiveState=active
634SubState=exited
635FragmentPath=/usr/lib/systemd/system/kmod-static-nodes.service
636UnitFileState=static
637ActiveEnterTimestamp=Sat 2026-08-08 20:24:46 EDT
638
639MainPID=0
640User=
641Id=ldconfig.service
642Description=Rebuild Dynamic Linker Cache
643LoadState=loaded
644ActiveState=inactive
645SubState=dead
646FragmentPath=/usr/lib/systemd/system/ldconfig.service
647UnitFileState=static
648ActiveEnterTimestamp=
649
650MainPID=0
651User=
652Id=lightdm.service
653Description=Light Display Manager
654LoadState=loaded
655ActiveState=inactive
656SubState=dead
657FragmentPath=/usr/lib/systemd/system/lightdm.service
658UnitFileState=indirect
659ActiveEnterTimestamp=
660
661MainPID=0
662User=
663Id=lm-sensors.service
664Description=Initialize hardware monitoring sensors
665LoadState=loaded
666ActiveState=active
667SubState=exited
668FragmentPath=/usr/lib/systemd/system/lm-sensors.service
669UnitFileState=enabled
670ActiveEnterTimestamp=Sat 2026-08-08 20:25:10 EDT
671
672MainPID=0
673User=
674Id=logrotate.service
675Description=Rotate log files
676LoadState=loaded
677ActiveState=inactive
678SubState=dead
679FragmentPath=/usr/lib/systemd/system/logrotate.service
680UnitFileState=static
681ActiveEnterTimestamp=
682
683MainPID=0
684User=
685Id=lvm2-lvmpolld.service
686Description=LVM2 poll daemon
687LoadState=loaded
688ActiveState=inactive
689SubState=dead
690FragmentPath=/usr/lib/systemd/system/lvm2-lvmpolld.service
691UnitFileState=static
692ActiveEnterTimestamp=
693
694MainPID=0
695User=
696Id=lvm2-monitor.service
697Description=Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or progress polling
698LoadState=loaded
699ActiveState=active
700SubState=exited
701FragmentPath=/usr/lib/systemd/system/lvm2-monitor.service
702UnitFileState=enabled
703ActiveEnterTimestamp=Sat 2026-08-08 20:24:51 EDT
704
705MainPID=0
706User=man
707Id=man-db.service
708Description=Daily man-db regeneration
709LoadState=loaded
710ActiveState=inactive
711SubState=dead
712FragmentPath=/usr/lib/systemd/system/man-db.service
713UnitFileState=static
714ActiveEnterTimestamp=
715
716MainPID=0
717User=
718Id=mintsystem.service
719Description=mintsystem.service
720LoadState=loaded
721ActiveState=inactive
722SubState=dead
723FragmentPath=/usr/lib/systemd/system/mintsystem.service
724UnitFileState=enabled
725ActiveEnterTimestamp=Sat 2026-08-08 20:25:08 EDT
726
727MainPID=918
728User=root
729Id=ModemManager.service
730Description=Modem Manager
731LoadState=loaded
732ActiveState=active
733SubState=running
734FragmentPath=/usr/lib/systemd/system/ModemManager.service
735UnitFileState=enabled
736ActiveEnterTimestamp=Sat 2026-08-08 20:25:16 EDT
737
738MainPID=0
739User=
740Id=modprobe@configfs.service
741Description=Load Kernel Module configfs
742LoadState=loaded
743ActiveState=inactive
744SubState=dead
745FragmentPath=/usr/lib/systemd/system/modprobe@.service
746UnitFileState=static
747ActiveEnterTimestamp=
748
749MainPID=0
750User=
751Id=modprobe@dm_mod.service
752Description=Load Kernel Module dm_mod
753LoadState=loaded
754ActiveState=inactive
755SubState=dead
756FragmentPath=/usr/lib/systemd/system/modprobe@.service
757UnitFileState=static
758ActiveEnterTimestamp=
759
760MainPID=0
761User=
762Id=modprobe@drm.service
763Description=Load Kernel Module drm
764LoadState=loaded
765ActiveState=inactive
766SubState=dead
767FragmentPath=/usr/lib/systemd/system/modprobe@.service
768UnitFileState=static
769ActiveEnterTimestamp=
770
771MainPID=0
772User=
773Id=modprobe@efi_pstore.service
774Description=Load Kernel Module efi_pstore
775LoadState=loaded
776ActiveState=inactive
777SubState=dead
778FragmentPath=/usr/lib/systemd/system/modprobe@.service
779UnitFileState=static
780ActiveEnterTimestamp=
781
782MainPID=0
783User=
784Id=modprobe@fuse.service
785Description=Load Kernel Module fuse
786LoadState=loaded
787ActiveState=inactive
788SubState=dead
789FragmentPath=/usr/lib/systemd/system/modprobe@.service
790UnitFileState=static
791ActiveEnterTimestamp=
792
793MainPID=0
794User=
795Id=modprobe@loop.service
796Description=Load Kernel Module loop
797LoadState=loaded
798ActiveState=inactive
799SubState=dead
800FragmentPath=/usr/lib/systemd/system/modprobe@.service
801UnitFileState=static
802ActiveEnterTimestamp=
803
804MainPID=0
805User=
806Id=motd-news.service
807Description=Message of the Day
808LoadState=loaded
809ActiveState=inactive
810SubState=dead
811FragmentPath=/usr/lib/systemd/system/motd-news.service
812UnitFileState=static
813ActiveEnterTimestamp=
814
815MainPID=0
816User=
817Id=netplan-ovs-cleanup.service
818Description=OpenVSwitch configuration for cleanup
819LoadState=loaded
820ActiveState=inactive
821SubState=dead
822FragmentPath=/run/systemd/system/netplan-ovs-cleanup.service
823UnitFileState=enabled-runtime
824ActiveEnterTimestamp=
825
826MainPID=0
827User=
828Id=network-manager.service
829Description=network-manager.service
830LoadState=not-found
831ActiveState=inactive
832SubState=dead
833FragmentPath=
834UnitFileState=
835ActiveEnterTimestamp=
836
837MainPID=0
838User=
839Id=networkd-dispatcher.service
840Description=Dispatcher daemon for systemd-networkd
841LoadState=loaded
842ActiveState=inactive
843SubState=dead
844FragmentPath=/usr/lib/systemd/system/networkd-dispatcher.service
845UnitFileState=enabled
846ActiveEnterTimestamp=
847
848MainPID=0
849User=
850Id=NetworkManager-wait-online.service
851Description=Network Manager Wait Online
852LoadState=loaded
853ActiveState=active
854SubState=exited
855FragmentPath=/usr/lib/systemd/system/NetworkManager-wait-online.service
856UnitFileState=enabled
857ActiveEnterTimestamp=Sat 2026-08-08 20:25:30 EDT
858
859MainPID=887
860User=
861Id=NetworkManager.service
862Description=Network Manager
863LoadState=loaded
864ActiveState=active
865SubState=running
866FragmentPath=/usr/lib/systemd/system/NetworkManager.service
867UnitFileState=enabled
868ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
869
870MainPID=0
871User=
872Id=nslcd.service
873Description=nslcd.service
874LoadState=not-found
875ActiveState=inactive
876SubState=dead
877FragmentPath=
878UnitFileState=
879ActiveEnterTimestamp=
880
881MainPID=0
882User=
883Id=oem-config.service
884Description=oem-config.service
885LoadState=not-found
886ActiveState=inactive
887SubState=dead
888FragmentPath=
889UnitFileState=
890ActiveEnterTimestamp=
891
892MainPID=0
893User=
894Id=openvpn.service
895Description=OpenVPN service
896LoadState=loaded
897ActiveState=active
898SubState=exited
899FragmentPath=/usr/lib/systemd/system/openvpn.service
900UnitFileState=enabled
901ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
902
903MainPID=0
904User=
905Id=ovsdb-server.service
906Description=ovsdb-server.service
907LoadState=not-found
908ActiveState=inactive
909SubState=dead
910FragmentPath=
911UnitFileState=
912ActiveEnterTimestamp=
913
914MainPID=1081
915User=plex
916Id=plexmediaserver.service
917Description=Plex Media Server
918LoadState=loaded
919ActiveState=active
920SubState=running
921FragmentPath=/usr/lib/systemd/system/plexmediaserver.service
922UnitFileState=enabled
923ActiveEnterTimestamp=Sat 2026-08-08 20:25:31 EDT
924
925MainPID=0
926User=
927Id=plocate-updatedb.service
928Description=Update the plocate database
929LoadState=loaded
930ActiveState=inactive
931SubState=dead
932FragmentPath=/usr/lib/systemd/system/plocate-updatedb.service
933UnitFileState=static
934ActiveEnterTimestamp=
935
936MainPID=0
937User=
938Id=plymouth-halt.service
939Description=Show Plymouth Halt Screen
940LoadState=loaded
941ActiveState=inactive
942SubState=dead
943FragmentPath=/usr/lib/systemd/system/plymouth-halt.service
944UnitFileState=static
945ActiveEnterTimestamp=
946
947MainPID=0
948User=
949Id=plymouth-poweroff.service
950Description=Show Plymouth Power Off Screen
951LoadState=loaded
952ActiveState=inactive
953SubState=dead
954FragmentPath=/usr/lib/systemd/system/plymouth-poweroff.service
955UnitFileState=static
956ActiveEnterTimestamp=
957
958MainPID=0
959User=
960Id=plymouth-quit-wait.service
961Description=Hold until boot process finishes up
962LoadState=loaded
963ActiveState=active
964SubState=exited
965FragmentPath=/usr/lib/systemd/system/plymouth-quit-wait.service
966UnitFileState=static
967ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
968
969MainPID=0
970User=
971Id=plymouth-quit.service
972Description=Terminate Plymouth Boot Screen
973LoadState=loaded
974ActiveState=active
975SubState=exited
976FragmentPath=/usr/lib/systemd/system/plymouth-quit.service
977UnitFileState=static
978ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
979
980MainPID=0
981User=
982Id=plymouth-read-write.service
983Description=Tell Plymouth To Write Out Runtime Data
984LoadState=loaded
985ActiveState=active
986SubState=exited
987FragmentPath=/usr/lib/systemd/system/plymouth-read-write.service
988UnitFileState=static
989ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
990
991MainPID=0
992User=
993Id=plymouth-reboot.service
994Description=Show Plymouth Reboot Screen
995LoadState=loaded
996ActiveState=inactive
997SubState=dead
998FragmentPath=/usr/lib/systemd/system/plymouth-reboot.service
999UnitFileState=static
1000ActiveEnterTimestamp=
1001
1002MainPID=0
1003User=
1004Id=plymouth-start.service
1005Description=Show Plymouth Boot Screen
1006LoadState=loaded
1007ActiveState=active
1008SubState=exited
1009FragmentPath=/usr/lib/systemd/system/plymouth-start.service
1010UnitFileState=static
1011ActiveEnterTimestamp=Sat 2026-08-08 20:24:56 EDT
1012
1013MainPID=0
1014User=
1015Id=plymouth-switch-root.service
1016Description=Plymouth switch root service
1017LoadState=loaded
1018ActiveState=inactive
1019SubState=dead
1020FragmentPath=/usr/lib/systemd/system/plymouth-switch-root.service
1021UnitFileState=static
1022ActiveEnterTimestamp=
1023
1024MainPID=785
1025User=polkitd
1026Id=polkit.service
1027Description=Authorization Manager
1028LoadState=loaded
1029ActiveState=active
1030SubState=running
1031FragmentPath=/usr/lib/systemd/system/polkit.service
1032UnitFileState=static
1033ActiveEnterTimestamp=Sat 2026-08-08 20:25:13 EDT
1034
1035MainPID=0
1036User=
1037Id=power-profiles-daemon.service
1038Description=Power Profiles daemon
1039LoadState=loaded
1040ActiveState=inactive
1041SubState=dead
1042FragmentPath=/usr/lib/systemd/system/power-profiles-daemon.service
1043UnitFileState=enabled
1044ActiveEnterTimestamp=
1045
1046MainPID=1014
1047User=fleeky
1048Id=prowlarr.service
1049Description=Prowlarr Daemon
1050LoadState=loaded
1051ActiveState=active
1052SubState=running
1053FragmentPath=/etc/systemd/system/prowlarr.service
1054UnitFileState=enabled
1055ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
1056
1057MainPID=1015
1058User=fleeky
1059Id=radarr.service
1060Description=Radarr Daemon
1061LoadState=loaded
1062ActiveState=active
1063SubState=running
1064FragmentPath=/etc/systemd/system/radarr.service
1065UnitFileState=enabled
1066ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
1067
1068MainPID=0
1069User=
1070Id=rbdmap.service
1071Description=rbdmap.service
1072LoadState=not-found
1073ActiveState=inactive
1074SubState=dead
1075FragmentPath=
1076UnitFileState=
1077ActiveEnterTimestamp=
1078
1079MainPID=0
1080User=
1081Id=rc-local.service
1082Description=/etc/rc.local Compatibility
1083LoadState=loaded
1084ActiveState=inactive
1085SubState=dead
1086FragmentPath=/usr/lib/systemd/system/rc-local.service
1087UnitFileState=static
1088ActiveEnterTimestamp=
1089
1090MainPID=0
1091User=
1092Id=rescue.service
1093Description=Rescue Shell
1094LoadState=loaded
1095ActiveState=inactive
1096SubState=dead
1097FragmentPath=/usr/lib/systemd/system/rescue.service
1098UnitFileState=static
1099ActiveEnterTimestamp=
1100
1101MainPID=797
1102User=
1103Id=rsyslog.service
1104Description=System Logging Service
1105LoadState=loaded
1106ActiveState=active
1107SubState=running
1108FragmentPath=/usr/lib/systemd/system/rsyslog.service
1109UnitFileState=enabled
1110ActiveEnterTimestamp=Sat 2026-08-08 20:25:10 EDT
1111
1112MainPID=1264
1113User=
1114Id=rtkit-daemon.service
1115Description=RealtimeKit Scheduling Policy Service
1116LoadState=loaded
1117ActiveState=active
1118SubState=running
1119FragmentPath=/usr/lib/systemd/system/rtkit-daemon.service
1120UnitFileState=disabled
1121ActiveEnterTimestamp=Sat 2026-08-08 20:26:19 EDT
1122
1123MainPID=0
1124User=
1125Id=sabnzbdplus.service
1126Description=LSB: SABnzbd+ binary newsgrabber
1127LoadState=loaded
1128ActiveState=active
1129SubState=running
1130FragmentPath=/run/systemd/generator.late/sabnzbdplus.service
1131UnitFileState=generated
1132ActiveEnterTimestamp=Sat 2026-08-08 20:27:09 EDT
1133
1134MainPID=0
1135User=
1136Id=secureboot-db.service
1137Description=Secure Boot updates for DB and DBX
1138LoadState=loaded
1139ActiveState=inactive
1140SubState=dead
1141FragmentPath=/usr/lib/systemd/system/secureboot-db.service
1142UnitFileState=enabled
1143ActiveEnterTimestamp=
1144
1145MainPID=0
1146User=
1147Id=setvtrgb.service
1148Description=Set console scheme
1149LoadState=loaded
1150ActiveState=active
1151SubState=exited
1152FragmentPath=/usr/lib/systemd/system/setvtrgb.service
1153UnitFileState=enabled
1154ActiveEnterTimestamp=Sat 2026-08-08 20:25:25 EDT
1155
1156MainPID=790
1157User=
1158Id=smartmontools.service
1159Description=Self Monitoring and Reporting Technology (SMART) Daemon
1160LoadState=loaded
1161ActiveState=active
1162SubState=running
1163FragmentPath=/usr/lib/systemd/system/smartmontools.service
1164UnitFileState=enabled
1165ActiveEnterTimestamp=Sat 2026-08-08 20:26:28 EDT
1166
1167MainPID=1016
1168User=fleeky
1169Id=sonarr.service
1170Description=Sonarr Daemon
1171LoadState=loaded
1172ActiveState=active
1173SubState=running
1174FragmentPath=/etc/systemd/system/sonarr.service
1175UnitFileState=enabled
1176ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
1177
1178MainPID=1142
1179User=
1180Id=ssh.service
1181Description=OpenBSD Secure Shell server
1182LoadState=loaded
1183ActiveState=active
1184SubState=running
1185FragmentPath=/usr/lib/systemd/system/ssh.service
1186UnitFileState=disabled
1187ActiveEnterTimestamp=Sat 2026-08-08 20:25:45 EDT
1188
1189MainPID=0
1190User=
1191Id=ssl-cert.service
1192Description=Generate snakeoil SSL keypair
1193LoadState=loaded
1194ActiveState=inactive
1195SubState=dead
1196FragmentPath=/usr/lib/systemd/system/ssl-cert.service
1197UnitFileState=enabled
1198ActiveEnterTimestamp=
1199
1200MainPID=0
1201User=
1202Id=switcheroo-control.service
1203Description=Switcheroo Control Proxy service
1204LoadState=loaded
1205ActiveState=inactive
1206SubState=dead
1207FragmentPath=/usr/lib/systemd/system/switcheroo-control.service
1208UnitFileState=enabled
1209ActiveEnterTimestamp=
1210
1211MainPID=0
1212User=
1213Id=system76-power.service
1214Description=system76-power.service
1215LoadState=not-found
1216ActiveState=inactive
1217SubState=dead
1218FragmentPath=
1219UnitFileState=
1220ActiveEnterTimestamp=
1221
1222MainPID=0
1223User=
1224Id=systemd-ask-password-console.service
1225Description=Dispatch Password Requests to Console
1226LoadState=loaded
1227ActiveState=inactive
1228SubState=dead
1229FragmentPath=/usr/lib/systemd/system/systemd-ask-password-console.service
1230UnitFileState=static
1231ActiveEnterTimestamp=
1232
1233MainPID=0
1234User=
1235Id=systemd-ask-password-plymouth.service
1236Description=Forward Password Requests to Plymouth
1237LoadState=loaded
1238ActiveState=inactive
1239SubState=dead
1240FragmentPath=/usr/lib/systemd/system/systemd-ask-password-plymouth.service
1241UnitFileState=static
1242ActiveEnterTimestamp=
1243
1244MainPID=0
1245User=
1246Id=systemd-ask-password-wall.service
1247Description=Forward Password Requests to Wall
1248LoadState=loaded
1249ActiveState=inactive
1250SubState=dead
1251FragmentPath=/usr/lib/systemd/system/systemd-ask-password-wall.service
1252UnitFileState=static
1253ActiveEnterTimestamp=
1254
1255MainPID=0
1256User=
1257Id=systemd-battery-check.service
1258Description=Check battery level during early boot
1259LoadState=loaded
1260ActiveState=inactive
1261SubState=dead
1262FragmentPath=/usr/lib/systemd/system/systemd-battery-check.service
1263UnitFileState=static
1264ActiveEnterTimestamp=
1265
1266MainPID=0
1267User=
1268Id=systemd-binfmt.service
1269Description=Set Up Additional Binary Formats
1270LoadState=loaded
1271ActiveState=active
1272SubState=exited
1273FragmentPath=/usr/lib/systemd/system/systemd-binfmt.service
1274UnitFileState=static
1275ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
1276
1277MainPID=0
1278User=
1279Id=systemd-bsod.service
1280Description=Displays emergency message in full screen.
1281LoadState=loaded
1282ActiveState=inactive
1283SubState=dead
1284FragmentPath=/usr/lib/systemd/system/systemd-bsod.service
1285UnitFileState=static
1286ActiveEnterTimestamp=
1287
1288MainPID=0
1289User=
1290Id=systemd-coredump@883-3308187-0.service
1291Description=Process Core Dump (PID 3308187/UID 0)
1292LoadState=loaded
1293ActiveState=failed
1294SubState=failed
1295FragmentPath=/usr/lib/systemd/system/systemd-coredump@.service
1296UnitFileState=static
1297ActiveEnterTimestamp=Mon 2026-09-07 15:54:17 EDT
1298
1299MainPID=0
1300User=
1301Id=systemd-coredump@884-1768591-0.service
1302Description=Process Core Dump (PID 1768591/UID 0)
1303LoadState=loaded
1304ActiveState=failed
1305SubState=failed
1306FragmentPath=/usr/lib/systemd/system/systemd-coredump@.service
1307UnitFileState=static
1308ActiveEnterTimestamp=Tue 2026-09-15 00:11:52 EDT
1309
1310MainPID=0
1311User=
1312Id=systemd-coredump@885-1430075-0.service
1313Description=Process Core Dump (PID 1430075/UID 0)
1314LoadState=loaded
1315ActiveState=failed
1316SubState=failed
1317FragmentPath=/usr/lib/systemd/system/systemd-coredump@.service
1318UnitFileState=static
1319ActiveEnterTimestamp=Tue 2026-09-22 21:06:35 EDT
1320
1321MainPID=0
1322User=
1323Id=systemd-coredump@886-1726789-0.service
1324Description=Process Core Dump (PID 1726789/UID 0)
1325LoadState=loaded
1326ActiveState=failed
1327SubState=failed
1328FragmentPath=/usr/lib/systemd/system/systemd-coredump@.service
1329UnitFileState=static
1330ActiveEnterTimestamp=Wed 2026-09-23 12:44:01 EDT
1331
1332MainPID=0
1333User=
1334Id=systemd-firstboot.service
1335Description=First Boot Wizard
1336LoadState=loaded
1337ActiveState=inactive
1338SubState=dead
1339FragmentPath=/usr/lib/systemd/system/systemd-firstboot.service
1340UnitFileState=static
1341ActiveEnterTimestamp=
1342
1343MainPID=0
1344User=
1345Id=systemd-fsck-root.service
1346Description=File System Check on Root Device
1347LoadState=loaded
1348ActiveState=inactive
1349SubState=dead
1350FragmentPath=/usr/lib/systemd/system/systemd-fsck-root.service
1351UnitFileState=enabled-runtime
1352ActiveEnterTimestamp=
1353
1354MainPID=0
1355User=
1356Id=systemd-fsck@dev-disk-by\x2duuid-BA88\x2d8C53.service
1357Description=File System Check on /dev/disk/by-uuid/BA88-8C53
1358LoadState=loaded
1359ActiveState=active
1360SubState=exited
1361FragmentPath=/usr/lib/systemd/system/systemd-fsck@.service
1362UnitFileState=static
1363ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
1364
1365MainPID=0
1366User=
1367Id=systemd-fsck@dev-disk-by\x2duuid-e146b8c4\x2df50b\x2d45c3\x2d8a8e\x2d9ae4c68dfca2.service
1368Description=File System Check on /dev/disk/by-uuid/e146b8c4-f50b-45c3-8a8e-9ae4c68dfca2
1369LoadState=loaded
1370ActiveState=active
1371SubState=exited
1372FragmentPath=/usr/lib/systemd/system/systemd-fsck@.service
1373UnitFileState=static
1374ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
1375
1376MainPID=0
1377User=
1378Id=systemd-fsckd.service
1379Description=File System Check Daemon to report status
1380LoadState=loaded
1381ActiveState=inactive
1382SubState=dead
1383FragmentPath=/usr/lib/systemd/system/systemd-fsckd.service
1384UnitFileState=static
1385ActiveEnterTimestamp=Sat 2026-08-08 20:24:58 EDT
1386
1387MainPID=0
1388User=
1389Id=systemd-halt.service
1390Description=System Halt
1391LoadState=loaded
1392ActiveState=inactive
1393SubState=dead
1394FragmentPath=/usr/lib/systemd/system/systemd-halt.service
1395UnitFileState=static
1396ActiveEnterTimestamp=
1397
1398MainPID=0
1399User=
1400Id=systemd-hibernate-resume.service
1401Description=Resume from hibernation
1402LoadState=loaded
1403ActiveState=inactive
1404SubState=dead
1405FragmentPath=/usr/lib/systemd/system/systemd-hibernate-resume.service
1406UnitFileState=static
1407ActiveEnterTimestamp=
1408
1409MainPID=0
1410User=
1411Id=systemd-hibernate.service
1412Description=System Hibernate
1413LoadState=loaded
1414ActiveState=inactive
1415SubState=dead
1416FragmentPath=/usr/lib/systemd/system/systemd-hibernate.service
1417UnitFileState=static
1418ActiveEnterTimestamp=
1419
1420MainPID=0
1421User=
1422Id=systemd-hwdb-update.service
1423Description=Rebuild Hardware Database
1424LoadState=loaded
1425ActiveState=inactive
1426SubState=dead
1427FragmentPath=/usr/lib/systemd/system/systemd-hwdb-update.service
1428UnitFileState=static
1429ActiveEnterTimestamp=
1430
1431MainPID=0
1432User=
1433Id=systemd-hybrid-sleep.service
1434Description=System Hybrid Suspend+Hibernate
1435LoadState=loaded
1436ActiveState=inactive
1437SubState=dead
1438FragmentPath=/usr/lib/systemd/system/systemd-hybrid-sleep.service
1439UnitFileState=static
1440ActiveEnterTimestamp=
1441
1442MainPID=0
1443User=
1444Id=systemd-initctl.service
1445Description=initctl Compatibility Daemon
1446LoadState=loaded
1447ActiveState=inactive
1448SubState=dead
1449FragmentPath=/usr/lib/systemd/system/systemd-initctl.service
1450UnitFileState=static
1451ActiveEnterTimestamp=
1452
1453MainPID=0
1454User=
1455Id=systemd-journal-catalog-update.service
1456Description=Rebuild Journal Catalog
1457LoadState=loaded
1458ActiveState=inactive
1459SubState=dead
1460FragmentPath=/usr/lib/systemd/system/systemd-journal-catalog-update.service
1461UnitFileState=static
1462ActiveEnterTimestamp=
1463
1464MainPID=0
1465User=
1466Id=systemd-journal-flush.service
1467Description=Flush Journal to Persistent Storage
1468LoadState=loaded
1469ActiveState=active
1470SubState=exited
1471FragmentPath=/usr/lib/systemd/system/systemd-journal-flush.service
1472UnitFileState=static
1473ActiveEnterTimestamp=Sat 2026-08-08 20:25:03 EDT
1474
1475MainPID=3814529
1476User=
1477Id=systemd-journald.service
1478Description=Journal Service
1479LoadState=loaded
1480ActiveState=active
1481SubState=running
1482FragmentPath=/usr/lib/systemd/system/systemd-journald.service
1483UnitFileState=static
1484ActiveEnterTimestamp=Wed 2026-10-07 08:04:33 EDT
1485
1486MainPID=792
1487User=
1488Id=systemd-logind.service
1489Description=User Login Management
1490LoadState=loaded
1491ActiveState=active
1492SubState=running
1493FragmentPath=/usr/lib/systemd/system/systemd-logind.service
1494UnitFileState=static
1495ActiveEnterTimestamp=Sat 2026-08-08 20:25:09 EDT
1496
1497MainPID=0
1498User=
1499Id=systemd-machine-id-commit.service
1500Description=Commit a transient machine-id on disk
1501LoadState=loaded
1502ActiveState=inactive
1503SubState=dead
1504FragmentPath=/usr/lib/systemd/system/systemd-machine-id-commit.service
1505UnitFileState=static
1506ActiveEnterTimestamp=
1507
1508MainPID=0
1509User=
1510Id=systemd-modules-load.service
1511Description=Load Kernel Modules
1512LoadState=loaded
1513ActiveState=active
1514SubState=exited
1515FragmentPath=/usr/lib/systemd/system/systemd-modules-load.service
1516UnitFileState=static
1517ActiveEnterTimestamp=Sat 2026-08-08 20:24:48 EDT
1518
1519MainPID=0
1520User=systemd-network
1521Id=systemd-networkd.service
1522Description=Network Configuration
1523LoadState=loaded
1524ActiveState=inactive
1525SubState=dead
1526FragmentPath=/usr/lib/systemd/system/systemd-networkd.service
1527UnitFileState=disabled
1528ActiveEnterTimestamp=
1529
1530MainPID=0
1531User=
1532Id=systemd-oomd.service
1533Description=systemd-oomd.service
1534LoadState=not-found
1535ActiveState=inactive
1536SubState=dead
1537FragmentPath=
1538UnitFileState=
1539ActiveEnterTimestamp=
1540
1541MainPID=0
1542User=
1543Id=systemd-pcrmachine.service
1544Description=TPM2 PCR Machine ID Measurement
1545LoadState=loaded
1546ActiveState=inactive
1547SubState=dead
1548FragmentPath=/usr/lib/systemd/system/systemd-pcrmachine.service
1549UnitFileState=static
1550ActiveEnterTimestamp=
1551
1552MainPID=0
1553User=
1554Id=systemd-pcrphase-initrd.service
1555Description=TPM2 PCR Barrier (initrd)
1556LoadState=loaded
1557ActiveState=inactive
1558SubState=dead
1559FragmentPath=/usr/lib/systemd/system/systemd-pcrphase-initrd.service
1560UnitFileState=static
1561ActiveEnterTimestamp=
1562
1563MainPID=0
1564User=
1565Id=systemd-pcrphase-sysinit.service
1566Description=TPM2 PCR Barrier (Initialization)
1567LoadState=loaded
1568ActiveState=inactive
1569SubState=dead
1570FragmentPath=/usr/lib/systemd/system/systemd-pcrphase-sysinit.service
1571UnitFileState=static
1572ActiveEnterTimestamp=
1573
1574MainPID=0
1575User=
1576Id=systemd-pcrphase.service
1577Description=TPM2 PCR Barrier (User)
1578LoadState=loaded
1579ActiveState=inactive
1580SubState=dead
1581FragmentPath=/usr/lib/systemd/system/systemd-pcrphase.service
1582UnitFileState=static
1583ActiveEnterTimestamp=
1584
1585MainPID=0
1586User=
1587Id=systemd-poweroff.service
1588Description=System Power Off
1589LoadState=loaded
1590ActiveState=inactive
1591SubState=dead
1592FragmentPath=/usr/lib/systemd/system/systemd-poweroff.service
1593UnitFileState=static
1594ActiveEnterTimestamp=
1595
1596MainPID=0
1597User=
1598Id=systemd-pstore.service
1599Description=Platform Persistent Storage Archival
1600LoadState=loaded
1601ActiveState=inactive
1602SubState=dead
1603FragmentPath=/usr/lib/systemd/system/systemd-pstore.service
1604UnitFileState=enabled
1605ActiveEnterTimestamp=
1606
1607MainPID=0
1608User=
1609Id=systemd-quotacheck.service
1610Description=File System Quota Check
1611LoadState=loaded
1612ActiveState=inactive
1613SubState=dead
1614FragmentPath=/usr/lib/systemd/system/systemd-quotacheck.service
1615UnitFileState=static
1616ActiveEnterTimestamp=
1617
1618MainPID=0
1619User=
1620Id=systemd-random-seed.service
1621Description=Load/Save OS Random Seed
1622LoadState=loaded
1623ActiveState=active
1624SubState=exited
1625FragmentPath=/usr/lib/systemd/system/systemd-random-seed.service
1626UnitFileState=static
1627ActiveEnterTimestamp=Sat 2026-08-08 20:24:48 EDT
1628
1629MainPID=0
1630User=
1631Id=systemd-reboot.service
1632Description=System Reboot
1633LoadState=loaded
1634ActiveState=inactive
1635SubState=dead
1636FragmentPath=/usr/lib/systemd/system/systemd-reboot.service
1637UnitFileState=static
1638ActiveEnterTimestamp=
1639
1640MainPID=0
1641User=
1642Id=systemd-remount-fs.service
1643Description=Remount Root and Kernel File Systems
1644LoadState=loaded
1645ActiveState=active
1646SubState=exited
1647FragmentPath=/usr/lib/systemd/system/systemd-remount-fs.service
1648UnitFileState=enabled-runtime
1649ActiveEnterTimestamp=Sat 2026-08-08 20:24:47 EDT
1650
1651MainPID=0
1652User=
1653Id=systemd-repart.service
1654Description=Repartition Root Disk
1655LoadState=loaded
1656ActiveState=inactive
1657SubState=dead
1658FragmentPath=/usr/lib/systemd/system/systemd-repart.service
1659UnitFileState=static
1660ActiveEnterTimestamp=
1661
1662MainPID=762
1663User=systemd-resolve
1664Id=systemd-resolved.service
1665Description=Network Name Resolution
1666LoadState=loaded
1667ActiveState=active
1668SubState=running
1669FragmentPath=/usr/lib/systemd/system/systemd-resolved.service
1670UnitFileState=enabled
1671ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
1672
1673MainPID=0
1674User=
1675Id=systemd-rfkill.service
1676Description=Load/Save RF Kill Switch Status
1677LoadState=loaded
1678ActiveState=inactive
1679SubState=dead
1680FragmentPath=/usr/lib/systemd/system/systemd-rfkill.service
1681UnitFileState=static
1682ActiveEnterTimestamp=Sat 2026-08-08 20:25:04 EDT
1683
1684MainPID=0
1685User=
1686Id=systemd-soft-reboot.service
1687Description=Reboot System Userspace
1688LoadState=loaded
1689ActiveState=inactive
1690SubState=dead
1691FragmentPath=/usr/lib/systemd/system/systemd-soft-reboot.service
1692UnitFileState=static
1693ActiveEnterTimestamp=
1694
1695MainPID=0
1696User=
1697Id=systemd-suspend-then-hibernate.service
1698Description=System Suspend then Hibernate
1699LoadState=loaded
1700ActiveState=inactive
1701SubState=dead
1702FragmentPath=/usr/lib/systemd/system/systemd-suspend-then-hibernate.service
1703UnitFileState=static
1704ActiveEnterTimestamp=
1705
1706MainPID=0
1707User=
1708Id=systemd-suspend.service
1709Description=System Suspend
1710LoadState=loaded
1711ActiveState=inactive
1712SubState=dead
1713FragmentPath=/usr/lib/systemd/system/systemd-suspend.service
1714UnitFileState=static
1715ActiveEnterTimestamp=
1716
1717MainPID=0
1718User=
1719Id=systemd-sysctl.service
1720Description=Apply Kernel Variables
1721LoadState=loaded
1722ActiveState=active
1723SubState=exited
1724FragmentPath=/usr/lib/systemd/system/systemd-sysctl.service
1725UnitFileState=static
1726ActiveEnterTimestamp=Sat 2026-08-08 20:24:51 EDT
1727
1728MainPID=0
1729User=
1730Id=systemd-sysext.service
1731Description=Merge System Extension Images into /usr/ and /opt/
1732LoadState=loaded
1733ActiveState=inactive
1734SubState=dead
1735FragmentPath=/usr/lib/systemd/system/systemd-sysext.service
1736UnitFileState=disabled
1737ActiveEnterTimestamp=
1738
1739MainPID=0
1740User=
1741Id=systemd-sysusers.service
1742Description=Create System Users
1743LoadState=loaded
1744ActiveState=inactive
1745SubState=dead
1746FragmentPath=/usr/lib/systemd/system/systemd-sysusers.service
1747UnitFileState=static
1748ActiveEnterTimestamp=
1749
1750MainPID=763
1751User=systemd-timesync
1752Id=systemd-timesyncd.service
1753Description=Network Time Synchronization
1754LoadState=loaded
1755ActiveState=active
1756SubState=running
1757FragmentPath=/usr/lib/systemd/system/systemd-timesyncd.service
1758UnitFileState=enabled
1759ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
1760
1761MainPID=0
1762User=
1763Id=systemd-tmpfiles-clean.service
1764Description=Cleanup of Temporary Directories
1765LoadState=loaded
1766ActiveState=inactive
1767SubState=dead
1768FragmentPath=/usr/lib/systemd/system/systemd-tmpfiles-clean.service
1769UnitFileState=static
1770ActiveEnterTimestamp=
1771
1772MainPID=0
1773User=
1774Id=systemd-tmpfiles-setup-dev-early.service
1775Description=Create Static Device Nodes in /dev gracefully
1776LoadState=loaded
1777ActiveState=active
1778SubState=exited
1779FragmentPath=/usr/lib/systemd/system/systemd-tmpfiles-setup-dev-early.service
1780UnitFileState=static
1781ActiveEnterTimestamp=Sat 2026-08-08 20:24:50 EDT
1782
1783MainPID=0
1784User=
1785Id=systemd-tmpfiles-setup-dev.service
1786Description=Create Static Device Nodes in /dev
1787LoadState=loaded
1788ActiveState=active
1789SubState=exited
1790FragmentPath=/usr/lib/systemd/system/systemd-tmpfiles-setup-dev.service
1791UnitFileState=static
1792ActiveEnterTimestamp=Sat 2026-08-08 20:24:50 EDT
1793
1794MainPID=0
1795User=
1796Id=systemd-tmpfiles-setup.service
1797Description=Create Volatile Files and Directories
1798LoadState=loaded
1799ActiveState=active
1800SubState=exited
1801FragmentPath=/usr/lib/systemd/system/systemd-tmpfiles-setup.service
1802UnitFileState=static
1803ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
1804
1805MainPID=0
1806User=
1807Id=systemd-tpm2-setup-early.service
1808Description=TPM2 SRK Setup (Early)
1809LoadState=loaded
1810ActiveState=inactive
1811SubState=dead
1812FragmentPath=/usr/lib/systemd/system/systemd-tpm2-setup-early.service
1813UnitFileState=static
1814ActiveEnterTimestamp=
1815
1816MainPID=0
1817User=
1818Id=systemd-tpm2-setup.service
1819Description=TPM2 SRK Setup
1820LoadState=loaded
1821ActiveState=inactive
1822SubState=dead
1823FragmentPath=/usr/lib/systemd/system/systemd-tpm2-setup.service
1824UnitFileState=static
1825ActiveEnterTimestamp=
1826
1827MainPID=0
1828User=
1829Id=systemd-udev-settle.service
1830Description=Wait for udev To Complete Device Initialization
1831LoadState=loaded
1832ActiveState=inactive
1833SubState=dead
1834FragmentPath=/usr/lib/systemd/system/systemd-udev-settle.service
1835UnitFileState=static
1836ActiveEnterTimestamp=
1837
1838MainPID=0
1839User=
1840Id=systemd-udev-trigger.service
1841Description=Coldplug All udev Devices
1842LoadState=loaded
1843ActiveState=active
1844SubState=exited
1845FragmentPath=/usr/lib/systemd/system/systemd-udev-trigger.service
1846UnitFileState=static
1847ActiveEnterTimestamp=Sat 2026-08-08 20:24:47 EDT
1848
1849MainPID=410
1850User=
1851Id=systemd-udevd.service
1852Description=Rule-based Manager for Device Events and Files
1853LoadState=loaded
1854ActiveState=active
1855SubState=running
1856FragmentPath=/usr/lib/systemd/system/systemd-udevd.service
1857UnitFileState=static
1858ActiveEnterTimestamp=Sat 2026-08-08 20:24:55 EDT
1859
1860MainPID=0
1861User=
1862Id=systemd-update-done.service
1863Description=Update is Completed
1864LoadState=loaded
1865ActiveState=inactive
1866SubState=dead
1867FragmentPath=/usr/lib/systemd/system/systemd-update-done.service
1868UnitFileState=static
1869ActiveEnterTimestamp=
1870
1871MainPID=0
1872User=
1873Id=systemd-update-utmp-runlevel.service
1874Description=Record Runlevel Change in UTMP
1875LoadState=loaded
1876ActiveState=inactive
1877SubState=dead
1878FragmentPath=/usr/lib/systemd/system/systemd-update-utmp-runlevel.service
1879UnitFileState=static
1880ActiveEnterTimestamp=
1881
1882MainPID=0
1883User=
1884Id=systemd-update-utmp.service
1885Description=Record System Boot/Shutdown in UTMP
1886LoadState=loaded
1887ActiveState=active
1888SubState=exited
1889FragmentPath=/usr/lib/systemd/system/systemd-update-utmp.service
1890UnitFileState=static
1891ActiveEnterTimestamp=Sat 2026-08-08 20:25:07 EDT
1892
1893MainPID=0
1894User=
1895Id=systemd-user-sessions.service
1896Description=Permit User Sessions
1897LoadState=loaded
1898ActiveState=active
1899SubState=exited
1900FragmentPath=/usr/lib/systemd/system/systemd-user-sessions.service
1901UnitFileState=static
1902ActiveEnterTimestamp=Sat 2026-08-08 20:25:24 EDT
1903
1904MainPID=0
1905User=
1906Id=systemd-vconsole-setup.service
1907Description=systemd-vconsole-setup.service
1908LoadState=not-found
1909ActiveState=inactive
1910SubState=dead
1911FragmentPath=
1912UnitFileState=
1913ActiveEnterTimestamp=
1914
1915MainPID=793
1916User=
1917Id=thermald.service
1918Description=Thermal Daemon Service
1919LoadState=loaded
1920ActiveState=active
1921SubState=running
1922FragmentPath=/usr/lib/systemd/system/thermald.service
1923UnitFileState=enabled
1924ActiveEnterTimestamp=Sat 2026-08-08 20:25:12 EDT
1925
1926MainPID=0
1927User=
1928Id=tlp.service
1929Description=tlp.service
1930LoadState=not-found
1931ActiveState=inactive
1932SubState=dead
1933FragmentPath=
1934UnitFileState=
1935ActiveEnterTimestamp=
1936
1937MainPID=0
1938User=
1939Id=tpm-udev.service
1940Description=Handle dynamically added tpm devices
1941LoadState=loaded
1942ActiveState=inactive
1943SubState=dead
1944FragmentPath=/usr/lib/systemd/system/tpm-udev.service
1945UnitFileState=static
1946ActiveEnterTimestamp=
1947
1948MainPID=0
1949User=
1950Id=tuned.service
1951Description=tuned.service
1952LoadState=not-found
1953ActiveState=inactive
1954SubState=dead
1955FragmentPath=
1956UnitFileState=
1957ActiveEnterTimestamp=
1958
1959MainPID=0
1960User=
1961Id=ubuntu-system-adjustments.service
1962Description=Ubuntu system adjustments
1963LoadState=loaded
1964ActiveState=active
1965SubState=exited
1966FragmentPath=/usr/lib/systemd/system/ubuntu-system-adjustments.service
1967UnitFileState=enabled
1968ActiveEnterTimestamp=Sat 2026-08-08 20:25:13 EDT
1969
1970MainPID=11797
1971User=
1972Id=udisks2.service
1973Description=Disk Manager
1974LoadState=loaded
1975ActiveState=active
1976SubState=running
1977FragmentPath=/usr/lib/systemd/system/udisks2.service
1978UnitFileState=enabled
1979ActiveEnterTimestamp=Sat 2026-08-08 21:05:35 EDT
1980
1981MainPID=0
1982User=
1983Id=ufw.service
1984Description=Uncomplicated firewall
1985LoadState=loaded
1986ActiveState=active
1987SubState=exited
1988FragmentPath=/usr/lib/systemd/system/ufw.service
1989UnitFileState=enabled
1990ActiveEnterTimestamp=Sat 2026-08-08 20:25:00 EDT
1991
1992MainPID=11791
1993User=
1994Id=upower.service
1995Description=Daemon for power management
1996LoadState=loaded
1997ActiveState=active
1998SubState=running
1999FragmentPath=/usr/lib/systemd/system/upower.service
2000UnitFileState=disabled
2001ActiveEnterTimestamp=Sat 2026-08-08 21:05:34 EDT
2002
2003MainPID=0
2004User=
2005Id=user-runtime-dir@1000.service
2006Description=User Runtime Directory /run/user/1000
2007LoadState=loaded
2008ActiveState=active
2009SubState=exited
2010FragmentPath=/usr/lib/systemd/system/user-runtime-dir@.service
2011UnitFileState=static
2012ActiveEnterTimestamp=Sat 2026-08-08 20:25:54 EDT
2013
2014MainPID=0
2015User=
2016Id=user-runtime-dir@1001.service
2017Description=User Runtime Directory /run/user/1001
2018LoadState=loaded
2019ActiveState=active
2020SubState=exited
2021FragmentPath=/usr/lib/systemd/system/user-runtime-dir@.service
2022UnitFileState=static
2023ActiveEnterTimestamp=Mon 2026-10-19 12:56:13 EDT
2024
2025MainPID=1172
2026User=1000
2027Id=user@1000.service
2028Description=User Manager for UID 1000
2029LoadState=loaded
2030ActiveState=active
2031SubState=running
2032FragmentPath=/usr/lib/systemd/system/user@.service
2033UnitFileState=static
2034ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
2035
2036MainPID=3177644
2037User=1001
2038Id=user@1001.service
2039Description=User Manager for UID 1001
2040LoadState=loaded
2041ActiveState=active
2042SubState=running
2043FragmentPath=/usr/lib/systemd/system/user@.service
2044UnitFileState=static
2045ActiveEnterTimestamp=Mon 2026-10-19 12:56:14 EDT
2046
2047MainPID=0
2048User=uuidd
2049Id=uuidd.service
2050Description=Daemon for generating UUIDs
2051LoadState=loaded
2052ActiveState=inactive
2053SubState=dead
2054FragmentPath=/usr/lib/systemd/system/uuidd.service
2055UnitFileState=indirect
2056ActiveEnterTimestamp=
2057
2058MainPID=0
2059User=
2060Id=wicd.service
2061Description=wicd.service
2062LoadState=not-found
2063ActiveState=inactive
2064SubState=dead
2065FragmentPath=
2066UnitFileState=
2067ActiveEnterTimestamp=
2068
2069MainPID=888
2070User=
2071Id=wpa_supplicant.service
2072Description=WPA supplicant
2073LoadState=loaded
2074ActiveState=active
2075SubState=running
2076FragmentPath=/usr/lib/systemd/system/wpa_supplicant.service
2077UnitFileState=enabled
2078ActiveEnterTimestamp=Sat 2026-08-08 20:25:12 EDT
test/fixtures/systemctl-user-show.txt +274 -0
……@@ -0,0 +1,274 @@
1MainPID=719556
2User=
3Id=at-spi-dbus-bus.service
4Description=Accessibility services bus
5LoadState=loaded
6ActiveState=active
7SubState=running
8FragmentPath=/usr/lib/systemd/user/at-spi-dbus-bus.service
9UnitFileState=static
10ActiveEnterTimestamp=Mon 2026-08-10 12:30:12 EDT
11
12MainPID=1241
13User=
14Id=dbus.service
15Description=D-Bus User Message Bus
16LoadState=loaded
17ActiveState=active
18SubState=running
19FragmentPath=/usr/lib/systemd/user/dbus.service
20UnitFileState=static
21ActiveEnterTimestamp=Sat 2026-08-08 20:26:19 EDT
22
23MainPID=0
24User=
25Id=dirmngr.service
26Description=GnuPG network certificate management daemon
27LoadState=loaded
28ActiveState=inactive
29SubState=dead
30FragmentPath=/usr/lib/systemd/user/dirmngr.service
31UnitFileState=static
32ActiveEnterTimestamp=
33
34MainPID=1230
35User=
36Id=filter-chain.service
37Description=PipeWire filter chain daemon
38LoadState=loaded
39ActiveState=active
40SubState=running
41FragmentPath=/usr/lib/systemd/user/filter-chain.service
42UnitFileState=enabled
43ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
44
45MainPID=0
46User=
47Id=gcr-ssh-agent.service
48Description=GCR ssh-agent wrapper
49LoadState=loaded
50ActiveState=inactive
51SubState=dead
52FragmentPath=/usr/lib/systemd/user/gcr-ssh-agent.service
53UnitFileState=enabled
54ActiveEnterTimestamp=
55
56MainPID=0
57User=
58Id=gnome-keyring-daemon.service
59Description=GNOME Keyring daemon
60LoadState=loaded
61ActiveState=inactive
62SubState=dead
63FragmentPath=/usr/lib/systemd/user/gnome-keyring-daemon.service
64UnitFileState=enabled
65ActiveEnterTimestamp=
66
67MainPID=0
68User=
69Id=gpg-agent.service
70Description=GnuPG cryptographic agent and passphrase cache
71LoadState=loaded
72ActiveState=inactive
73SubState=dead
74FragmentPath=/usr/lib/systemd/user/gpg-agent.service
75UnitFileState=static
76ActiveEnterTimestamp=
77
78MainPID=719494
79User=
80Id=gvfs-daemon.service
81Description=Virtual filesystem service
82LoadState=loaded
83ActiveState=active
84SubState=running
85FragmentPath=/usr/lib/systemd/user/gvfs-daemon.service
86UnitFileState=static
87ActiveEnterTimestamp=Mon 2026-08-10 12:30:03 EDT
88
89MainPID=0
90User=
91Id=keyboxd.service
92Description=GnuPG public key management service
93LoadState=loaded
94ActiveState=inactive
95SubState=dead
96FragmentPath=/usr/lib/systemd/user/keyboxd.service
97UnitFileState=static
98ActiveEnterTimestamp=
99
100MainPID=1227
101User=
102Id=kodi-repo.service
103Description=Kodi addon update repo (patched Jacktook fork)
104LoadState=loaded
105ActiveState=active
106SubState=running
107FragmentPath=/home/fleeky/.config/systemd/user/kodi-repo.service
108UnitFileState=enabled
109ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
110
111MainPID=0
112User=
113Id=launchpadlib-cache-clean.service
114Description=Clean up old files in the Launchpadlib cache
115LoadState=loaded
116ActiveState=inactive
117SubState=dead
118FragmentPath=/usr/lib/systemd/user/launchpadlib-cache-clean.service
119UnitFileState=static
120ActiveEnterTimestamp=
121
122MainPID=0
123User=
124Id=pipewire-media-session.service
125Description=pipewire-media-session.service
126LoadState=not-found
127ActiveState=inactive
128SubState=dead
129FragmentPath=
130UnitFileState=
131ActiveEnterTimestamp=
132
133MainPID=1234
134User=
135Id=pipewire-pulse.service
136Description=PipeWire PulseAudio
137LoadState=loaded
138ActiveState=active
139SubState=running
140FragmentPath=/usr/lib/systemd/user/pipewire-pulse.service
141UnitFileState=enabled
142ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
143
144MainPID=0
145User=
146Id=pipewire-session-manager.service
147Description=pipewire-session-manager.service
148LoadState=not-found
149ActiveState=inactive
150SubState=dead
151FragmentPath=
152UnitFileState=
153ActiveEnterTimestamp=
154
155MainPID=1228
156User=
157Id=pipewire.service
158Description=PipeWire Multimedia Service
159LoadState=loaded
160ActiveState=active
161SubState=running
162FragmentPath=/usr/lib/systemd/user/pipewire.service
163UnitFileState=enabled
164ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
165
166MainPID=0
167User=
168Id=pk-debconf-helper.service
169Description=debconf communication service
170LoadState=loaded
171ActiveState=inactive
172SubState=dead
173FragmentPath=/usr/lib/systemd/user/pk-debconf-helper.service
174UnitFileState=static
175ActiveEnterTimestamp=
176
177MainPID=0
178User=
179Id=pulseaudio.service
180Description=pulseaudio.service
181LoadState=not-found
182ActiveState=inactive
183SubState=dead
184FragmentPath=
185UnitFileState=
186ActiveEnterTimestamp=
187
188MainPID=0
189User=
190Id=speech-dispatcher.service
191Description=Common interface to speech synthesizers
192LoadState=loaded
193ActiveState=inactive
194SubState=dead
195FragmentPath=/usr/lib/systemd/user/speech-dispatcher.service
196UnitFileState=static
197ActiveEnterTimestamp=
198
199MainPID=3079448
200User=
201Id=stremio-vsembed.service
202Description=Stremio VsEmbed extractor addon
203LoadState=loaded
204ActiveState=active
205SubState=running
206FragmentPath=/home/fleeky/.config/systemd/user/stremio-vsembed.service
207UnitFileState=enabled
208ActiveEnterTimestamp=Sun 2026-10-18 21:51:11 EDT
209
210MainPID=0
211User=
212Id=systemd-remount-fs.service
213Description=systemd-remount-fs.service
214LoadState=not-found
215ActiveState=inactive
216SubState=dead
217FragmentPath=
218UnitFileState=
219ActiveEnterTimestamp=
220
221MainPID=1233
222User=
223Id=wireplumber.service
224Description=Multimedia Service Session Manager
225LoadState=loaded
226ActiveState=active
227SubState=running
228FragmentPath=/usr/lib/systemd/user/wireplumber.service
229UnitFileState=enabled
230ActiveEnterTimestamp=Sat 2026-08-08 20:26:09 EDT
231
232MainPID=0
233User=
234Id=xdg-desktop-portal-gtk.service
235Description=Portal service (GTK/GNOME implementation)
236LoadState=loaded
237ActiveState=failed
238SubState=failed
239FragmentPath=/usr/lib/systemd/user/xdg-desktop-portal-gtk.service
240UnitFileState=static
241ActiveEnterTimestamp=
242
243MainPID=0
244User=
245Id=xdg-desktop-portal.service
246Description=Portal service
247LoadState=loaded
248ActiveState=failed
249SubState=failed
250FragmentPath=/usr/lib/systemd/user/xdg-desktop-portal.service
251UnitFileState=static
252ActiveEnterTimestamp=
253
254MainPID=719662
255User=
256Id=xdg-document-portal.service
257Description=flatpak document portal service
258LoadState=loaded
259ActiveState=active
260SubState=running
261FragmentPath=/usr/lib/systemd/user/xdg-document-portal.service
262UnitFileState=static
263ActiveEnterTimestamp=Mon 2026-08-10 12:30:26 EDT
264
265MainPID=719657
266User=
267Id=xdg-permission-store.service
268Description=sandboxed app permission store
269LoadState=loaded
270ActiveState=active
271SubState=running
272FragmentPath=/usr/lib/systemd/user/xdg-permission-store.service
273UnitFileState=static
274ActiveEnterTimestamp=Mon 2026-08-10 12:30:25 EDT
test/parse.test.js +96 -0
……@@ -0,0 +1,96 @@
1// Parsers against output captured on stortron (2026-10-19).
2import test from 'brittle'
3import fs from 'bare-fs'
4import path from 'bare-path'
5import {
6 parseProcNetTcp, bindScope, parseSystemctlShow, isVendorUnit, parsePghStatus,
7 parseHttpHead, extractTitle, parseArp, parseAvahiBrowse, parseIpAddr
8} from '../lib/parse.js'
9
10const FIX = path.join(path.dirname(new URL(import.meta.url).pathname), 'fixtures')
11const fix = (name) => fs.readFileSync(path.join(FIX, name)).toString()
12
13test('proc/net/tcp: LISTEN rows only, addresses and owners decoded', (t) => {
14 const rows = parseProcNetTcp(fix('proc-net-tcp.txt'), 4)
15 const jellyfin = rows.find((r) => r.port === 8096)
16 t.is(jellyfin.addr, '0.0.0.0')
17 t.is(jellyfin.uid, 122)
18 t.is(rows.filter((r) => r.port === 8096).length, 1, 'established 8096 connections are not listeners')
19 const host = rows.find((r) => r.port === 4042)
20 t.is(host.addr, '127.0.0.1')
21 t.is(host.uid, 1000)
22})
23
24test('proc/net/tcp6: any and loopback addresses', (t) => {
25 const rows = parseProcNetTcp(fix('proc-net-tcp6.txt'), 6)
26 t.ok(rows.length > 0)
27 const https = rows.find((r) => r.port === 443)
28 t.is(https.addr, '::')
29 t.is(bindScope(https.addr), 'all')
30 t.ok(rows.some((r) => r.addr === '::1' && r.port === 631), 'cups on ::1')
31})
32
33test('bindScope', (t) => {
34 t.is(bindScope('0.0.0.0'), 'all')
35 t.is(bindScope('127.0.0.53'), 'local')
36 t.is(bindScope('::1'), 'local')
37 t.is(bindScope('192.168.50.215'), 'addr')
38})
39
40test('systemctl show: blocks become units; vendor vs hand-installed', (t) => {
41 const units = parseSystemctlShow(fix('systemctl-show.txt'))
42 const j = units.find((u) => u.Id === 'jellyfin.service')
43 t.is(j.ActiveState, 'active')
44 t.is(j.User, 'jellyfin')
45 t.ok(isVendorUnit(j))
46 const user = parseSystemctlShow(fix('systemctl-user-show.txt'))
47 const k = user.find((u) => u.Id === 'kodi-repo.service')
48 t.is(k.ActiveState, 'active')
49 t.absent(isVendorUnit(k), 'a unit in ~/.config/systemd is hand-installed')
50})
51
52test('pgh app status table', (t) => {
53 const apps = parsePghStatus(fix('pgh-app-status.txt'))
54 t.is(apps.length, 18)
55 const c = apps.find((a) => a.name === 'cthuloid-web')
56 t.is(c.running, true)
57 t.is(c.pid, 1945148)
58 t.is(c.autostart, true)
59 t.is(c.version, '0.2.1')
60 const w = apps.find((a) => a.name === 'favela-web')
61 t.is(w.running, false)
62 t.is(w.autostart, false)
63 t.is(apps.find((a) => a.name === 'favela-gateway').lastExitCode, 75)
64 t.is(apps.find((a) => a.name === 'hyper-tps').lastExitCode, null, 'never started: no exit code')
65})
66
67test('HTTP head: status, redirect, title; non-HTTP is null', (t) => {
68 const h = parseHttpHead('HTTP/1.1 302 Found\r\nLocation: /login\r\nServer: Kestrel\r\n\r\n')
69 t.is(h.code, 302)
70 t.is(h.location, '/login')
71 t.is(h.server, 'Kestrel')
72 const p = parseHttpHead('HTTP/1.0 200 OK\r\n\r\n<html><head><title>\n repos &middot; pear-git-v3 </title>')
73 t.is(p.title, 'repos · pear-git-v3')
74 t.is(parseHttpHead('SSH-2.0-OpenSSH_9.6p1\r\n'), null)
75 t.is(extractTitle('<p>no title</p>'), null)
76})
77
78test('arp: complete entries only', (t) => {
79 const arp = parseArp(fix('proc-net-arp.txt'))
80 t.ok(arp.length > 5)
81 t.ok(arp.every((a) => /^[0-9a-f:]{17}$/.test(a.mac) && a.mac !== '00:00:00:00:00:00'))
82 t.ok(arp.some((a) => a.ip === '192.168.50.228'))
83 t.is(parseArp('IP address HW type Flags HW address Mask Device\n192.168.50.9 0x1 0x0 00:00:00:00:00:00 * enp2s0\n').length, 0)
84})
85
86test('avahi browse: resolved IPv4 services', (t) => {
87 const svcs = parseAvahiBrowse(fix('avahi-browse.txt'))
88 t.ok(svcs.length > 0)
89 t.ok(svcs.every((s) => /^\d+\.\d+\.\d+\.\d+$/.test(s.ip) && Number.isInteger(s.port)))
90 t.ok(svcs.some((s) => s.type === '_spotify-connect._tcp' && s.ip === '192.168.50.111'))
91})
92
93test('ip addr: LAN and tunnel addresses', (t) => {
94 const a = parseIpAddr(fix('ip-addr.txt'))
95 t.alike(a.map((x) => [x.dev, x.ip, x.prefix]), [['enp2s0', '192.168.50.215', 24], ['tunnel0', '10.22.0.2', 16]])
96})