Skip to content

Commit c4f639c

Browse files
committed
system/nxpkg: network sync, install hardening, and CLI completion
Fill in most of what the initial nxpkg slice deferred: network sync and artifact acquisition over plain HTTP (verified via SHA-256), an install rewrite that supports network sources and reclaims state left by an interrupted previous install, and wiring update/remove/rollback/ available into the CLI. Harden the package path against untrusted input along the way: bounded memory-safety helpers and explicit size limits throughout, storage read/write hardened against partially-written files, path-traversal rejection in manifest name/version before they reach the filesystem, and a fix for a lost-update race on the shared installed-packages database (two concurrent installs of different packages could otherwise silently clobber each other's recorded state). Add an optional manifest icon field for the nxstore GUI frontend to consume, raise PKG_INDEX_MAX now that the in-memory index is heap-allocated, and document the repository layout and local server setup in system/nxpkg/README.txt. Also fixes a real build break: pkg_runtime_compat() unconditionally referenced CONFIG_ARCH_BOARD, a Kconfig string symbol with no default clause under ARCH_BOARD_CUSTOM, so it is left entirely undefined rather than defined-but-empty on a custom board - falls back to CONFIG_ARCH_BOARD_CUSTOM_NAME instead. Routes pkg_error()/pkg_info() through syslog rather than stdio, since neither is visible to a supervisor with no attached console. Assisted-by: Claude <noreply@anthropic.com> Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
1 parent 1d15c81 commit c4f639c

13 files changed

Lines changed: 2259 additions & 183 deletions

File tree

system/nxpkg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ if(CONFIG_SYSTEM_NXPKG)
3838
pkg_log.c
3939
pkg_manifest.c
4040
pkg_metadata.c
41+
pkg_repo.c
4142
pkg_store.c
4243
pkg_txn.c)
4344
endif()

system/nxpkg/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ config SYSTEM_NXPKG_STACKSIZE
2929
int "'nxpkg' stack size"
3030
default 16384
3131

32+
config SYSTEM_NXPKG_ROOT
33+
string "'nxpkg' storage root"
34+
default "/tmp/nxpkg"
35+
---help---
36+
Base directory used by nxpkg for its local index, installed
37+
metadata, temporary downloads, and package payload storage.
38+
Boards with external storage can point this to a persistent
39+
location such as /mnt/sdcard/nxpkg.
40+
3241
endif

system/nxpkg/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ MODULE = $(CONFIG_SYSTEM_NXPKG)
2929

3030
CSRCS = pkg_compat.c pkg_hash.c pkg_install.c pkg_log.c pkg_manifest.c
3131
CSRCS += pkg_metadata.c pkg_store.c pkg_txn.c
32+
CSRCS += pkg_repo.c
3233
MAINSRC = pkg_main.c
3334

3435
include $(APPDIR)/Application.mk

system/nxpkg/README.txt

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
nxpkg - a package manager for NuttX application images
2+
========================================================
3+
4+
nxpkg installs, updates, uninstalls, and rolls back standalone loadable
5+
ELF applications (MODULE=m in an app's Makefile - see games/NXDoom or
6+
examples/calculator for real ports) from a package repository served
7+
over HTTP, onto local storage (an SD card on this board's config).
8+
system/nxstore is an LVGL touchscreen front-end for nxpkg; this
9+
document covers the repository/server side, which nxstore and the
10+
nxpkg CLI both consume identically.
11+
12+
13+
Repository layout
14+
==================
15+
16+
A repository is nothing more than a directory of static files:
17+
18+
<repo-root>/
19+
index.json - the package catalog
20+
artifacts/<arch>/<chip>/<compat>/<name>/<version>/<artifact-file>
21+
icons/<arch>/<chip>/<compat>/<name>.bin - optional, see below
22+
23+
index.json is a single JSON object with one "packages" array. Each
24+
entry is one installable version of one package:
25+
26+
{
27+
"packages": [
28+
{
29+
"name": "calc",
30+
"version": "6",
31+
"arch": "xtensa",
32+
"compat": "esp32s3-touch-lcd-7",
33+
"artifact": "artifacts/xtensa/esp32s3/esp32s3-touch-lcd-7/calc/6/calc",
34+
"sha256": "<64 hex chars>",
35+
"type": "elf",
36+
"description": "Simple 4-function calculator",
37+
"category": "Utilities",
38+
"icon": "icons/xtensa/esp32s3/esp32s3-touch-lcd-7/calc.bin"
39+
}
40+
]
41+
}
42+
43+
"artifact" and "icon" (both optional except "artifact") are resolved
44+
relative to the repository's own base URL (the URL index.json itself
45+
was fetched from, with the filename stripped) unless they're already
46+
a full http(s):// URL - see pkg_resolve_artifact_source()/
47+
pkg_resolve_icon_source() in pkg_repo.c. That means the whole
48+
directory tree above can be moved, mirrored, or served from a
49+
completely different host without editing a single path in
50+
index.json, as long as the *relative* structure between index.json
51+
and artifacts/icons/ stays the same.
52+
53+
"arch"/"compat" must match this board's CONFIG_ARCH ("xtensa") and
54+
CONFIG_ARCH_BOARD ("esp32s3-touch-lcd-7") exactly - see
55+
pkg_compat_check() in pkg_compat.c - so one repository can host
56+
packages for several different boards side by side; each board only
57+
ever installs the entries that match its own identity.
58+
59+
60+
What actually serves this - any static file host works
61+
========================================================
62+
63+
pkg_repo_fetch_url() (pkg_repo.c) does a plain HTTP GET with no auth,
64+
no custom headers, and no server-side logic required - it just needs
65+
a 2xx response. This means literally any static file server works:
66+
nginx or Apache with a DocumentRoot pointed at the repo directory, a
67+
one-line Python http.server, GitHub Pages, S3/R2/Cloudflare Pages, or
68+
a NAS's built-in web server. There is nothing nxpkg-specific to
69+
install on the server side.
70+
71+
For local development/testing (what this project actually used while
72+
building and testing every package in this series on real hardware):
73+
74+
cd /path/to/repo-root
75+
python3 -m http.server 8000
76+
77+
That's the entire "server setup." Confirm it's reachable from your
78+
own machine first:
79+
80+
curl -sI http://<your-machine-ip>:8000/index.json
81+
82+
sha256 verification (pkg_hash_file_sha256(), checked against the
83+
manifest's "sha256" field before an artifact is ever activated)
84+
already protects payload integrity in transit even over plain HTTP.
85+
Only confidentiality/availability would need HTTPS on top of this if
86+
that matters for a given deployment - nothing in the protocol assumes
87+
HTTP specifically, an https:// URL works exactly the same way.
88+
89+
90+
Populating a repository: tools/export_pkg_repo.py
91+
==================================================
92+
93+
Building the JSON by hand is error-prone (sha256 digests, exact
94+
relative paths); tools/export_pkg_repo.py does it for you from a
95+
built binary:
96+
97+
python3 tools/export_pkg_repo.py \
98+
--arch xtensa --chip esp32s3 --compat esp32s3-touch-lcd-7 \
99+
--package "calc:6:elf:/path/to/apps/bin/calc:Simple 4-function calculator:Utilities" \
100+
/path/to/repo-root
101+
102+
--package can be repeated to export several packages/versions in one
103+
call. The colon-separated spec is:
104+
105+
<name>:<version>:<elf|shared-lib>:<source>[:<description>:<category>[:<icon>]]
106+
107+
<source> is the built artifact on your machine (e.g. apps/bin/calc
108+
after a normal `make`). <icon> is optional: any image Pillow can open
109+
(PNG, JPEG, ...) - the tool resizes it to a fixed 48x48 and encodes it
110+
into the small raw RGB565 format system/nxstore's icon rendering
111+
expects (see nxstore_load_icon() in nxstore_main.c for the exact
112+
on-wire layout). Re-running the script is idempotent: it loads
113+
whatever index.json already exists in the target directory, merges in
114+
the packages you just passed (matched on name+version+arch+compat+
115+
type), and rewrites the file - existing unrelated entries are left
116+
alone.
117+
118+
This board's FAT-formatted SD card only supports short (8.3) file/
119+
directory names with no long-name extension - any path component
120+
(package name, artifact leaf filename) over 8 characters before its
121+
extension fails to install with errno 22, not a clear error message.
122+
Keep names short (this is why this series' packages are named "calc",
123+
"brick", "cgol" rather than "calculator", "brickmatch", "conway") -
124+
this is a board/filesystem constraint, not an export_pkg_repo.py or
125+
nxpkg one, and would not apply to a board with a different storage
126+
filesystem.
127+
128+
129+
Pointing the board at your repository
130+
======================================
131+
132+
Two ways to get an index synced onto the board's local storage,
133+
useful in different situations:
134+
135+
1. One-shot from NSH, useful for iterating on a package during
136+
development (this is what building and testing every package in
137+
this series actually looked like):
138+
139+
nxpkg sync http://<your-machine-ip>:8000/index.json
140+
nxpkg install <name>
141+
nxpkg update <name> # re-check for/install a newer version
142+
nxpkg list # show what's installed, current vs previous
143+
nxpkg rollback <name> # swap back to the previous version
144+
nxpkg remove <name>
145+
146+
2. Automatically at boot, via system/nxstore: the repo URL nxstore
147+
syncs from at startup is currently a single hardcoded string in
148+
board_nxstore_autostart() (boards/xtensa/esp32s3/
149+
esp32s3-touch-lcd-7/src/esp32s3_bringup.c) - change that one line
150+
to point at a different host, or a public one, and every boot
151+
re-syncs the catalog automatically (falling back to whatever was
152+
last synced to local storage if the network/server is unreachable
153+
at boot - nxstore never blocks the app list on a failed sync).
154+
155+
Development-network note: if your development machine's IP address
156+
changes (switching Wi-Fi networks, a new DHCP lease, a hotspot),
157+
re-run `nxpkg sync` with the new address - the board and the machine
158+
serving the repository just need to be able to reach each other over
159+
IP; nothing else about the setup changes.
160+
161+
162+
Concurrency note
163+
=================
164+
165+
nxpkg protects a single package's own install/update/rollback against
166+
running twice at once (a per-package lock file), and separately
167+
protects the shared installed-packages database against being
168+
corrupted by two *different* packages' installs racing each other
169+
(see pkg_install_acquire_installed_lock() in pkg_install.c) - so it is
170+
safe to have, for example, system/nxstore's own background install
171+
worker and a directly-invoked `nxpkg install` running at the same
172+
time for different packages. Two operations on the *same* package
173+
name at the same time will have the second one fail with -EBUSY
174+
(surfaced as "package has an install/update in progress") rather than
175+
either one silently clobbering the other's work.

0 commit comments

Comments
 (0)