Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
abc6ab9
fix(analytics): apply nodeBlacklist and hiddenNamePrefixes to Route P…
efiten Oct 13, 2026
b43fd4a
feat(#2107): backfill unresolved channel/scope rows when the key set …
SaarMesh-Bot Oct 13, 2026
e53c055
perf(store): share one copy of each observer string across observatio…
A13xB0 Oct 13, 2026
478f74a
fix(config): write the geo filter through a symlinked config.json (#2…
nullrouten0 Oct 13, 2026
8d2ae40
perf(store): cache an observation's parsed time in 8 bytes (#2196)
A13xB0 Oct 13, 2026
b8e571c
perf(store): build the relay-filtered candidate lists once (#2195)
A13xB0 Oct 13, 2026
59708ee
ci: publish the README badges to a badges branch with GITHUB_TOKEN (#…
efiten Oct 13, 2026
fbeb9e4
ci: frontend coverage from every E2E suite, merged across the shards …
efiten Oct 13, 2026
7c7c510
docs(readme): correct stale claims and add the optional features (#2205)
efiten Oct 13, 2026
1ae4c74
feat(observers): rolling 1 h TX airtime and a duty-cycle warning (#66…
efiten Oct 14, 2026
a164a44
fix(docker): run the Go services as an unprivileged user (#2125)
nullrouten0 Oct 15, 2026
92dd172
perf(store): bulk loaders reuse their row scan targets (#2201)
A13xB0 Oct 15, 2026
2cdb18e
perf(store): the decoded-payload cache drops fields nothing reads (#2…
A13xB0 Oct 15, 2026
09e4651
test(store): read resolved_path pubkeys without decoding to pointers
A13xB0 Oct 11, 2026
7c2d78d
perf(store): read resolved_path pubkeys in place, one copy each
A13xB0 Oct 11, 2026
ada1eff
test(store): eviction's resolved pubkey lookup stays cheap
A13xB0 Oct 11, 2026
d122cff
perf(store): eviction reads resolved pubkeys in place
A13xB0 Oct 11, 2026
a7f00ad
perf: intern resolved pubkeys with internStr; scan NullString again
A13xB0 Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"MeshAudio": "readonly",
"MeshConfigReady": "readonly",
"NodeHopAnalytics": "readonly",
"ObserverAirtime": "readonly",
"PAYLOAD_COLORS": "readonly",
"PAYLOAD_TYPES": "readonly",
"PERF_SLOW_MS": "readonly",
Expand Down
167 changes: 117 additions & 50 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ jobs:
- name: Staging disk-monitor unit tests (issue #1684)
run: bash scripts/staging/test-disk-monitor.sh

- name: Container service-user selection unit tests
run: bash docker/test-select-service-user.sh

- name: QA SQL parameter-binding unit tests (issue #1977)
run: bash qa/scripts/test-blacklist-sql.sh

Expand Down Expand Up @@ -543,6 +546,10 @@ jobs:
done

- name: Run Playwright E2E tests (fail-fast)
env:
# On master pushes every suite records the frontend coverage of the
# pages it opens into .nyc_output/ (#2203). Off on PRs to keep them fast.
NODE_OPTIONS: ${{ github.event_name == 'push' && '--require ./scripts/e2e-coverage-hook.js' || '' }}
run: |
# suite <shard> <file> [VAR=value ...]: runs tests/e2e/<file> with
# exactly those variables, but only on its own shard. The shard numbers
Expand Down Expand Up @@ -579,6 +586,7 @@ jobs:
suite 2 test-map-nodes-pagination-e2e.js CHROMIUM_REQUIRE=1 BASE_URL=$B
suite 3 test-observer-iata-1188-e2e.js BASE_URL=$B
suite 2 test-issue-1639-observers-sort-e2e.js BASE_URL=$B
suite 2 test-issue-661-airtime-e2e.js BASE_URL=$B
suite 2 test-issue-1758-ng-filter-rerenders-e2e.js CHROMIUM_REQUIRE=1 BASE_URL=$B
suite 2 test-nav-fluid-1055-e2e.js CHROMIUM_REQUIRE=1 BASE_URL=$B
suite 3 test-nav-priority-1102-e2e.js CHROMIUM_REQUIRE=1 BASE_URL=$B
Expand Down Expand Up @@ -720,26 +728,26 @@ jobs:
done
echo "3 passed"

# Shard 2 runs test-e2e-playwright.js, the one suite that writes
# .nyc_output, so the coverage collection and its badge live there too.
# An extra coverage source on top of the suites: a tour of the main
# page groups. Its files land in .nyc_output/ beside the suites' files.
- name: Collect frontend coverage (parallel)
if: success() && github.event_name == 'push' && matrix.shard == 2
run: |
BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt || true

- name: Generate frontend coverage badge
if: success()
# One file per suite is about 6 MB, mostly the same maps, so the shard
# merges them before upload. e2e-test merges the three shards and makes
# the frontend coverage badge.
# Coverage never decides the outcome: a failure here must not skip the
# shard upload below (if: success()) and so fail the E2E gate.
- name: Merge this shard's frontend coverage
if: success() && github.event_name == 'push'
continue-on-error: true
run: |
mkdir -p .badges
if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-coverage.json ]; then
npx nyc report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt
FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || echo "0")
FE_COVERAGE=${FE_COVERAGE:-0}
FE_COLOR="red"
[ "$(echo "$FE_COVERAGE > 50" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="yellow"
[ "$(echo "$FE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="brightgreen"
echo "{\"schemaVersion\":1,\"label\":\"frontend coverage\",\"message\":\"${FE_COVERAGE}%\",\"color\":\"${FE_COLOR}\"}" > .badges/frontend-coverage.json
echo "## Frontend: ${FE_COVERAGE}% coverage" >> $GITHUB_STEP_SUMMARY
if ls .nyc_output/*.json >/dev/null 2>&1; then
npx nyc merge .nyc_output coverage-shard.json
else
echo "::warning::no frontend coverage was recorded on shard ${{ matrix.shard }}"
fi

- name: Stop test server
Expand All @@ -761,7 +769,7 @@ jobs:
name: e2e-shard-${{ matrix.shard }}
path: |
e2e-output.txt
.badges/
coverage-shard.json
retention-days: 1
if-no-files-found: ignore
include-hidden-files: true
Expand Down Expand Up @@ -800,8 +808,9 @@ jobs:
ls shards
[ "$(ls -d shards/e2e-shard-* | wc -l)" -eq 3 ] || { echo "::error::expected results from 3 shards"; exit 1; }
cat shards/e2e-shard-*/e2e-output.txt > e2e-output.txt
mkdir -p .badges
cp shards/e2e-shard-*/.badges/*.json .badges/ 2>/dev/null || true
# Start empty: the checkout holds master's old .badges/ files,
# which would otherwise go out in e2e-badges (#2136).
rm -rf .badges && mkdir .badges
# Aggregate per-suite PASS/FAIL across every test-*-e2e.js summary.
# The previous regex (grep -oP '[0-9]+(?=/)' | tail -1) caught a
# stray digits-before-slash like the '2' in '2/3 tests passed' from
Expand All @@ -819,6 +828,40 @@ jobs:
echo "{\"schemaVersion\":1,\"label\":\"e2e tests\",\"message\":\"${E2E_MSG}\",\"color\":\"${E2E_COLOR}\"}" > .badges/e2e-tests.json
echo "E2E: ${E2E_MSG} ($(grep -c '^=== E2E SUITE' e2e-output.txt) suite runs)" | tee -a "$GITHUB_STEP_SUMMARY"

- name: Set up Node.js 22
if: github.event_name == 'push'
continue-on-error: true
uses: actions/setup-node@v5
with:
node-version: '22'

# Coverage from every suite on every shard (#2203), merged into one
# number. Only master pushes record it, and it never fails the gate.
- name: Generate frontend coverage badge
if: github.event_name == 'push'
continue-on-error: true
run: |
mkdir -p .nyc_output
for f in shards/e2e-shard-*/coverage-shard.json; do
[ -f "$f" ] && cp "$f" ".nyc_output/$(basename "$(dirname "$f")").json"
done
if ! ls .nyc_output/*.json >/dev/null 2>&1; then
echo "::warning::no shard recorded frontend coverage; the badge keeps its previous value"
exit 0
fi
echo "Frontend coverage from: $(ls .nyc_output | paste -sd ' ' -)"
npx --yes nyc@18.0.0 report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt
FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || true)
if [ -z "$FE_COVERAGE" ]; then
echo "::warning::nyc report gave no statement coverage; the badge keeps its previous value"
exit 0
fi
FE_COLOR="red"
[ "$(echo "$FE_COVERAGE > 50" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="yellow"
[ "$(echo "$FE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="brightgreen"
echo "{\"schemaVersion\":1,\"label\":\"frontend coverage\",\"message\":\"${FE_COVERAGE}%\",\"color\":\"${FE_COLOR}\"}" > .badges/frontend-coverage.json
echo "## Frontend: ${FE_COVERAGE}% coverage" >> "$GITHUB_STEP_SUMMARY"

- name: Upload E2E badges
uses: actions/upload-artifact@v6
with:
Expand Down Expand Up @@ -1225,54 +1268,78 @@ jobs:
# jobs, so ordering is unchanged.
needs: [build-and-publish]
runs-on: ubuntu-latest
# The badges go to their own `badges` branch (#2136). master is protected,
# so writing there took a PAT (BADGE_PUSH_TOKEN), which stopped working.
# The badges branch is unprotected, so the job's own GITHUB_TOKEN can
# write it.
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
# The push below passes its own token; later steps need none.
persist-credentials: false

- name: Download Go coverage badges
# go-badges second, so its files win if e2e-badges ever carries go
# badges again.
- name: Download E2E badges
continue-on-error: true
uses: actions/download-artifact@v6
with:
name: go-badges
path: .badges/
name: e2e-badges
path: badges-new/

- name: Download E2E badges
- name: Download Go coverage badges
continue-on-error: true
uses: actions/download-artifact@v6
with:
name: e2e-badges
path: .badges/
name: go-badges
path: badges-new/

- name: Publish coverage badges to repo
- name: Publish coverage badges to the badges branch
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }}
GH_TOKEN: ${{ github.token }}
run: |
# GITHUB_TOKEN cannot push to protected branches (required status checks).
# Use admin PAT (BADGE_PUSH_TOKEN) via GitHub Contents API instead.
for badge in .badges/*.json; do
FILENAME=$(basename "$badge")
FILEPATH=".badges/$FILENAME"
CONTENT=$(base64 -w0 "$badge")
CURRENT_SHA=$(gh api "repos/${{ github.repository }}/contents/$FILEPATH" --jq '.sha' 2>/dev/null || echo "")
if [ -n "$CURRENT_SHA" ]; then
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
-X PUT \
-f message="ci: update $FILENAME [skip ci]" \
-f content="$CONTENT" \
-f sha="$CURRENT_SHA" \
-f branch="master" \
--silent 2>&1 || echo "Failed to update $FILENAME"
else
gh api "repos/${{ github.repository }}/contents/$FILEPATH" \
-X PUT \
-f message="ci: update $FILENAME [skip ci]" \
-f content="$CONTENT" \
-f branch="master" \
--silent 2>&1 || echo "Failed to create $FILENAME"
fi
done
echo "Badge publish complete"
# Start from what the badges branch holds now (master's .badges/ the
# first time) and overwrite only the badges this run produced, so a
# missing artifact never removes a badge. The branch is one orphan
# commit, force-pushed, so it never grows.
# Every step checks its own result: bash ignores set -e inside a
# function whose status is tested, as the `if` below does.
publish() {
ls badges-new/*.json >/dev/null 2>&1 || { echo "no badge artifacts were downloaded"; return 1; }
out=$(mktemp -d) || return 1
# Exit 2 means the branch does not exist yet; any other failure
# must not be taken for that, or the badges this run did not
# produce would be reset.
git ls-remote --exit-code --heads origin badges >/dev/null
case $? in
0)
git fetch -q --depth=1 origin badges || return 1
git archive FETCH_HEAD | tar -x -C "$out" || return 1
;;
2)
if ls .badges/*.json >/dev/null 2>&1; then
cp .badges/*.json "$out"/ || return 1
fi
;;
*) return 1 ;;
esac
cp badges-new/*.json "$out"/ || return 1
cd "$out" || return 1
git init -q -b badges || return 1
git add -- *.json || return 1
git -c user.name="github-actions[bot]" \
-c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -q -m "ci: badges for ${GITHUB_SHA::8}" || return 1
git push -q --force "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:badges || return 1
ls -- *.json
}
if ! (publish); then
echo "::warning title=Badges not published::Publishing to the badges branch failed (see the step log); the README badges keep their previous values."
fi

- name: Post deployment summary
run: |
Expand Down
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ FROM alpine:3.20

RUN apk add --no-cache mosquitto mosquitto-clients supervisor caddy wget

# Account for the Go services when a deployment opts into running them
# unprivileged with RUN_AS_UID=1000, the usual choice. Creating it costs
# nothing when it goes unused, and saves the entrypoint creating one at start.
#
# Nothing in the image is given to it here. -H matters for that: without it
# busybox adduser creates the home directory itself, and /app would be
# corescope-owned mode 2755 — the setgid bit then giving gid 1000 to /app/data
# and to every file written into it, which Docker copies into each new named
# volume. The home field still points at /app; only the directory is left to
# the WORKDIR below, root-owned as in earlier releases.
RUN addgroup -S -g 1000 corescope && \
adduser -S -u 1000 -G corescope -H -h /app -s /sbin/nologin corescope

# Default: the services run as root, exactly as in earlier releases. The
# entrypoint overrides this when RUN_AS_UID asks for an unprivileged account.
ENV CORESCOPE_SERVICE_USER=root

WORKDIR /app

# Go binaries (statically linked; they do not use this image's libc)
Expand Down Expand Up @@ -155,6 +172,8 @@ RUN mkdir -p /app/data /var/lib/mosquitto /data/caddy && \

# Entrypoint
COPY docker/entrypoint-go.sh /entrypoint.sh
# Sourced by the entrypoint, so deliberately not executable.
COPY docker/select-service-user.sh /select-service-user.sh
RUN chmod +x /entrypoint.sh

EXPOSE 80 443 1883
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# corescope build entry point.
#
# This repo is 14 Go modules wired with `replace ../../internal/*` and no
# This repo is 16 Go modules wired with `replace ../../internal/*` and no
# go.work, so every recipe changes into its module. The build flags live here
# because the SQLite driver is cgo now (github.com/mattn/go-sqlite3) and the
# flags stopped being something you can safely retype at each call site.
Expand Down
Loading