🐤 Sync 2026-09-18 23:02:02

This commit is contained in:
github-actions[bot] committed 2026-09-18 23:02:02 +08:00
1 parent d4bee34c32
commit 3587c9c61f
5 files changed
+363 -385

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
LUCI_PKGARCH:=all
PKG_VERSION:=0.1.44
PKG_RELEASE:=24
PKG_RELEASE:=25
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
ifdef SOURCE_DATE_EPOCH
@@ -46,6 +46,21 @@ function persistConsentDismissed() {
}
}
function enableLoggingButton(state, callbacks) {
return E(
'button',
{
'class': 'cbi-button cbi-button-action',
'type': 'button',
'disabled': state.loggingBusy ? '' : null,
'click': function () {
callbacks.onEnable();
}
},
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
);
}
function blockerCode(state) {
const blockers = (state.loggingStatus && state.loggingStatus.blockers) || [];
if (blockers.indexOf('no_wan_zone') >= 0) return 'no_wan_zone';
@@ -164,19 +179,7 @@ function buildConsentPanel(state, callbacks) {
])
]),
E('p', { 'class': 'fwlive-consent-actions' }, [
E(
'button',
{
'class': 'cbi-button cbi-button-action',
'type': 'button',
'disabled': state.loggingBusy ? '' : null,
'click': function () {
persistConsentDismissed();
callbacks.onEnable();
}
},
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
),
enableLoggingButton(state, callbacks),
' ',
E(
'button',
@@ -289,19 +292,7 @@ function buildEmptyStateNodes(state, callbacks) {
);
nodes.push(
E('p', {}, [
E(
'button',
{
'class': 'cbi-button cbi-button-action',
'type': 'button',
'disabled': state.loggingBusy ? '' : null,
'click': function () {
persistConsentDismissed();
callbacks.onEnable();
}
},
[state.loggingBusy ? _('Enabling…') : _('Enable WAN drop/reject logging')]
),
enableLoggingButton(state, callbacks),
' ',
links.firewallZonesLink(_('I’ll configure this under Network → Firewall'))
])
@@ -687,40 +687,26 @@ return view.extend({
this.renderRows(true);
},
async handleEnableLogging() {
async runLoggingToggle(opts) {
if (this.loggingBusy) return;
this.loggingBusy = true;
this.loggingNotice = '';
this.updateEmptyStateUi();
this.updateLoggingToolbarUi();
opts.initialUi();
try {
const res = await callFwliveEnableLogging();
const res = await opts.call();
if (!res || !res.ok) {
if (res && res.error === 'nf_log_missing')
this.loggingNotice = _(
'Cannot enable logging until kernel log modules are installed.'
);
else if (res && res.error === 'firewall_changes_pending')
this.loggingNotice = _(
'Another change is staged for the firewall; apply or revert it first.'
);
else this.loggingNotice = _('Could not enable logging.');
this.loggingNotice = opts.failureNotice(res);
await this.loadLoggingStatus();
return;
}
if (res.changed)
this.loggingNotice = _(
'WAN drop/reject logging is on. Blocked inbound traffic should appear here as it happens — not normal LAN browsing.'
);
else this.loggingNotice = _('WAN logging is already enabled.');
logging.persistConsentDismissed();
this.loggingNotice = opts.successNotice(res);
if (opts.onSuccess) opts.onSuccess(res);
await this.loadLoggingStatus();
} catch (e) {
this.loggingNotice = _('Administrator access is required to enable logging.');
this.loggingNotice = opts.catchNotice();
await this.loadLoggingStatus();
} finally {
this.loggingBusy = false;
@@ -729,35 +715,47 @@ return view.extend({
}
},
async handleDisableLogging() {
if (this.loggingBusy) return;
this.loggingBusy = true;
this.loggingNotice = '';
this.updateLoggingToolbarUi();
try {
const res = await callFwliveDisableLogging();
if (!res || !res.ok) {
async handleEnableLogging() {
return this.runLoggingToggle({
call: () => callFwliveEnableLogging(),
initialUi: () => {
this.updateEmptyStateUi();
this.updateLoggingToolbarUi();
},
failureNotice: (res) => {
if (res && res.error === 'nf_log_missing')
return _('Cannot enable logging until kernel log modules are installed.');
if (res && res.error === 'firewall_changes_pending')
this.loggingNotice = _(
return _(
'Another change is staged for the firewall; apply or revert it first.'
);
else this.loggingNotice = _('Could not disable logging.');
await this.loadLoggingStatus();
return;
}
return _('Could not enable logging.');
},
successNotice: (res) =>
res.changed
? _(
'WAN drop/reject logging is on. Blocked inbound traffic should appear here as it happens — not normal LAN browsing.'
)
: _('WAN logging is already enabled.'),
onSuccess: () => logging.persistConsentDismissed(),
catchNotice: () => _('Administrator access is required to enable logging.')
});
},
if (res.changed) this.loggingNotice = _('WAN drop/reject logging is off.');
await this.loadLoggingStatus();
} catch (e) {
this.loggingNotice = _('Administrator access is required to disable logging.');
await this.loadLoggingStatus();
} finally {
this.loggingBusy = false;
this.updateEmptyStateUi();
this.updateLoggingToolbarUi();
}
async handleDisableLogging() {
return this.runLoggingToggle({
call: () => callFwliveDisableLogging(),
initialUi: () => this.updateLoggingToolbarUi(),
failureNotice: (res) => {
if (res && res.error === 'firewall_changes_pending')
return _(
'Another change is staged for the firewall; apply or revert it first.'
);
return _('Could not disable logging.');
},
successNotice: (res) => (res.changed ? _('WAN drop/reject logging is off.') : ''),
catchNotice: () => _('Administrator access is required to disable logging.')
});
},
shouldShowLoggingConsent() {
@@ -889,6 +887,13 @@ return view.extend({
/* Caller must discard stale epochs before this synchronous application.
* This updates transport/adaptive state, summary/banner UI, rows, and buffer. */
failPollReply(rtt) {
this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true);
this.updateAdaptiveBanner();
},
applyPollReply(poll, context) {
const reply = poll.reply;
const rtt = poll.rtt;
@@ -899,25 +904,16 @@ return view.extend({
this.lastPollEffectiveLimit = null;
if (!reply || typeof reply !== 'object' || Array.isArray(reply)) {
this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true);
this.updateAdaptiveBanner();
this.failPollReply(rtt);
return;
}
if (reply.error) {
this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true);
this.updateAdaptiveBanner();
this.failPollReply(rtt);
return;
}
const raw = reply.log;
if (!Array.isArray(raw)) {
this.lastPollError = true;
this.fillingBuffer = false;
this.notePollRtt(rtt, true);
this.updateAdaptiveBanner();
this.failPollReply(rtt);
return;
}
@@ -1173,11 +1169,7 @@ return view.extend({
}
},
updateAdaptiveBanner() {
const el = document.getElementById('fwlive-adaptive');
if (!el) return;
if (!el.style) el.style = { display: '' };
adaptiveBannerParts() {
const parts = [];
const mode = this.fetchMode === 'manual' ? _('Manual') : _('Auto');
if (this.lastPollRequestedLines !== null && this.lastPollReturnedMessages !== null) {
@@ -1218,6 +1210,15 @@ return view.extend({
else if (this.serverTruncated) parts.push(_('Server truncated this poll (adaptive cap).'));
if (this.resolveLoadShed)
parts.push(_('Hostname resolve paused while the router is under load.'));
return parts;
},
updateAdaptiveBanner() {
const el = document.getElementById('fwlive-adaptive');
if (!el) return;
if (!el.style) el.style = { display: '' };
const parts = this.adaptiveBannerParts();
if (parts.length) {
el.style.display = 'block';
@@ -1603,6 +1604,18 @@ return view.extend({
return Array.from(ips);
},
isLoadShedReply(res) {
return res && typeof res === 'object' && res.disabled === 'load';
},
resolveNamesFromReply(res) {
return res && typeof res === 'object' && res.names && typeof res.names === 'object'
? res.names
: res && typeof res === 'object' && !Array.isArray(res)
? res
: {};
},
async resolveHostnamesForEntries(entries) {
if (!this.showHostnames || this.resolveInFlight) return;
@@ -1633,7 +1646,7 @@ return view.extend({
const res = await callFwliveResolve({ addresses: need });
if (gen !== this.resolveGeneration) return;
if (res && typeof res === 'object' && res.disabled === 'load') {
if (this.isLoadShedReply(res)) {
this.resolveLoadShed = true;
this.resolveShedUntil = Date.now() + 60000;
this.updateAdaptiveBanner();
@@ -1649,12 +1662,7 @@ return view.extend({
if (res && typeof res === 'object' && typeof res.error === 'string' && res.error)
return;
/* Full reply: names map under .names; legacy expect-unwrap was the map. */
const names =
res && typeof res === 'object' && res.names && typeof res.names === 'object'
? res.names
: res && typeof res === 'object' && !Array.isArray(res)
? res
: {};
const names = this.resolveNamesFromReply(res);
let updated = false;
for (let i = 0; i < need.length; i++) {
@@ -2008,6 +2016,168 @@ return view.extend({
});
},
renderDisplayDrawer() {
return E('div', { 'id': 'fwlive-display-drawer', 'class': 'fwlive-display-bar' }, [
E('span', { 'class': 'fwlive-display-bar-label' }, [_('Display options')]),
E('div', { 'class': 'fwlive-display-controls' }, [
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-limit' }, [
_('Limit'),
E(
'select',
{
'id': 'fwlive-limit',
'class': 'cbi-input-select'
},
this.limitSelectOptions()
)
]),
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-fetch-mode' }, [
_('Fetch budget'),
E(
'select',
{
'id': 'fwlive-fetch-mode',
'class': 'cbi-input-select',
'aria-controls': 'fwlive-manual-lines'
},
this.fetchModeOptions()
)
]),
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-manual-lines' }, [
_('Maximum raw lines'),
E(
'select',
{
'id': 'fwlive-manual-lines',
'class': 'cbi-input-select',
'title': _('Manual still uses server protection and poll cadence')
},
this.manualFetchLinesOptions()
)
]),
E('label', { 'class': 'fwlive-display-ctl' }, [
E('input', {
'id': 'fwlive-row-tint-toggle',
'type': 'checkbox',
'title': _('Show pass/deny row background colors')
}),
_('Row tint')
]),
E(
'label',
{
'id': 'fwlive-row-tint-palette-wrap',
'class': 'fwlive-display-ctl',
'for': 'fwlive-row-tint'
},
[
_('Palette'),
E(
'select',
{
'id': 'fwlive-row-tint',
'class': 'cbi-input-select',
'title': _('Classic uses green/red; Accessible uses teal/orange')
},
this.rowTintPaletteOptions()
)
]
),
E('label', { 'class': 'fwlive-display-ctl' }, [
E('input', {
'id': 'fwlive-show-hostnames',
'type': 'checkbox'
}),
_('Show hostnames')
])
])
]);
},
renderHelpNodes() {
return E('div', { 'class': 'fwlive-help-row' }, [
E('details', { 'id': 'fwlive-help', 'class': 'fwlive-help' }, [
E('summary', {}, [_('Help')]),
E('ul', {}, [
E('li', {}, [
_(
'The table updates automatically when your firewall logs traffic. Use Pause if it moves too fast.'
)
]),
E('li', {}, [
_(
'Enable logging turns on WAN zone drop/reject logging only (same as Network → Firewall). It does not add rules or log normal LAN browsing.'
)
]),
E('li', {}, [
_('Display options on the bar set Limit, row tint, palette, and hostnames.')
]),
E('li', {}, [
_(
'Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum.'
)
]),
E('li', {}, [
_(
'Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval.'
)
]),
E('li', {}, [
_(
'For a responsive table on a weak device, keep Limit at 250 rows or below. The weak-device cap affects rendered rows; the buffer can still retain more.'
)
]),
E('li', {}, [
_(
'Switching tabs pauses polling; returning performs one catch-up poll. Hostnames are off by default because lookups add work.'
)
]),
E('li', {}, [
_(
'The router log ring may evict older events before fwlive reads them. fwlive cannot recover evicted entries or change forwarding behavior.'
)
]),
E('li', {}, [
_(
'The rate shown for WAN logging is the firewall zone log_limit. OpenWrt defaults to 10/minute when no explicit limit is configured; fwlive does not impose this cap.'
)
]),
E('li', { 'id': 'fwlive-manual-test' }, []),
E('li', {}, [
_(
'Click a row (Time or other non-link cells) to see the full log line (Simple view).'
)
]),
E('li', {}, [
_(
'Click an IP, action, or protocol to filter; use the Protocol menu (or ≠ on a chip) to exclude.'
)
]),
E('li', {}, [
_(
'Row tint shows pass/deny row backgrounds when checked. Choose Classic (green/red, default) or Accessible (teal/orange). Action text stays colored either way.'
)
]),
E('li', {}, [_('Use Detail for all columns (flags, length, raw message).')]),
E('li', {}, [
_(
'If Row tint looks missing, the active LuCI theme may omit success/error or info/warn CSS variables; fwlive falls back to local colors (air-gapped, no data leaves the device).'
)
])
])
]),
E(
'span',
{
'id': 'fwlive-build',
'class': 'fwlive-build',
'title': 'luci-app-fwlive'
},
['v' + constants.APP_VERSION]
)
]);
},
render() {
/* LuCI may finish the load/render/addFooter sequence after pagehide. */
if (this.viewDisposed) return E('div', { 'class': 'cbi-map' });
@@ -2164,89 +2334,7 @@ return view.extend({
])
]
),
E('div', { 'id': 'fwlive-display-drawer', 'class': 'fwlive-display-bar' }, [
E('span', { 'class': 'fwlive-display-bar-label' }, [_('Display options')]),
E('div', { 'class': 'fwlive-display-controls' }, [
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-limit' }, [
_('Limit'),
E(
'select',
{
'id': 'fwlive-limit',
'class': 'cbi-input-select'
},
this.limitSelectOptions()
)
]),
E('label', { 'class': 'fwlive-display-ctl', 'for': 'fwlive-fetch-mode' }, [
_('Fetch budget'),
E(
'select',
{
'id': 'fwlive-fetch-mode',
'class': 'cbi-input-select',
'aria-controls': 'fwlive-manual-lines'
},
this.fetchModeOptions()
)
]),
E(
'label',
{ 'class': 'fwlive-display-ctl', 'for': 'fwlive-manual-lines' },
[
_('Maximum raw lines'),
E(
'select',
{
'id': 'fwlive-manual-lines',
'class': 'cbi-input-select',
'title': _(
'Manual still uses server protection and poll cadence'
)
},
this.manualFetchLinesOptions()
)
]
),
E('label', { 'class': 'fwlive-display-ctl' }, [
E('input', {
'id': 'fwlive-row-tint-toggle',
'type': 'checkbox',
'title': _('Show pass/deny row background colors')
}),
_('Row tint')
]),
E(
'label',
{
'id': 'fwlive-row-tint-palette-wrap',
'class': 'fwlive-display-ctl',
'for': 'fwlive-row-tint'
},
[
_('Palette'),
E(
'select',
{
'id': 'fwlive-row-tint',
'class': 'cbi-input-select',
'title': _(
'Classic uses green/red; Accessible uses teal/orange'
)
},
this.rowTintPaletteOptions()
)
]
),
E('label', { 'class': 'fwlive-display-ctl' }, [
E('input', {
'id': 'fwlive-show-hostnames',
'type': 'checkbox'
}),
_('Show hostnames')
])
])
]),
this.renderDisplayDrawer(),
E(
'div',
{ 'id': 'fwlive-filter-panel', 'class': 'fwlive-filter-panel fwlive-find-row' },
@@ -2348,91 +2436,7 @@ return view.extend({
E('tbody', {}, [])
])
]),
E('div', { 'class': 'fwlive-help-row' }, [
E('details', { 'id': 'fwlive-help', 'class': 'fwlive-help' }, [
E('summary', {}, [_('Help')]),
E('ul', {}, [
E('li', {}, [
_(
'The table updates automatically when your firewall logs traffic. Use Pause if it moves too fast.'
)
]),
E('li', {}, [
_(
'Enable logging turns on WAN zone drop/reject logging only (same as Network → Firewall). It does not add rules or log normal LAN browsing.'
)
]),
E('li', {}, [
_(
'Display options on the bar set Limit, row tint, palette, and hostnames.'
)
]),
E('li', {}, [
_(
'Fetch budget controls raw log lines per poll. Auto derives from Limit; Manual selects a bounded maximum.'
)
]),
E('li', {}, [
_(
'Manual still uses server protection and poll cadence; it changes the fetch budget, not the polling interval.'
)
]),
E('li', {}, [
_(
'For a responsive table on a weak device, keep Limit at 250 rows or below. The weak-device cap affects rendered rows; the buffer can still retain more.'
)
]),
E('li', {}, [
_(
'Switching tabs pauses polling; returning performs one catch-up poll. Hostnames are off by default because lookups add work.'
)
]),
E('li', {}, [
_(
'The router log ring may evict older events before fwlive reads them. fwlive cannot recover evicted entries or change forwarding behavior.'
)
]),
E('li', {}, [
_(
'The rate shown for WAN logging is the firewall zone log_limit. OpenWrt defaults to 10/minute when no explicit limit is configured; fwlive does not impose this cap.'
)
]),
E('li', { 'id': 'fwlive-manual-test' }, []),
E('li', {}, [
_(
'Click a row (Time or other non-link cells) to see the full log line (Simple view).'
)
]),
E('li', {}, [
_(
'Click an IP, action, or protocol to filter; use the Protocol menu (or ≠ on a chip) to exclude.'
)
]),
E('li', {}, [
_(
'Row tint shows pass/deny row backgrounds when checked. Choose Classic (green/red, default) or Accessible (teal/orange). Action text stays colored either way.'
)
]),
E('li', {}, [
_('Use Detail for all columns (flags, length, raw message).')
]),
E('li', {}, [
_(
'If Row tint looks missing, the active LuCI theme may omit success/error or info/warn CSS variables; fwlive falls back to local colors (air-gapped, no data leaves the device).'
)
])
])
]),
E(
'span',
{
'id': 'fwlive-build',
'class': 'fwlive-build',
'title': 'luci-app-fwlive'
},
['v' + constants.APP_VERSION]
)
])
this.renderHelpNodes()
]
);
},
+91 -106
View File
@@ -280,6 +280,64 @@ _fwlive_mktemp() {
printf '%s' "$_mk_file"
}
enrich_rules_dump() {
_dump_prefix="$1"
_dump_kind="$2"
case "$_dump_kind" in
nft)
_dump_error='nft_failed'
;;
iptables)
_dump_error='iptables_failed'
;;
ip6tables)
_dump_error='ip6tables_failed'
;;
*)
return 1
;;
esac
_rules_dump=$(_fwlive_mktemp "$_dump_prefix") || _rules_dump=''
if [ -n "$_rules_dump" ]; then
# Keep the dump command and stream parser paired by backend. The
# ip6tables caller is availability-gated below; its empty-error
# precedence is intentionally preserved in the mktemp branch.
if case "$_dump_kind" in
nft)
nft_list_ruleset >"$_rules_dump" 2>/dev/null
;;
iptables|ip6tables)
run_with_timeout "$IPTABLES_TIMEOUT" "${_dump_kind}-save" >"$_rules_dump" 2>/dev/null
;;
esac
then
case "$_dump_kind" in
nft)
map_from_nft_stream labeled <"$_rules_dump"
map_from_nft_stream unlabeled <"$_rules_dump"
;;
iptables|ip6tables)
map_from_iptables_save_stream labeled <"$_rules_dump"
map_from_iptables_save_stream unlabeled <"$_rules_dump"
;;
esac
elif [ -z "$_enrich_error" ]; then
_enrich_error="$_dump_error"
fi
rm -f "$_rules_dump"
elif [ "$_dump_kind" = ip6tables ]; then
# IPv6 enrichment is optional and must not replace an earlier
# IPv4/backend error when its temp file cannot be allocated.
if [ -z "$_enrich_error" ]; then
_enrich_error='mktemp_failed'
fi
else
_enrich_error='mktemp_failed'
fi
}
build_rules_map() {
OUT=''
_map_keys=0
@@ -293,47 +351,12 @@ build_rules_map() {
case "$backend" in
nft)
_rules_dump=$(_fwlive_mktemp fwlive-nft) || _rules_dump=''
if [ -n "$_rules_dump" ]; then
# Dumps land on /tmp (tmpfs). Duration bounded by
# NFT_TIMEOUT / IPTABLES_TIMEOUT; key/byte caps in map_add.
if nft_list_ruleset >"$_rules_dump" 2>/dev/null; then
map_from_nft_stream labeled <"$_rules_dump"
map_from_nft_stream unlabeled <"$_rules_dump"
elif [ -z "$_enrich_error" ]; then
_enrich_error='nft_failed'
fi
rm -f "$_rules_dump"
else
_enrich_error='mktemp_failed'
fi
enrich_rules_dump fwlive-nft nft
;;
iptables)
_rules_dump=$(_fwlive_mktemp fwlive-ipt) || _rules_dump=''
if [ -n "$_rules_dump" ]; then
if run_with_timeout "$IPTABLES_TIMEOUT" iptables-save >"$_rules_dump" 2>/dev/null; then
map_from_iptables_save_stream labeled <"$_rules_dump"
map_from_iptables_save_stream unlabeled <"$_rules_dump"
elif [ -z "$_enrich_error" ]; then
_enrich_error='iptables_failed'
fi
rm -f "$_rules_dump"
else
_enrich_error='mktemp_failed'
fi
enrich_rules_dump fwlive-ipt iptables
if command -v ip6tables-save >/dev/null 2>&1; then
_rules_dump=$(_fwlive_mktemp fwlive-ip6t) || _rules_dump=''
if [ -n "$_rules_dump" ]; then
if run_with_timeout "$IPTABLES_TIMEOUT" ip6tables-save >"$_rules_dump" 2>/dev/null; then
map_from_iptables_save_stream labeled <"$_rules_dump"
map_from_iptables_save_stream unlabeled <"$_rules_dump"
elif [ -z "$_enrich_error" ]; then
_enrich_error='ip6tables_failed'
fi
rm -f "$_rules_dump"
elif [ -z "$_enrich_error" ]; then
_enrich_error='mktemp_failed'
fi
enrich_rules_dump fwlive-ip6t ip6tables
fi
;;
esac
@@ -696,32 +719,36 @@ EOF
printf '{"names":{%s}}' "$OUT"
}
check_eq() {
_check_name="$1"
_check_got="$2"
_check_expected="$3"
_check_failure="$4"
if [ "$_check_got" != "$_check_expected" ]; then
# Keep the failure suffix at each call site so stderr stays byte-for-byte
# identical to the pre-extraction selftest messages.
echo "$_check_name: $_check_failure" >&2
return 1
fi
}
run_selftest() {
input=$(printf 'a\\b"c\nd\te')
escaped=$(printf '%s' "$input" | json_escape)
expected=$(printf 'a\\\\b\\"c\\nd\\te')
if [ "$escaped" != "$expected" ]; then
echo "json_escape: mismatch" >&2
return 1
fi
check_eq json_escape "$escaped" "$expected" mismatch || return 1
# Remaining C0 controls must become \u00XX (RFC 8259), not raw bytes.
input=$(printf 'x\001y\037z')
escaped=$(printf '%s' "$input" | json_escape)
expected='x\u0001y\u001fz'
if [ "$escaped" != "$expected" ]; then
echo "json_escape C0: expected $expected got $escaped" >&2
return 1
fi
check_eq 'json_escape C0' "$escaped" "$expected" "expected $expected got $escaped" || return 1
# Blank lines are data (paragraph-mode RS="" used to drop them).
input=$(printf 'a\n\nb')
escaped=$(printf '%s' "$input" | json_escape)
expected=$(printf 'a\\n\\nb')
if [ "$escaped" != "$expected" ]; then
echo "json_escape blank line: expected $expected got $escaped" >&2
return 1
fi
check_eq 'json_escape blank line' "$escaped" "$expected" "expected $expected got $escaped" || return 1
if is_resolvable_address 'not an ip'; then
echo 'is_resolvable_address: expected reject for spaces' >&2
@@ -812,25 +839,13 @@ run_selftest() {
fi
got=$(poll_clamp_lines "99999999999999999999")
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_clamp_lines: over-long expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_clamp_lines "$got" "$POLL_LINES_MAX" "over-long expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_clamp_lines "0")
if [ "$got" != "50" ]; then
echo "poll_clamp_lines: zero expected 50 got $got" >&2
return 1
fi
check_eq poll_clamp_lines "$got" "50" "zero expected 50 got $got" || return 1
got=$(poll_clamp_lines "2001")
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_clamp_lines: 2001 expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_clamp_lines "$got" "$POLL_LINES_MAX" "2001 expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_clamp_lines "500")
if [ "$got" != "500" ]; then
echo "poll_clamp_lines: expected 500 got $got" >&2
return 1
fi
check_eq poll_clamp_lines "$got" "500" "expected 500 got $got" || return 1
if ! _fwlive_tmp_dir_ok; then
echo "_fwlive_tmp_dir_ok: /tmp must be a sticky directory" >&2
@@ -841,18 +856,12 @@ run_selftest() {
Address: 127.0.0.1:53
8.8.8.8.in-addr.arpa name = dns.google.")
if [ "$got" != "dns.google" ]; then
echo "parse_nslookup_name: bind-style expected dns.google got '$got'" >&2
return 1
fi
check_eq parse_nslookup_name "$got" "dns.google" "bind-style expected dns.google got '$got'" || return 1
got=$(parse_nslookup_name "Server: 127.0.0.1
Address: 127.0.0.1:53
Address 1: 8.8.8.8 dns.google.")
if [ "$got" != "dns.google" ]; then
echo "parse_nslookup_name: busybox Address N expected dns.google got '$got'" >&2
return 1
fi
check_eq parse_nslookup_name "$got" "dns.google" "busybox Address N expected dns.google got '$got'" || return 1
got=$(parse_nslookup_name "Server: 192.168.1.1
Address 1: 192.168.1.1 router.lan
@@ -864,10 +873,7 @@ Address 1: 8.8.8.8 dns.google")
fi
got=$(parse_nslookup_name "Server: 127.0.0.1
Address: 127.0.0.1:53")
if [ -n "$got" ]; then
echo "parse_nslookup_name: server-only expected empty got '$got'" >&2
return 1
fi
check_eq parse_nslookup_name "$got" '' "server-only expected empty got '$got'" || return 1
run_logging_selftest || return 1
@@ -888,41 +894,20 @@ Address: 127.0.0.1:53")
fi
got=$(poll_lines_from_input '{"addresses":["999999"]}')
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_lines_from_input: expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["99999999999999999999"]}')
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_lines_from_input: over-long expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "over-long expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["18446744073709551616"]}')
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_lines_from_input: 18446744073709551616 expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "18446744073709551616 expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["0"]}')
if [ "$got" != "50" ]; then
echo "poll_lines_from_input: zero expected 50 got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "50" "zero expected 50 got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["2001"]}')
if [ "$got" != "$POLL_LINES_MAX" ]; then
echo "poll_lines_from_input: 2001 expected $POLL_LINES_MAX got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "$POLL_LINES_MAX" "2001 expected $POLL_LINES_MAX got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["500"]}')
if [ "$got" != "500" ]; then
echo "poll_lines_from_input: expected 500 got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "500" "expected 500 got $got" || return 1
got=$(poll_lines_from_input '{"addresses":["100"]}')
if [ "$got" != "100" ]; then
echo "poll_lines_from_input: expected 100 got $got" >&2
return 1
fi
check_eq poll_lines_from_input "$got" "100" "expected 100 got $got" || return 1
return 0
}
+12 -14
View File
@@ -1,17 +1,18 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=webd
PKG_RELEASE:=1
PKG_RELEASE:=3
PKG_VERSION:=20240223
PKG_VERSION_MEDIATEK_FILO:=20240710
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=A Lightweight self-hosted netdisk application
URL:=https://webd90kb.github.io/webd/
SECTION:=net
CATEGORY:=Network
TITLE:=A Lightweight self-hosted netdisk application
URL:=https://webd.cf
endef
TARGET_LIBC:=musl
@@ -79,12 +80,14 @@ endif
ifeq ($(ARCH),aarch64)
TARGET_ARCH:=aarch64
ifeq ($(BOARD),rockchip)
TARGET_BOARD:=musl.rockchip-armv8
endif
ifeq ($(BOARD),brcm2708)
TARGET_BOARD:=musl.brcm2708-bcm2710
endif
ifeq ($(BOARD),mediatek)
ifneq (,$(filter filogic,$(SUBTARGET)))
TARGET_BOARD:=musl.mediatek-filogic
@@ -93,16 +96,8 @@ ifeq ($(ARCH),aarch64)
endif
endif
ifneq ($(DUMP),1)
ifneq ($(ARCH),)
ifeq ($(strip $(TARGET_ARCH)$(TARGET_BOARD)),)
$(error Unsupported ARCH/BOARD combo: ARCH=$(ARCH) BOARD=$(BOARD) SUBTARGET=$(SUBTARGET))
endif
endif
endif
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(TARGET_ARCH)-openwrt-linux-$(TARGET_BOARD).tar.gz
PKG_SOURCE_URL:=https://webd90kb.github.io/webd/webd/webd_dl/20240223/
PKG_SOURCE_URL:=https://webd90kb.github.io/webd/webd/webd_dl/$(PKG_VERSION)/
PKG_HASH:=skip
UNTAR_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
@@ -118,10 +113,13 @@ endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(UNTAR_DIR)/webd/webd $(1)/usr/bin/webd
$(INSTALL_DIR) $(1)/usr/share/$(PKG_NAME)/web
$(CP) -r $(UNTAR_DIR)/webd/web/* $(1)/usr/share/$(PKG_NAME)/web/
$(INSTALL_DIR) $(1)/etc
$(INSTALL_DATA) $(UNTAR_DIR)/webd/webd.conf $(1)/etc/webd.conf.example
endef
$(eval $(call BuildPackage,$(PKG_NAME)))