Updates
This commit is contained in:
+3
-2
@@ -15,8 +15,9 @@ PEARDATA_DEFAULT_ROLE=viewer
|
|||||||
# PEARDATA_DATA_DIR=./data
|
# PEARDATA_DATA_DIR=./data
|
||||||
# PEARDATA_RATE_LIMIT_RPM=120
|
# PEARDATA_RATE_LIMIT_RPM=120
|
||||||
# PEARDATA_MAX_RECONNECT=20
|
# PEARDATA_MAX_RECONNECT=20
|
||||||
# Host journal for Logs tab / GET /api/v*/logs?source=journal (Linux; needs journal ACL)
|
# Host journal for Logs tab / GET /api/v*/logs?source=journal (on by default on Linux)
|
||||||
# PEARDATA_JOURNAL=0
|
# Installer adds peardata to systemd-journal. Set to 0 to disable.
|
||||||
|
# PEARDATA_JOURNAL=1
|
||||||
|
|
||||||
# ── Metrics pipeline ─────────────────────────────────────────
|
# ── Metrics pipeline ─────────────────────────────────────────
|
||||||
# PEARDATA_SAMPLE_MS=1000
|
# PEARDATA_SAMPLE_MS=1000
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ TimeoutStartSec=30
|
|||||||
TimeoutStopSec=30
|
TimeoutStopSec=30
|
||||||
User=peardata
|
User=peardata
|
||||||
Group=peardata
|
Group=peardata
|
||||||
|
# Host journal for Logs tab (journalctl). Installer also adds peardata to this group.
|
||||||
|
SupplementaryGroups=systemd-journal
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
|
# Journal on by default; override in .env with PEARDATA_JOURNAL=0 to disable
|
||||||
|
Environment=PEARDATA_JOURNAL=1
|
||||||
EnvironmentFile=-/opt/peardata/.env
|
EnvironmentFile=-/opt/peardata/.env
|
||||||
# Optional host journal in Logs tab: set PEARDATA_JOURNAL=1 in .env and uncomment:
|
|
||||||
# SupplementaryGroups=systemd-journal
|
|
||||||
# Identity + HyperDB / metric buffers
|
# Identity + HyperDB / metric buffers
|
||||||
ReadWritePaths=/opt/peardata
|
ReadWritePaths=/opt/peardata
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Treat `SERVER_SEED` like a root password. Prefer `pd1.` invites for operators.
|
|||||||
| `PEARDATA_STORAGE` | — | Electron/Pear storage dir override (bookmarks prefer `Pear.config.storage`) |
|
| `PEARDATA_STORAGE` | — | Electron/Pear storage dir override (bookmarks prefer `Pear.config.storage`) |
|
||||||
| `PEARDATA_RATE_LIMIT_RPM` | `120` | Per-peer RPC requests per minute |
|
| `PEARDATA_RATE_LIMIT_RPM` | `120` | Per-peer RPC requests per minute |
|
||||||
| `PEARDATA_MAX_RECONNECT` | `20` | Client manager reconnect attempts per peer |
|
| `PEARDATA_MAX_RECONNECT` | `20` | Client manager reconnect attempts per peer |
|
||||||
| `PEARDATA_JOURNAL` | off | `1` enables host `journalctl` for Logs / `queryLogs` (Linux; needs journal ACL) |
|
| `PEARDATA_JOURNAL` | **on** | Host `journalctl` for Logs / `queryLogs` (Linux). Set `0` to disable. Installer adds `peardata` to `systemd-journal` and sets unit `SupplementaryGroups` |
|
||||||
|
|
||||||
```
|
```
|
||||||
data/
|
data/
|
||||||
|
|||||||
@@ -110,12 +110,14 @@ npm start
|
|||||||
|
|
||||||
## systemd (Linux)
|
## systemd (Linux)
|
||||||
|
|
||||||
Prefer the [one-line installer](#one-line-installer-recommended) (`--server`). It installs the Bare binary and writes `peardata.service`.
|
Prefer the [one-line installer](#one-line-installer-recommended) (`--server`). It installs the Bare binary, writes `peardata.service`, adds `peardata` to **`systemd-journal`**, and enables host journal for the Logs tab (`PEARDATA_JOURNAL=1`).
|
||||||
|
|
||||||
Manual (matches installer layout):
|
Manual (matches installer layout):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# After placing peardata-server + .env under /opt/peardata
|
# After placing peardata-server + .env under /opt/peardata
|
||||||
|
sudo useradd --system --home /opt/peardata --shell /usr/sbin/nologin peardata # if needed
|
||||||
|
sudo usermod -aG systemd-journal peardata
|
||||||
sudo cp deploy/peardata.service /etc/systemd/system/
|
sudo cp deploy/peardata.service /etc/systemd/system/
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now peardata
|
sudo systemctl enable --now peardata
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ REST parity: `GET /api/v*/weights`. Engine: `server/services/weights.js`.
|
|||||||
| `priority` / `unit` | Journal filters |
|
| `priority` / `unit` | Journal filters |
|
||||||
| `limit` / `cursor` | Cap (≤2000) + pagination offset |
|
| `limit` / `cursor` | Cap (≤2000) + pagination offset |
|
||||||
|
|
||||||
Journal requires `PEARDATA_JOURNAL=1` on Linux. REST: `GET /api/v*/logs`. Engine: `server/services/logs.js`. UI: [user-guide/logs.md](../user-guide/logs.md).
|
Journal is **enabled by default** on Linux (`PEARDATA_JOURNAL=0` to disable). Installer grants `systemd-journal` to the agent user. REST: `GET /api/v*/logs`. Engine: `server/services/logs.js`. UI: [user-guide/logs.md](../user-guide/logs.md).
|
||||||
|
|
||||||
### Live subscriptions
|
### Live subscriptions
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -47,7 +47,7 @@
|
|||||||
- [ ] Run under systemd with `ProtectSystem` / `NoNewPrivileges` (see `deploy/`)
|
- [ ] Run under systemd with `ProtectSystem` / `NoNewPrivileges` (see `deploy/`)
|
||||||
- [ ] Do not embed seed in frontend builds, CI logs, or crash reports
|
- [ ] Do not embed seed in frontend builds, CI logs, or crash reports
|
||||||
- [ ] Review `LOG_LEVEL=debug` before production (avoid verbose auth noise)
|
- [ ] Review `LOG_LEVEL=debug` before production (avoid verbose auth noise)
|
||||||
- [ ] Keep `PEARDATA_JOURNAL` off unless operators need host logs; journal access is **admin-only** over P2P and can expose secrets from other units — add `SupplementaryGroups=systemd-journal` only when enabling
|
- [ ] Journal is **on by default** (admin-only over P2P); it can expose secrets from other units — set `PEARDATA_JOURNAL=0` if you do not want host log access
|
||||||
- [ ] Keep Pear / dependency updates current (`npm outdated`)
|
- [ ] Keep Pear / dependency updates current (`npm outdated`)
|
||||||
|
|
||||||
## Threat notes
|
## Threat notes
|
||||||
@@ -86,7 +86,7 @@ Implementation: `shared/crypto-auth.js`.
|
|||||||
| `.env` | Critical | Never commit; backup offline |
|
| `.env` | Critical | Never commit; backup offline |
|
||||||
| `data/peer-policy.json` | High | Contains roles & JTIs |
|
| `data/peer-policy.json` | High | Contains roles & JTIs |
|
||||||
| `data/audit.log` | Medium | Peer activity metadata; readable via Logs/audit (admin) |
|
| `data/audit.log` | Medium | Peer activity metadata; readable via Logs/audit (admin) |
|
||||||
| Host journal (`PEARDATA_JOURNAL`) | High | May include secrets from other units; admin-only; off by default |
|
| Host journal (`PEARDATA_JOURNAL`) | High | May include secrets from other units; admin-only; **on by default** (disable with `=0`) |
|
||||||
| `identity.json` | High for that user | Per-machine client secret |
|
| `identity.json` | High for that user | Per-machine client secret |
|
||||||
| Release tarballs | Low | Source only; no secrets |
|
| Release tarballs | Low | Source only; no secrets |
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ When adding an RPC method:
|
|||||||
| Metric Correlations | Charts → Correlate → brush ≥15s → Find Correlations → wall filters; Clear restores; Related ⇢ still works |
|
| Metric Correlations | Charts → Correlate → brush ≥15s → Find Correlations → wall filters; Clear restores; Related ⇢ still works |
|
||||||
| Charts Filters | Filters closed → full-width wall; `/` opens search; chip when filter active; Esc closes panel |
|
| Charts Filters | Filters closed → full-width wall; `/` opens search; chip when filter active; Esc closes panel |
|
||||||
| Weights REST | `curl -sG 'http://127.0.0.1:19999/api/v3/weights' --data-urlencode 'method=volume' --data-urlencode 'after=-60' --data-urlencode 'before=0'` |
|
| Weights REST | `curl -sG 'http://127.0.0.1:19999/api/v3/weights' --data-urlencode 'method=volume' --data-urlencode 'after=-60' --data-urlencode 'before=0'` |
|
||||||
| Logs | Logs tab → Anomalies search; admin → Audit; with `PEARDATA_JOURNAL=1` → Journal |
|
| Logs | Logs tab defaults to Journal (admin); Anomalies for viewers; Audit admin-only |
|
||||||
| Logs REST | `curl -sG 'http://127.0.0.1:19999/api/v3/logs' --data-urlencode 'source=anomaly' --data-urlencode 'limit=20'` |
|
| Logs REST | `curl -sG 'http://127.0.0.1:19999/api/v3/logs' --data-urlencode 'source=anomaly' --data-urlencode 'limit=20'` |
|
||||||
|
|
||||||
## Soak test
|
## Soak test
|
||||||
|
|||||||
+2
-2
@@ -278,9 +278,9 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="logs-toolbar">
|
<div class="logs-toolbar">
|
||||||
<div id="logs-sources" class="logs-sources" role="tablist" aria-label="Log source">
|
<div id="logs-sources" class="logs-sources" role="tablist" aria-label="Log source">
|
||||||
<button type="button" class="metrics-tf active" data-log-source="anomaly" role="tab" aria-selected="true">Anomalies</button>
|
<button type="button" class="metrics-tf active" data-log-source="journal" role="tab" aria-selected="true" title="Admin only · host journal (default)">Journal</button>
|
||||||
|
<button type="button" class="metrics-tf" data-log-source="anomaly" role="tab" aria-selected="false">Anomalies</button>
|
||||||
<button type="button" class="metrics-tf" data-log-source="audit" role="tab" aria-selected="false" title="Admin only">Audit</button>
|
<button type="button" class="metrics-tf" data-log-source="audit" role="tab" aria-selected="false" title="Admin only">Audit</button>
|
||||||
<button type="button" class="metrics-tf" data-log-source="journal" role="tab" aria-selected="false" title="Admin only · PEARDATA_JOURNAL=1">Journal</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="logs-search-row">
|
<div class="logs-search-row">
|
||||||
<input id="logs-q" type="search" placeholder="Search message…" autocomplete="off" />
|
<input id="logs-q" type="search" placeholder="Search message…" autocomplete="off" />
|
||||||
|
|||||||
@@ -321,9 +321,33 @@ install_server() {
|
|||||||
fi
|
fi
|
||||||
id peardata >/dev/null 2>&1 || die "peardata user was not created"
|
id peardata >/dev/null 2>&1 || die "peardata user was not created"
|
||||||
|
|
||||||
|
# Host journal access for Logs tab (journalctl as peardata)
|
||||||
|
if getent group systemd-journal >/dev/null 2>&1; then
|
||||||
|
log "Adding peardata to systemd-journal for host log access…"
|
||||||
|
if command -v usermod >/dev/null 2>&1; then
|
||||||
|
run_root usermod -aG systemd-journal peardata || warn "usermod -aG systemd-journal peardata failed"
|
||||||
|
elif command -v gpasswd >/dev/null 2>&1; then
|
||||||
|
run_root gpasswd -a peardata systemd-journal || warn "gpasswd -a peardata systemd-journal failed"
|
||||||
|
else
|
||||||
|
warn "Could not add peardata to systemd-journal (no usermod/gpasswd)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Group systemd-journal not found — host Journal in Logs may be unavailable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure journal is enabled in .env (default on; operators may set PEARDATA_JOURNAL=0)
|
||||||
|
if ! run_root grep -qE '^PEARDATA_JOURNAL=' "${SERVER_DIR}/.env" 2>/dev/null; then
|
||||||
|
log "Enabling PEARDATA_JOURNAL=1 in ${SERVER_DIR}/.env"
|
||||||
|
run_root sh -c "printf '\\n# Host journal for Logs tab (set to 0 to disable)\\nPEARDATA_JOURNAL=1\\n' >> '${SERVER_DIR}/.env'"
|
||||||
|
fi
|
||||||
|
|
||||||
run_root chown -R peardata:peardata "$SERVER_DIR" 2>/dev/null || run_root chown -R peardata "$SERVER_DIR"
|
run_root chown -R peardata:peardata "$SERVER_DIR" 2>/dev/null || run_root chown -R peardata "$SERVER_DIR"
|
||||||
|
|
||||||
unit_path="/etc/systemd/system/peardata.service"
|
unit_path="/etc/systemd/system/peardata.service"
|
||||||
|
local journal_group_line=""
|
||||||
|
if getent group systemd-journal >/dev/null 2>&1; then
|
||||||
|
journal_group_line="SupplementaryGroups=systemd-journal"
|
||||||
|
fi
|
||||||
log "Writing ${unit_path}…"
|
log "Writing ${unit_path}…"
|
||||||
run_root tee "$unit_path" >/dev/null <<EOF
|
run_root tee "$unit_path" >/dev/null <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -342,9 +366,11 @@ TimeoutStartSec=30
|
|||||||
TimeoutStopSec=30
|
TimeoutStopSec=30
|
||||||
User=peardata
|
User=peardata
|
||||||
Group=peardata
|
Group=peardata
|
||||||
|
${journal_group_line}
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PEARDATA_JOURNAL=1
|
||||||
EnvironmentFile=-${SERVER_DIR}/.env
|
EnvironmentFile=-${SERVER_DIR}/.env
|
||||||
# Identity + HyperDB / ring buffers
|
# Identity + HyperDB / ring buffers
|
||||||
ReadWritePaths=${SERVER_DIR}
|
ReadWritePaths=${SERVER_DIR}
|
||||||
@@ -380,6 +406,7 @@ ${C_BOLD}Server installed${C_RESET}
|
|||||||
Config: ${SERVER_DIR}/.env ${C_BOLD}← check this file for your keys${C_RESET}
|
Config: ${SERVER_DIR}/.env ${C_BOLD}← check this file for your keys${C_RESET}
|
||||||
Data: ${SERVER_DIR}/data
|
Data: ${SERVER_DIR}/data
|
||||||
Service: peardata.service
|
Service: peardata.service
|
||||||
|
Journal: peardata ∈ systemd-journal · PEARDATA_JOURNAL=1 (Logs tab)
|
||||||
REST: http://127.0.0.1:19999/api/v3/info (localhost by default)
|
REST: http://127.0.0.1:19999/api/v3/info (localhost by default)
|
||||||
|
|
||||||
${C_BOLD}Keys (written on first successful start)${C_RESET}
|
${C_BOLD}Keys (written on first successful start)${C_RESET}
|
||||||
|
|||||||
@@ -209,9 +209,13 @@ export function buildJournalArgv(opts = {}) {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Host journal is on by default; set PEARDATA_JOURNAL=0 to disable. */
|
||||||
export function isJournalEnabled() {
|
export function isJournalEnabled() {
|
||||||
const v = String(process.env.PEARDATA_JOURNAL || '').trim().toLowerCase()
|
const raw = process.env.PEARDATA_JOURNAL
|
||||||
return v === '1' || v === 'true' || v === 'yes' || v === 'on'
|
if (raw == null || String(raw).trim() === '') return true
|
||||||
|
const v = String(raw).trim().toLowerCase()
|
||||||
|
if (v === '0' || v === 'false' || v === 'no' || v === 'off') return false
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,7 +311,7 @@ export async function queryLogs(args = {}, deps = {}) {
|
|||||||
ok: false,
|
ok: false,
|
||||||
source,
|
source,
|
||||||
error: 'journal_disabled',
|
error: 'journal_disabled',
|
||||||
hint: 'Set PEARDATA_JOURNAL=1 and ensure the agent user can read the journal (e.g. SupplementaryGroups=systemd-journal)',
|
hint: 'Journal disabled (PEARDATA_JOURNAL=0). Re-enable and ensure the agent user is in systemd-journal (installer / SupplementaryGroups)',
|
||||||
entries: [],
|
entries: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -160,9 +160,12 @@ test('queryLogs audit from temp file', async (t) => {
|
|||||||
t.ok(res.entries[0].message.includes('runJob'))
|
t.ok(res.entries[0].message.includes('runJob'))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('queryLogs journal disabled / unsupported', async (t) => {
|
test('queryLogs journal default on / disable / unsupported', async (t) => {
|
||||||
const prev = process.env.PEARDATA_JOURNAL
|
const prev = process.env.PEARDATA_JOURNAL
|
||||||
delete process.env.PEARDATA_JOURNAL
|
delete process.env.PEARDATA_JOURNAL
|
||||||
|
t.ok(isJournalEnabled(), 'journal enabled by default')
|
||||||
|
|
||||||
|
process.env.PEARDATA_JOURNAL = '0'
|
||||||
t.absent(isJournalEnabled())
|
t.absent(isJournalEnabled())
|
||||||
const off = await queryLogs({ source: 'journal', role: Roles.admin })
|
const off = await queryLogs({ source: 'journal', role: Roles.admin })
|
||||||
t.absent(off.ok)
|
t.absent(off.ok)
|
||||||
@@ -175,7 +178,6 @@ test('queryLogs journal disabled / unsupported', async (t) => {
|
|||||||
t.absent(uns.ok)
|
t.absent(uns.ok)
|
||||||
t.is(uns.error, 'unsupported')
|
t.is(uns.error, 'unsupported')
|
||||||
} else {
|
} else {
|
||||||
// On Linux with journal enabled, inject a fake spawn that fails cleanly
|
|
||||||
const res = await queryLogs(
|
const res = await queryLogs(
|
||||||
{ source: 'journal', role: Roles.admin, limit: 5 },
|
{ source: 'journal', role: Roles.admin, limit: 5 },
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-1
@@ -24,7 +24,8 @@ import { Roles, roleAllows } from '../shared/protocol.js'
|
|||||||
*/
|
*/
|
||||||
export function createLogsView(opts) {
|
export function createLogsView(opts) {
|
||||||
const state = {
|
const state = {
|
||||||
source: 'anomaly',
|
/** Prefer host journal; falls back to anomaly when not admin */
|
||||||
|
source: 'journal',
|
||||||
range: '1h',
|
range: '1h',
|
||||||
loading: false,
|
loading: false,
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -8,7 +8,7 @@ The **Logs** tab searches recent events from the active agent: anomalies, the ag
|
|||||||
|--------|----------------|---------------|
|
|--------|----------------|---------------|
|
||||||
| **Anomalies** | Viewer+ | Threshold / z-score events (same family as Alerts) |
|
| **Anomalies** | Viewer+ | Threshold / z-score events (same family as Alerts) |
|
||||||
| **Audit** | Admin | Mutating RPC trail from `data/audit.log` |
|
| **Audit** | Admin | Mutating RPC trail from `data/audit.log` |
|
||||||
| **Journal** | Admin | Host `journalctl` lines (Linux, opt-in) |
|
| **Journal** (default) | Admin | Host `journalctl` lines (Linux; on by default) |
|
||||||
|
|
||||||
Audit and Journal tabs are disabled for non-admin sessions (tooltip explains why).
|
Audit and Journal tabs are disabled for non-admin sessions (tooltip explains why).
|
||||||
|
|
||||||
@@ -22,15 +22,15 @@ Audit and Journal tabs are disabled for non-admin sessions (tooltip explains why
|
|||||||
|
|
||||||
Results show severity, message (monospace), time, and unit. Anomaly rows offer **Show** / **Correlate** like the Alerts tab.
|
Results show severity, message (monospace), time, and unit. Anomaly rows offer **Show** / **Correlate** like the Alerts tab.
|
||||||
|
|
||||||
## Enabling host journal
|
## Host journal (default on)
|
||||||
|
|
||||||
Default is **off** (no `journalctl` spawn).
|
The one-line / `--server` installer:
|
||||||
|
|
||||||
1. Set `PEARDATA_JOURNAL=1` on the agent.
|
1. Adds the `peardata` user to the **`systemd-journal`** group
|
||||||
2. Ensure the agent user can read the journal (often `SupplementaryGroups=systemd-journal` in the systemd unit).
|
2. Sets unit **`SupplementaryGroups=systemd-journal`**
|
||||||
3. Restart the agent.
|
3. Writes **`PEARDATA_JOURNAL=1`** into `/opt/peardata/.env` (and the unit Environment)
|
||||||
|
|
||||||
Without this, Journal returns a clear “disabled” hint. Non-Linux agents always report journal as unsupported.
|
To disable: set `PEARDATA_JOURNAL=0` in `.env` and restart. Non-Linux agents report journal as unsupported.
|
||||||
|
|
||||||
See [CONFIGURATION](../docs/CONFIGURATION.md) and [SECURITY](../docs/SECURITY.md).
|
See [CONFIGURATION](../docs/CONFIGURATION.md) and [SECURITY](../docs/SECURITY.md).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user