Compare commits

...

7 Commits

Author SHA1 Message Date
40d3b21e71 Add AGENTS.md and fill the gaps in the README
AGENTS.md describes the project for anyone -- human or agent -- changing a
script: what the repository is, where it sits inside the OpenWrt tree, which
scripts run from where, the shell conventions the existing code follows, and
the bash traps that have already caused bugs here.

README: document the options that were missing (--refresh, --skip-defconfig,
--extras-file/--no-extras-file, --extras, --wrap), distinguish --extras from
--extras-file, and replace the Debian-only Go install line with a
distribution-neutral description of how update-go-path.sh finds a toolchain.
Drop the hedged MIT claim, since no LICENSE file exists.

Both documents stay environment-neutral: no absolute home paths and no
assumption of a distribution, with platform-specific advice kept under an
explicit conditional heading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 10:59:41 -04:00
2abd8aba00 Rewrite the README for the current script set
The README had been patched incrementally across several changes and no longer
matched the repository: sections about adding a device and the package list
format had ended up nested under Quick start, and the layout it described
predated the per-device packages/ directory.

Restructure it around what someone actually does: layout, requirements, a
five-step quick start, then per-script reference. Document the parts that are
easy to get wrong and were learned the hard way — the buildbot flags and why
stripping them is safe, the Go toolchain options, the WSL PATH problem that
breaks every Go package, and the git clean -xdff hazard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:28:54 -04:00
a98b1e1c76 Make update-go-path.sh able to skip the Go bootstrap chain
Setting CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT on its own only skips the
initial bootstrap step; OpenWrt still builds the rest of the chain from
source because CONFIG_GOLANG_BUILD_BOOTSTRAP defaults to y. Add --external,
which clears that symbol so the installed toolchain builds host Go directly.

Fix the "no Go installed" detection, which never fired. An unmatched glob is
left as a literal, so the array held the pattern itself and the script wrote
CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT="/usr/lib/go-go-*/" and reported
success. nullglob alone is not enough either, since it only drops words that
contain a wildcard and the literal candidate paths survive regardless, so
each candidate is now checked for existence.

Also pick the Go version by asking bin/go rather than parsing the directory
name, warn when the installed Go is older than the bootstrap version the
tree expects, replace every existing form of a symbol instead of appending
a duplicate, and run make defconfig afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:24:54 -04:00
e668221b4e Keep one extras list per device instead of per version
The package lists were stored as config_<version>/packages_<device>_<version>.txt,
but the version dimension was pure duplication: the Linksys MX8500 extras were
byte-identical across 24.10.4, 25.12.0, 25.12.2 and 25.12.4, and the Cudy lists
only ever changed with the device, never with the release.

Your packages now live in packages/<device-id>.txt, one file per device and
independent of the OpenWrt version. The built-in part is fetched per release
anyway, so upgrading needs no file changes at all.

The combined list is a build artefact and moves to .generated/, which is
gitignored, along with the buildinfo saved by --save-buildinfo --offline.

This removes work rather than adding it: because the generated file is now
entirely machine-owned, the sentinel comments, the in-section replacement state
machine and the "line containing base-files and libc is the old generated one"
migration heuristic are all gone. Hand-written content is isolated in the extras
file, generated content in .generated/.

Also fix version detection when a script is run from the helper directory. The
helper checkout is its own git repository inside the OpenWrt worktree, so git
commands there described the helper repo -- a checkout of OpenWrt 25.12.5 was
reported as SNAPSHOT because the helper repo is on main. Version queries are now
anchored to the OpenWrt tree.

The config_* archives are deleted; git history keeps them. That includes
config_24.10.4/apply-dahdi-patches.sh, which only applied to 24.10.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:18:29 -04:00
0255d2f890 Generate the built-in package list from the official index
The ##built-in section of each package list was copied by hand from the
Firmware Selector for every new release. It is now generated from the
target's profiles.json (default_packages + device_packages, plus the two
packages the Selector adds on top), which reproduces the hand-written list
byte for byte, order included.

New lib-openwrt-upstream.sh holds the shared upstream access: URL
construction, cached fetches, version detection, device search over
.overview.json and profiles.json parsing. It needs python3, not jq.

New gen-package-list.sh writes config_<ver>/packages_<dev>_<ver>.txt,
replacing only the generated block so hand-written extras and the ##module
section survive; --stdout and --apply skip the file entirely.

download-config.sh:
- Look the device up in the official index instead of a hardcoded table of
  three devices, so any supported device can be selected by name.
- Build the firmware URLs from profiles.json images[]. The old code pasted
  the target into the filename with its '/' intact, so the URL was always
  wrong and the check always warned.
- Stage the download in a temp file and validate it before touching
  .config, and keep a backup. A 404 used to truncate .config to nothing.
- Fix version detection: `git describe --tags --abbrev=0` returns a tag
  whenever any tag is reachable, so the entire fallback chain below it was
  unreachable and a branch tip silently produced the wrong release URL.
- Build only the selected device instead of all 35 in the target, and turn
  off the buildbot flags (ALL_KMODS, ALL_NONSHARED, SDK, IB,
  MAKE_TOOLCHAIN, COLLECT_KERNEL_DEBUG, AUTOREMOVE). Verified not to change
  the firmware: the set of packages built into the image is identical
  either way (193 packages), while the module packages to build drop from
  1248 to 0. --keep-buildbot-flags and --all-profiles restore the old
  behaviour.
- Assert the device symbol survived make defconfig, so a tree that does not
  match the release fails loudly instead of silently building the default.

add-openwrt-packages.sh:
- Rewrite existing CONFIG_PACKAGE_ lines in place. Repeated runs used to
  append duplicates and grow .config every time.
- Support removal via a -pkg prefix or a ##remove section.
- Fix the parser dropping a lone '#' or an unknown ##header into the
  package list, where it was reported as a missing package named '#'.
- Read .config once into a map instead of grepping it per package, and
  compare package names exactly rather than as regexes.
- After make defconfig, report any package whose final state differs from
  what was asked for, which is the only way to see a removal that a
  dependency pulled back in.

Delete add-openwrt-packages_old.sh; nothing referenced it and it was a
strict subset of the current script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:14:36 -04:00
351e7c41f3 Require the OpenWrt root in add-external-repos.sh
Every path in the script is relative to the current directory, so running
it from anywhere else cloned the packages into that directory instead.
Now that the helper checkout lives at openwrt/helper/, running it there
would populate helper/package/ rather than the real package/.

Use the same guard the other helper scripts already have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:31:32 -04:00
de7f3f12fd Rewrite the prepare scripts for current OpenWrt and more distros
prepare-openwrt-env.sh
- Dispatch on the package manager instead of guessing from the distro
  version: apt, dnf/yum, pacman, zypper and apk are all supported.
- Probe every package against the running release and skip the ones that
  no longer exist. Previously a single dropped package (on Ubuntu 26.04
  arm64, gcc-multilib-s390x-linux-gnu) failed the whole apt command and
  set -e aborted the script, so nothing was installed at all.
- Align the package lists with the official build system guide, adding
  libelf-dev, python3-dev, bc, xsltproc, zstd, u-boot-tools and others.
- Make Go opt-in via --with-go rather than forced on arm64.
- Add -y/--yes, -n/--dry-run and -h/--help; allow running as root.

prepare-openwrt.sh
- Split read-only discovery from mutation. Versions are resolved over
  git ls-remote before anything is moved, so aborting at the prompt
  leaves the helper checkout untouched.
- Resume an interrupted first run instead of failing forever: a root
  with .git but no source tree was previously unrecoverable without a
  manual rm -rf. Also restore the worktree when HEAD already points at
  the target ref, where checkout is a no-op.
- Detect the relocated layout by content rather than directory name, so
  renaming the root no longer triggers a second, nested relocation.
- Replace the staged in-place relocation with a resumable content move,
  removing the window that could strand the checkout in a hidden dir.
- Filter release candidates before sorting; sort -V orders v25.12.0-rc1
  after v25.12.0, so the newest stable tag could be an rc.
- Follow upstream: snapshots use main (master as fallback) and the dead
  -SNAPSHOT tag lookup is gone.
- Use a partial clone (--filter=blob:none) by default.
- Guard version switches on a dirty tree and back up .config first.
- Add /helper/ to .git/info/exclude and warn that git clean -xdff still
  deletes it, since -x bypasses ignore rules by design.
- Leave a symlink at the old checkout path so shells and editors whose
  working directory still points there keep working.
- Add --stable/--branch/--snapshot plus -y, -n, --force, --full-clone,
  --allow-rc, --root, --skip-feeds, --skip-defconfig, --repo-url and
  --no-compat-link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:31:26 -04:00
33 changed files with 3085 additions and 50523 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Generated artefacts: full package lists and saved buildinfo.
# Regenerate with gen-package-list.sh / download-config.sh --save-buildinfo
.generated/

149
AGENTS.md Normal file
View File

@@ -0,0 +1,149 @@
# AGENTS.md
Guidance for coding agents working in this repository.
## What this is
A set of Bash scripts that automate building OpenWrt from source: install the
host dependencies, fetch the source tree, import a device's official
configuration, and apply a per-device package list.
There is no build system, no test suite and no dependency manifest. The
deliverable *is* the scripts. Everything is plain Bash plus `curl`/`wget`,
`git`, `awk`, `sed` and `python3` (only for reading JSON — `jq` is deliberately
not required).
The governing design rule: **a new OpenWrt release must not require editing any
file here.** Anything version specific is fetched from `downloads.openwrt.org`
at run time; only the user's own package choices are stored in git. Reject
changes that would reintroduce a version-pinned file, a hardcoded device table
or a copied-in package list.
## Repository layout
This repository is `helper/` *inside* an OpenWrt source tree. `prepare-openwrt.sh`
puts it there on first run:
```
<parent>/openwrt/ OpenWrt source root (not this repository)
<parent>/openwrt/helper/ this repository
<parent>/openwrt-build-helper symlink to openwrt/helper, for stale shells
```
```
helper/
*.sh the scripts
lib-openwrt-upstream.sh shared library, sourced not executed
packages/<device-id>.txt tracked: the user's packages for one device
.generated/ ignored: combined lists, saved buildinfo
README.md user-facing documentation
```
`<device-id>` is the official OpenWrt profile id (`linksys_mx8500`), so the
filename, the `--device` argument and upstream's `profiles.json` key are always
the same string.
## Where the scripts run from
Two groups, and mixing them up is the most common mistake:
- `prepare-openwrt-env.sh` and `prepare-openwrt.sh` run from **this directory**;
they exist to create the OpenWrt tree, so they cannot assume one.
- Everything else runs from the **OpenWrt root** (`./helper/<script>.sh`) because
it reads or writes `.config`, `feeds/`, `tmp/` or `package/`. Each of those
guards its entry point — `require_openwrt_root` from the library, or an
equivalent inline test for `feeds.conf.default` + `Makefile`. Keep that guard
when adding a script to this group. The exception is `gen-package-list.sh`,
which only produces text: it needs the tree for version detection (or
`--version`) and delegates the root check to `add-openwrt-packages.sh` on
`--apply`.
Never derive the OpenWrt root from `$0`'s parent alone: the compat symlink means
the script may be invoked through a path outside the tree. Resolve with
`pwd -P`, and use `find_openwrt_root` when a version query must be anchored to
the OpenWrt tree rather than to this repository's own git checkout.
## The scripts
| Script | Role |
|---|---|
| `prepare-openwrt-env.sh` | install host build dependencies; dispatches on the package manager (apt/dnf/yum/pacman/zypper/apk) |
| `prepare-openwrt.sh` | clone or update the OpenWrt tree, relocate this repository into it |
| `lib-openwrt-upstream.sh` | shared upstream access: URLs, caching, version detection, device lookup |
| `download-config.sh` | install a device's official `config.buildinfo` as `.config` |
| `gen-package-list.sh` | built-in packages from `profiles.json` + `packages/<id>.txt` |
| `add-openwrt-packages.sh` | apply a package list to `.config` |
| `update-go-path.sh` | point the build at an installed Go toolchain |
| `add-external-repos.sh` | clone extra package repositories into `package/` |
Only `download-config.sh` and `gen-package-list.sh` source the library; it is
guarded against double-sourcing by `_OWRT_LIB_LOADED`.
## Conventions
Match the existing style rather than introducing your own:
- `set -euo pipefail` and `umask 022` at the top of every executable script
(`add-external-repos.sh` predates the convention and still uses bare
`set -e`). The library sets nothing — the caller owns shell options.
- The usage block is the file's own header comment, printed by
`usage() { sed -n '<start>,<end>p' "$0" | sed 's/^# \{0,1\}//;s/^#$//'; }`.
**Editing the header changes `--help`** — keep the line range correct, and
update the header whenever you add an option.
- All logging goes to **stderr** (`info`/`warn`/`err`). Functions return their
value on stdout, so a stray `echo` to stdout corrupts `VAR="$(fn)"` at the
call site. This has broken the code before.
- Every destructive command goes through `run`, so `--dry-run` prints instead of
acting; every prompt goes through `confirm`, so `-y` skips it. Both honour the
`DRY_RUN` / `ASSUME_YES` globals.
- Read-only discovery first, mutation after the confirmation prompt. Aborting at
the prompt must leave the filesystem untouched.
- Write to a temp file and validate before replacing something that matters;
keep a `.bak`.
- Tunables are `${VAR:-default}` so they can be overridden from the environment
without editing the file.
Bash traps that have actually bitten this code:
- `local a="$1" b="$a"` — bash expands every word before assigning any, so `$a`
is unset under `set -u`. Use one `local` per dependent variable.
- `nullglob` only drops words that *contain* a wildcard; a literal path such as
`/usr/local/go/` survives even when it does not exist. Check with `-d`.
- `cmd | grep -q` under `pipefail` can fail on SIGPIPE. Prefer a pipe-free form.
- In `awk -F'\t'`, `$2` is not whitespace-split. Extract with string operations
when the field separator is not space.
- `git checkout <ref>` is a no-op when HEAD is already at `<ref>`, so it does not
restore deleted tracked files.
## Data files
`packages/<device-id>.txt` is hand written and holds **only** the user's extra
packages — never the built-in set, which is always generated. Format:
```
curl luci-app-ttyd whitespace separated, any number per line, applied as =y
-luci-app-wol leading '-' removes a package (=n)
##module following packages are set to =m
##remove ... =n
##built-in ... back to =y (the default)
pkg # note text after a single '#' is a comment
```
`.generated/` is git-ignored build output. Do not commit it, and do not make
anything depend on a file in it existing.
## Checking your work
There is no test runner. Before committing:
- `bash -n <script>` on everything you touched.
- `shellcheck` if available.
- Run the script with `-n/--dry-run` and with `-h`; confirm `-h` still prints the
full option list (the `sed` line range drifts silently).
- Behaviour-changing edits to config handling deserve a real check against a
concrete device: generate before and after, and diff the resulting sets of
`=y` packages. Do not claim equivalence you have not diffed.
Assume the user's machine is not the only target: no absolute home paths, no
assumption of a distro, an init system, or WSL. Platform-specific advice belongs
in the README under an explicit conditional heading.

356
README.md
View File

@@ -1,133 +1,261 @@
# OpenWrt Build Helper Scripts # OpenWrt Build Helper Scripts
## Overview Shell scripts that automate building OpenWrt from source: installing the host
This repository contains shell scripts that streamline preparing, configuring, and building OpenWrt from source on Debian/Ubuntu systems (x86_64 and ARM64). It also includes convenience utilities to manage package selections, fetch device config.buildinfo, apply a DAHDI driver patch, and add external LuCI packages. dependencies, fetching the source tree, importing a device's official
configuration, and keeping a per-device package list.
## Whats included The design goal is that **a new OpenWrt release requires no file changes**
- prepare-openwrt-env.sh everything version specific is fetched from upstream, and only your own package
- Installs required build dependencies via apt on Ubuntu 22.04/24.04/26.04 and Debian 12/13+. choices are stored in this repository.
- Handles x86_64 and ARM64 differences (Python/distutils, multilib notes, Go on ARM64).
- prepare-openwrt.sh
- Clones or updates the OpenWrt source repo in the top-level `openwrt/` root.
- Lets you select Stable, Beta, or Snapshot versions interactively.
- Uses the script location as the source of truth, so running it from another current working directory is supported.
- On first run from a helper checkout, relocates that checkout to `openwrt/helper/`.
- Updates feeds and runs make defconfig.
- add-external-repos.sh
- Clones or updates additional package repositories into package/.
- Currently includes luci-app-netspeedtest and luci-app-easytier (adjust REPOS as needed).
- download-config.sh
- Downloads config.buildinfo for a selected device based on your checked-out OpenWrt version (stable or snapshot).
- Writes .config and runs make defconfig.
- Prints firmware URL and a Firmware Selector link for reference.
- add-openwrt-packages.sh
- Enables packages listed in a text file by appending CONFIG_PACKAGE_<name>=y to .config (without removing existing settings).
- Verifies presence in package/ or feeds/ and reports missing ones, then runs make defconfig.
- update-go-path.sh
- Detects the latest Go installation under /usr/lib/go-* and sets CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT in .config.
- apply-dahdi-patches.sh
- Writes a DAHDI driver patch into package/feeds/telephony/dahdi-linux/patches/300-fix-dahdi-max-attempts.patch.
- Fixes MAX macro naming collisions in multiple DAHDI modules for OpenWrt 24.10.4 builds.
## System requirements ## Layout
- OS: Ubuntu 22.04/24.04/26.04 or Debian 12/13+ (others may work with adjustments)
- Architectures: x86_64/amd64 and aarch64/arm64 `prepare-openwrt.sh` normalizes the checkout on first run:
- Tools: git, wget, curl, bash
- Internet access for feeds and package downloads ```
- Optional: Go (required by some OpenWrt packages; auto-configured on ARM64 by prepare-openwrt-env.sh; configurable via update-go-path.sh) before <parent>/openwrt-build-helper/ this repository
first run <parent>/openwrt/ the OpenWrt source root
<parent>/openwrt/helper/ this repository, moved in
<parent>/openwrt-build-helper -> openwrt/helper (symlink)
later runs the same <parent>/openwrt/ is updated in place
```
The symlink means shells, editors and agent sessions still sitting in the old
path keep working. Disable it with `--no-compat-link`.
Inside `helper/`:
| Path | In git | Purpose |
|---|---|---|
| `packages/<device-id>.txt` | yes | your packages for one device, version independent |
| `.generated/` | no | combined package lists and saved buildinfo (build artefacts) |
## Requirements
- Linux with one of: apt (Debian/Ubuntu/Mint), dnf/yum (Fedora/RHEL/Rocky/Alma),
pacman (Arch/Manjaro), zypper (openSUSE), apk (Alpine)
- x86_64/amd64 or aarch64/arm64
- git, curl, wget, bash, python3 (used to read the upstream JSON indexes; jq is
not required)
- Internet access, and roughly 30 GB of disk for a full build
## Quick start ## Quick start
1) Prepare the host machine
- Run: ./prepare-openwrt-env.sh
- This installs compilers, headers, Python tooling, and other build prerequisites.
2) Get OpenWrt sources ```bash
- Run: ./prepare-openwrt.sh # 1. host dependencies
- Choose Stable, Beta, or Snapshot. ./prepare-openwrt-env.sh # -n to preview, --with-go for Go
- The script:
- Reuses or creates the top-level OpenWrt root at `./openwrt`.
- Moves this helper checkout to `./openwrt/helper` on first run.
- Updates/install feeds and runs make defconfig.
3) Add optional external packages # 2. OpenWrt source (relocates this checkout to openwrt/helper on first run)
- From the OpenWrt root: ./prepare-openwrt.sh # or --stable 25.12.5 -y
- ./helper/add-external-repos.sh
- This pulls extra LuCI packages into package/.
4) Import a device config.buildinfo (optional but convenient) # from here on, run everything from the OpenWrt root
- From the OpenWrt root: cd ../openwrt # or: cd openwrt
- ./helper/download-config.sh
- Pick a device, and the script writes .config and runs make defconfig.
5) Enable additional packages from a list (optional) # 3. the device's official configuration
- Create a file packages.txt containing package names (whitespace or newlines). ./helper/download-config.sh --device linksys_mx8500 -y
- From the OpenWrt root:
- ./helper/add-openwrt-packages.sh packages.txt
6) Configure Go bootstrap path (if needed) # 4. built-in packages + your own, applied to .config
- From the OpenWrt root: ./helper/gen-package-list.sh --device linksys_mx8500 --apply -y
- ./helper/update-go-path.sh
- This sets CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT to the latest /usr/lib/go-X.XX/ found.
- You can override it manually in .config if necessary.
7) Apply DAHDI patch (only if you build telephony/dahdi-linux) # 5. build
- Ensure telephony feed is installed: make -j$(nproc) download world
- ./scripts/feeds update telephony ```
- ./scripts/feeds install -a
- Then run:
- ./helper/config_24.10.4/apply-dahdi-patches.sh
- Build the package:
- make package/feeds/telephony/dahdi-linux/{clean,prepare} V=s
- make package/feeds/telephony/dahdi-linux/compile V=s
8) Build OpenWrt Firmware lands in `bin/targets/<target>/<subtarget>/`.
- Common commands:
- make menuconfig
- make -j$(nproc) download world
- Artifacts will be in bin/ after the build completes.
## Script usage notes and tips Run `./helper/download-config.sh` without `--device` to search for a device by
- Run locations: name. Add `--with-packages` to fold step 4 into step 3.
- `prepare-openwrt.sh`: resolves paths from the script location, not your current shell directory.
- `prepare-openwrt-env.sh`: can be run before the first normalization from the fresh helper checkout. ### Adding a device
- All other helper scripts: run them from inside the OpenWrt source root as `./helper/...` unless otherwise indicated.
- Feeds: ```bash
- If a package cant be found, ensure feeds are updated/installed: ./helper/gen-package-list.sh --device <id> --init-extras # creates packages/<id>.txt
- ./scripts/feeds update -a && ./scripts/feeds install -a $EDITOR helper/packages/<id>.txt # list what you want
- Go toolchain: ./helper/gen-package-list.sh --device <id> --apply
- On x86_64, Go is optional unless your selection pulls packages needing Go (e.g., firewall4 in some versions). If needed: ```
- sudo apt install golang -y
- Then use update-go-path.sh to set .config automatically. ### Upgrading to a new OpenWrt release
- ARM64 multilib:
- The script installs cross multilib packages for better compatibility, but not all targets need them. If apt errors on specific multilib packages, remove or adjust as appropriate for your environment. Nothing to edit — the built-in list is re-fetched and `packages/<id>.txt` is
reused as is:
```bash
./helper/prepare-openwrt.sh --stable -y
cd ../openwrt
./helper/download-config.sh --device <id> -y
./helper/gen-package-list.sh --device <id> --apply -y
make -j$(nproc) download world
```
## Scripts
Every script takes `-h/--help` and prints its full option list; the summaries
below cover the options worth knowing about.
- **prepare-openwrt-env.sh** — installs the build dependencies from the official
build system guide. Dispatches on the package manager rather than guessing from
the distro version, and probes every package against the running release so a
renamed or dropped package cannot abort the install.
`-y`, `-n/--dry-run`, `--with-go`.
- **prepare-openwrt.sh** — clones or updates the OpenWrt source in `openwrt/`.
Resolves the version over `git ls-remote` and prints a plan before touching
anything, so aborting at the prompt leaves the checkout untouched. Uses a
partial clone (`--filter=blob:none`) by default and resumes an interrupted
first run automatically.
`--stable [<ver>]`, `--branch <name>`, `--snapshot`, `-y`, `-n`, `-f/--force`,
`--full-clone`, `--allow-rc`, `--root <dir>`, `--skip-feeds`,
`--skip-defconfig`, `--repo-url <url>`, `--no-compat-link`.
- **download-config.sh** — installs a device's official `config.buildinfo` as
`.config`. Looks the device up in the official index, so any supported device
can be selected by name. Downloads to a temp file and validates it before
touching `.config`, keeping a `.config.download.bak`. By default builds only
the selected device and strips the buildbot flags (see below).
`--device <id>`, `--target <t>`, `--version <ver>`, `--snapshot`,
`--with-packages`, `--all-profiles`, `--keep-buildbot-flags`,
`--save-buildinfo`, `--offline`, `--output <file>`, `--refresh`,
`--skip-defconfig`, `-y`, `-n`.
- **gen-package-list.sh** — builds the complete package list: the built-in part
from the target's `profiles.json` (`default_packages` + `device_packages` plus
the two packages the Firmware Selector adds), combined with your
`packages/<device-id>.txt`.
`--device <id>`, `--target <t>`, `--version <ver>`, `--snapshot`,
`--apply`, `--init-extras`, `--extras-file <f>`, `--no-extras-file`,
`--stdout`, `--full-stdout`, `--out <path>`, `--extras "<pkgs>"`,
`--no-extras`, `--wrap <cols>`, `--refresh`, `-y`, `-n`.
`--extras`/`--no-extras` control the two packages the Firmware Selector adds
on top of the target's own set; `--extras-file`/`--no-extras-file` control
your `packages/<device-id>.txt`. They are different things.
- **add-openwrt-packages.sh** — applies a package list to `.config`. Rewrites
existing `CONFIG_PACKAGE_` lines in place, so repeated runs do not grow the
file. After `make defconfig` it reports any package whose final state differs
from what you asked for.
`-y`, `-n`, `--no-defconfig`, `--no-backup`, `--strict`, and `-` to read stdin.
- **update-go-path.sh** — points the build at an installed Go toolchain.
`--external` clears `CONFIG_GOLANG_BUILD_BOOTSTRAP` so the installed toolchain
builds host Go directly instead of OpenWrt building the whole bootstrap chain
from source.
`--external`, `--go-root <dir>`, `--skip-defconfig`, `-n`.
- **add-external-repos.sh** — clones extra package repositories into `package/`.
Edit the `REPOS` array to change the list.
## Package list format
Used by both `packages/<device-id>.txt` and the generated lists:
```
curl yq luci-app-ttyd packages, any number per line, set to =y
-luci-app-wol a leading '-' removes a package (=n)
##module packages after this are set to =m
##remove packages after this are set to =n
##built-in back to =y (the default)
pkg # note text after a single '#' is a comment
```
## Notes and gotchas
### Buildbot flags
The official `config.buildinfo` is the buildbot's own configuration: it enables
every model in the target and sets `ALL_KMODS`, `ALL_NONSHARED`, `SDK`, `IB`,
`MAKE_TOOLCHAIN`, `COLLECT_KERNEL_DEBUG` and `AUTOREMOVE`. For a Linksys MX8500
that is 1248 extra module packages plus the SDK, ImageBuilder and a toolchain
tarball on every build.
`download-config.sh` turns these off by default. This was verified not to change
the firmware: the set of packages built into the image (`=y`) is identical either
way — 193 packages before and after. The trade-off is that `bin/` no longer
contains prebuilt packages for modules you did not select, so you cannot later
install an arbitrary kmod from your own build output.
`--keep-buildbot-flags` and `--all-profiles` restore upstream behaviour.
### Go packages
Packages such as tailscale, adguardhome, easytier and cloudflared need a host Go.
By default OpenWrt builds the entire bootstrap chain from source, which works but
is slow. To skip it, install a Go from your distribution (or from go.dev) at
least as new as the version the tree expects, then run:
```bash
./helper/update-go-path.sh --external
```
The script searches `/usr/lib/go-*`, `/usr/local/go` and `/usr/lib/golang`,
picks the newest, and warns if it is older than the bootstrap version the tree
asks for. Point it elsewhere with `--go-root <dir>`.
### WSL: Windows PATH breaks Go package builds
On WSL, `PATH` includes Windows directories containing spaces and parentheses
(`/mnt/c/Program Files (x86)/...`). OpenWrt's Go build recipe interpolates `PATH`
unquoted into a shell command, so every Go package fails with:
```
bash: -c: line 1: syntax error near unexpected token `('
```
Either build with a sanitized PATH:
```bash
CLEAN_PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '^/mnt/' | paste -sd:)
env PATH="$CLEAN_PATH" make -j$(nproc) download world
```
or disable the interop PATH permanently in `/etc/wsl.conf` and restart WSL
(`wsl --shutdown`):
```ini
[interop]
appendWindowsPath = false
```
### Hazard: `git clean -xdff`
`helper/` lives inside the OpenWrt Git worktree but is not tracked by it.
`prepare-openwrt.sh` adds `/helper/` to `openwrt/.git/info/exclude`, which keeps
`git status` clean and stops `git add -A` from staging it — but that does **not**
protect it from `git clean -xdff`: `-x` deliberately includes ignored files and
`-ff` removes Git's refusal to recurse into a nested repository. Use instead:
```bash
git clean -xdf -e /helper
```
## Troubleshooting ## Troubleshooting
- Telephony feed not found when applying DAHDI patch:
- Run feeds update/install for telephony as shown above.
- Version detection in download-config.sh:
- If youre on a branch like openwrt-24.10, the script will try to find the latest v24.10.x tag automatically. Otherwise it prompts for a version (e.g., 24.10.4 or SNAPSHOT).
- Missing packages in add-openwrt-packages.sh:
- Ensure the corresponding feed is enabled in feeds.conf.default and run feeds update/install.
- Permission errors:
- Scripts use umask 022 and do not require root except when installing apt packages.
- Clean up and rebuild a package:
- make package/<pkg>/{clean,compile} V=s
- Full rebuild:
- make clean; make -j$(nproc) download world
## Directory behavior - **Version detection in download-config.sh** — it uses, in order: an explicit
- If you start from a freshly cloned helper checkout at `<parent>/<origin_folder>`, `prepare-openwrt.sh` will: `--version`/`--snapshot`; the exact tag at HEAD; the branch (`main`/`master`
- Reuse or create `<parent>/openwrt/` as the OpenWrt source root. SNAPSHOT, `openwrt-XX.YY` the newest matching tag merged into HEAD);
- Move the entire helper checkout, including its `.git`, to `<parent>/openwrt/helper/`. otherwise it asks. If HEAD is ahead of the tag it warns that the downloaded
- Initialize or update the OpenWrt source tree directly in `<parent>/openwrt/`. config describes the release rather than your tree. Version queries are
- If you later rerun `openwrt/helper/prepare-openwrt.sh`, it reuses the parent `openwrt/` directory and does not move directories again. anchored to the OpenWrt tree, not to whichever repository the script sits in.
- If the script is already located in a real OpenWrt source root, it treats that directory as the source root and does not relocate it just because the directory name is `openwrt`. - **The device symbol did not survive make defconfig** — your tree does not match
the release you downloaded a config for. The script stops and points at the
`.config.download.bak` it kept.
- **A package is reported missing** — names are verified against
`tmp/.config-package.in` and `tmp/.packageinfo`. Enable the relevant feed in
`feeds.conf.default`, then `./scripts/feeds update -a && ./scripts/feeds install -a`.
- **A removed package (`-pkg`) comes back after make defconfig** — something else
depends on it. Reported explicitly as "wanted =n, got =y".
- **A package is in .config but not in the manifest** — it is probably there under
a provider name (`libgcc``libgcc1`, `nftables``nftables-json`).
- **Rebuild one package** — `make package/<pkg>/{clean,compile} V=s`
- **Full rebuild** — `make clean; make -j$(nproc) download world`
## Security and safety ## Safety
- Scripts use set -e to stop on errors.
- They modify .config and write under package/ and feeds/ inside your OpenWrt tree.
- Backup your .config if you need a known baseline.
## License and authorship - Scripts use `set -euo pipefail` and `umask 022`, and need root only to install
- Scripts authored by Zhe Yuan with help from ChatGPT. host packages.
- License: MIT (unless you choose a different license; update this line accordingly). - `.config` is backed up before it is replaced or before a version switch.
- `prepare-openwrt.sh` refuses to switch versions on a tree with modified tracked
files unless you pass `--force`.
- Scripts write under `package/` and `feeds/` inside your OpenWrt tree.
## Contributing and authorship
- Scripts authored by Zhe Yuan.
- No `LICENSE` file is present yet; add one before redistributing.
- [AGENTS.md](AGENTS.md) documents the layout, the shell conventions and the
invariants to preserve — read it before changing a script.

View File

@@ -4,6 +4,16 @@
set -e # Exit immediately if any command fails set -e # Exit immediately if any command fails
umask 022 # Set default file permissions (files=644, dirs=755) umask 022 # Set default file permissions (files=644, dirs=755)
# --- Check if inside OpenWRT source directory ---
# Every path below is relative to the current directory, so running this from
# the helper checkout would clone into helper/package/ instead of the real
# OpenWRT package/ directory.
if [ ! -f "feeds.conf.default" ] || [ ! -d "package" ]; then
echo "[ERROR] Must run inside OpenWRT root directory."
echo "[ERROR] From the OpenWrt root, run it as: ./helper/add-external-repos.sh"
exit 1
fi
# Define repositories in the format: "git_url branch local_dir" # Define repositories in the format: "git_url branch local_dir"
REPOS=( REPOS=(
"https://github.com/muink/luci-app-netspeedtest.git master package/luci-app-netspeedtest" "https://github.com/muink/luci-app-netspeedtest.git master package/luci-app-netspeedtest"

View File

@@ -1,162 +1,340 @@
#!/bin/bash #!/bin/bash
# Add specified OpenWRT packages (from a text file) to .config without removing existing ones # Apply a package list to the OpenWrt .config.
#
# The list is a plain text file of package names, whitespace separated, any
# number per line. Section headers switch how the following packages are
# applied, and a leading '-' on a name removes that package:
#
# ##built-in the packages after this are set to =y (default)
# ##module ... are set to =m
# ##remove ... are set to =n
# -luci-app-ttyd set to =n regardless of the current section
# pkg # comment text after a single # is ignored
#
# Existing CONFIG_PACKAGE_ lines are rewritten in place rather than appended,
# so running this repeatedly does not grow .config.
#
# Usage: ./add-openwrt-packages.sh [options] <package_list.txt | ->
#
# -y, --yes do not ask for confirmation
# -n, --dry-run show the resulting diff, change nothing
# --no-defconfig do not run make defconfig afterwards
# --no-backup do not keep a .config.bak
# --strict exit non-zero if any listed package was not found
# -h, --help show this help
#
# Run it from the OpenWrt root: ./helper/add-openwrt-packages.sh <list>
#
# Author: Zhe Yuan # Author: Zhe Yuan
# Usage:
# ./add-openwrt-packages.sh packages.txt
# Description:
# - Reads package names from a text file (supports both newline or space separation)
# - Supports ##built-in and ##module sections to control build mode (=y or =m)
# - Packages default to built-in (=y) if no section header is specified
# - Adds missing CONFIG_PACKAGE_xxx entries to .config
# - Keeps existing settings intact
# - Reports missing packages
# - Runs make defconfig at the end to sync dependencies
set -e set -euo pipefail
umask 022 umask 022
# Check argument PKG_FILE=""
if [ "$#" -ne 1 ]; then ASSUME_YES=false
echo "❌ Usage: $0 <package_list.txt>" DRY_RUN=false
RUN_DEFCONFIG=true
KEEP_BACKUP=true
STRICT=false
info() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*"; }
err() { echo "[ERROR] $*" >&2; }
usage() { sed -n '2,29p' "$0" | sed 's/^# \{0,1\}//;s/^#$//'; }
confirm() {
local question="$1" default="$2" reply=""
[ "$ASSUME_YES" = true ] && return 0
if [ "$default" = "yes" ]; then
read -r -p "$question [Y/n]: " reply || reply=""
case "${reply:-}" in [nN] | [nN][oO]) return 1 ;; *) return 0 ;; esac
else
read -r -p "$question [y/N]: " reply || reply=""
case "${reply:-}" in [yY] | [yY][eE][sS]) return 0 ;; *) return 1 ;; esac
fi
}
while [ $# -gt 0 ]; do
case "$1" in
-y|--yes) ASSUME_YES=true ;;
-n|--dry-run) DRY_RUN=true ;;
--no-defconfig) RUN_DEFCONFIG=false ;;
--no-backup) KEEP_BACKUP=false ;;
--strict) STRICT=true ;;
-h|--help) usage; exit 0 ;;
-) PKG_FILE="-" ;;
-*) err "Unknown option: $1"; echo "Run '$0 --help' for usage." >&2; exit 1 ;;
*)
if [ -n "$PKG_FILE" ]; then
err "Only one package list can be given."
exit 1 exit 1
fi fi
PKG_FILE="$1" PKG_FILE="$1"
;;
esac
shift
done
# Verify file exists [ -t 0 ] || ASSUME_YES=true
if [ ! -f "$PKG_FILE" ]; then
echo "❌ Error: File not found: $PKG_FILE" if [ -z "$PKG_FILE" ]; then
err "No package list given."
usage >&2
exit 1 exit 1
fi fi
# Verify we are in OpenWRT source root if [ "$PKG_FILE" != "-" ] && [ ! -f "$PKG_FILE" ]; then
if [ ! -d "package" ] || [ ! -f "Makefile" ]; then err "Package list not found: $PKG_FILE"
echo "❌ Error: Please run this script in the OpenWRT source root directory."
exit 1 exit 1
fi fi
# Ensure .config exists if [ ! -d package ] || [ ! -f Makefile ] || [ ! -f feeds.conf.default ]; then
if [ ! -f ".config" ]; then err "Must run inside the OpenWrt root directory."
echo "⚙️ Generating initial .config..." err "From the OpenWrt root, run it as: ./helper/$(basename "$0")"
make defconfig exit 1
fi
if [ ! -f .config ]; then
info "No .config yet; running make defconfig first..."
make defconfig >/dev/null
fi fi
PACKAGE_INDEX="tmp/.config-package.in" PACKAGE_INDEX="tmp/.config-package.in"
PACKAGE_INFO="tmp/.packageinfo" PACKAGE_INFO="tmp/.packageinfo"
if [ ! -f "$PACKAGE_INDEX" ] || [ ! -f "$PACKAGE_INFO" ]; then
# Ensure package metadata exists for reliable package name lookups. info "Generating package metadata..."
if [ ! -f "$PACKAGE_INDEX" ] && [ ! -f "$PACKAGE_INFO" ]; then
echo "⚙️ Generating package metadata..."
make defconfig >/dev/null make defconfig >/dev/null
fi fi
is_already_builtin() { # --- Parse the list ------------------------------------------------------
local pkg="$1" # Section headers are handled BEFORE stripping comments: the old
# `sub(/#[^#].*/,"")` left a bare '#' behind, which was then reported as a
awk -v package_y="CONFIG_PACKAGE_${pkg}=y" \ # missing package named '#'.
-v default_y="CONFIG_DEFAULT_${pkg}=y" \ parse_list() {
'$0 == package_y || $0 == default_y { found=1; exit } awk '
END { exit !found }' .config BEGIN { mode = "y" }
{
line = $0
if (line ~ /^[ \t]*##/) {
if (line ~ /^[ \t]*##built-in/) { mode = "y"; next }
if (line ~ /^[ \t]*##module/) { mode = "m"; next }
if (line ~ /^[ \t]*##remove/) { mode = "n"; next }
print "[WARN] unknown section header, ignored: " line > "/dev/stderr"
next
}
sub(/#.*$/, "", line)
n = split(line, t, /[ \t]+/)
for (i = 1; i <= n; i++) {
tok = t[i]; m = mode
if (tok == "") continue
if (substr(tok, 1, 1) == "-") { tok = substr(tok, 2); m = "n" }
if (tok == "") continue
print tok "\t" m
}
}
'
} }
is_module() { if [ "$PKG_FILE" = "-" ]; then
local pkg="$1" ENTRIES="$(parse_list)"
PKG_LABEL="(stdin)"
else
ENTRIES="$(parse_list < "$PKG_FILE")"
PKG_LABEL="$PKG_FILE"
fi
grep -qx "CONFIG_PACKAGE_${pkg}=m" .config if [ -z "$ENTRIES" ]; then
} err "No packages found in $PKG_LABEL."
exit 1
fi
# --- Read the current state once -----------------------------------------
declare -A CUR=()
declare -A DEF=()
while IFS= read -r line; do
case "$line" in
CONFIG_PACKAGE_*=*) CUR["${line%%=*}"]="${line#*=}" ;;
"# CONFIG_PACKAGE_"*" is not set")
s="${line#\# }"
CUR["${s% is not set}"]="n"
;;
CONFIG_DEFAULT_*=y) DEF["${line%%=*}"]="y" ;;
esac
done < .config
# --- Verify and classify --------------------------------------------------
package_exists() { package_exists() {
local pkg="$1" local pkg="$1"
if [ -f "$PACKAGE_INDEX" ] && if [ -f "$PACKAGE_INDEX" ] &&
awk -v key="PACKAGE_${pkg}" '$1 == "config" && $2 == key { found=1; exit } awk -v key="PACKAGE_$pkg" '$1 == "config" && $2 == key { found = 1; exit }
END { exit !found }' "$PACKAGE_INDEX"; then END { exit !found }' "$PACKAGE_INDEX"; then
return 0 return 0
fi fi
if [ -f "$PACKAGE_INFO" ] && grep -Fqx "Package: $pkg" "$PACKAGE_INFO"; then if [ -f "$PACKAGE_INFO" ] && grep -Fqx "Package: $pkg" "$PACKAGE_INFO"; then
return 0 return 0
fi fi
return 1 return 1
} }
echo "📦 Reading packages from $PKG_FILE ..." DESIRED="$(mktemp "${TMPDIR:-/tmp}/desired.XXXXXX")"
NEWCFG="$(mktemp ./.config.new.XXXXXX)"
trap 'rm -f "$DESIRED" "$NEWCFG"' EXIT
# Parse the file into "package:mode" pairs, respecting ##built-in / ##module sections. declare -a MISSING=() SKIPPED=() PLANNED=() INVALID=()
# Default mode is "y" (built-in). Inline comments (#xxx but not ##) are stripped.
ENTRIES=$(awk '
BEGIN { mode = "y" }
/^##built-in/ { mode = "y"; next }
/^##module/ { mode = "m"; next }
{
sub(/#[^#].*/, "") # strip inline comments (single #), preserve ## headers
for (i = 1; i <= NF; i++) print $i ":" mode
}
' "$PKG_FILE")
ADDED=() while IFS=$'\t' read -r pkg mode; do
UPGRADED=() [ -z "$pkg" ] && continue
MISSING=()
for entry in $ENTRIES; do if ! [[ "$pkg" =~ ^[A-Za-z0-9._+-]+$ ]]; then
pkg="${entry%:*}" INVALID+=("$pkg")
mode="${entry##*:}"
CONFIG_NAME="CONFIG_PACKAGE_${pkg}"
if [ "$mode" = "y" ]; then
# --- built-in mode ---
if is_already_builtin "$pkg"; then
echo "✅ Already built-in: $pkg"
continue continue
fi fi
if is_module "$pkg"; then sym="CONFIG_PACKAGE_$pkg"
sed -i "s/^CONFIG_PACKAGE_${pkg}=m$/CONFIG_PACKAGE_${pkg}=y/" .config cur="${CUR[$sym]:-}"
UPGRADED+=("$pkg")
echo "⬆️ Module → built-in: $pkg" # Already provided by the target defaults: nothing to write, no churn.
continue if [ "$mode" = "y" ] && [ -n "${DEF[CONFIG_DEFAULT_$pkg]:-}" ] && [ "$cur" != "m" ]; then
fi SKIPPED+=("$pkg (target default)")
else
# --- module mode ---
if is_module "$pkg"; then
echo "✅ Already module: $pkg"
continue continue
fi fi
if is_already_builtin "$pkg"; then if [ "$cur" = "$mode" ]; then
echo "✅ Already built-in (keeping): $pkg" SKIPPED+=("$pkg (already =$mode)")
continue continue
fi fi
fi
# Check if package exists in OpenWrt package metadata. # A removal of something that was never enabled needs no check.
if ! package_exists "$pkg"; then if [ "$mode" != "n" ] && ! package_exists "$pkg"; then
echo "⚠️ Package not found: $pkg"
MISSING+=("$pkg") MISSING+=("$pkg")
continue continue
fi fi
# Append package config printf '%s\t%s\n' "$sym" "$mode" >> "$DESIRED"
echo "${CONFIG_NAME}=${mode}" >> .config PLANNED+=("$pkg: ${cur:-unset} -> $mode")
ADDED+=("$pkg (=${mode})") done <<< "$ENTRIES"
echo " Added: $pkg (=${mode})"
done if [ ${#INVALID[@]} -gt 0 ]; then
warn "Ignored ${#INVALID[@]} entries with invalid package names:"
printf ' %s\n' "${INVALID[@]}"
fi
if [ ! -s "$DESIRED" ]; then
info "Nothing to change; .config already matches $PKG_LABEL."
if [ ${#MISSING[@]} -gt 0 ]; then
warn "Packages not found (check feeds or spelling):"
printf ' %s\n' "${MISSING[@]}"
warn "Try: ./scripts/feeds update -a && ./scripts/feeds install -a"
[ "$STRICT" = true ] && exit 1
fi
exit 0
fi
# --- Merge into .config ---------------------------------------------------
# Existing symbols are rewritten where they already are, so the file neither
# grows nor reorders; duplicates left behind by older versions collapse.
awk -F'\t' '
FNR == NR { want[$1] = $2; order[++nk] = $1; next }
{
key = ""
if ($0 ~ /^CONFIG_PACKAGE_[^=]+=/) { key = $0; sub(/=.*$/, "", key) }
else if ($0 ~ /^# CONFIG_PACKAGE_[^ ]+ is not set$/) {
# Extract by string, not by field: FS is a tab here, so $2 is empty
# on .config lines and the symbol would never be matched.
key = $0; sub(/^# /, "", key); sub(/ is not set$/, "", key)
}
if (key != "" && (key in want)) {
if (!(key in done)) {
done[key] = 1
if (want[key] == "n") print "# " key " is not set"
else print key "=" want[key]
}
next
}
print
}
END {
for (i = 1; i <= nk; i++) {
k = order[i]
if (k in done) continue
if (want[k] == "n") print "# " k " is not set"
else print k "=" want[k]
}
}
' "$DESIRED" .config > "$NEWCFG"
# Run defconfig to sync dependencies
echo echo
echo "⚙️ Running make defconfig..." echo "========================================"
echo " Planned .config changes"
echo "========================================"
printf ' %s\n' "${PLANNED[@]}"
echo " ---"
echo " change: ${#PLANNED[@]} unchanged: ${#SKIPPED[@]} not found: ${#MISSING[@]}"
echo "========================================"
if [ ${#MISSING[@]} -gt 0 ]; then
warn "Packages not found (check feeds or spelling):"
printf ' %s\n' "${MISSING[@]}"
warn "Try: ./scripts/feeds update -a && ./scripts/feeds install -a"
fi
if [ "$DRY_RUN" = true ]; then
echo
diff -u .config "$NEWCFG" || true
echo
info "Dry run complete; .config was not modified."
[ "$STRICT" = true ] && [ ${#MISSING[@]} -gt 0 ] && exit 1
exit 0
fi
echo
if ! confirm "Apply these changes to .config?" yes; then
info "Aborted by user."
exit 0
fi
[ "$KEEP_BACKUP" = true ] && cp -a .config .config.bak
mv -f "$NEWCFG" .config
chmod 644 .config
trap 'rm -f "$DESIRED"' EXIT
if [ "$RUN_DEFCONFIG" = true ]; then
info "Running make defconfig..."
make defconfig >/dev/null make defconfig >/dev/null
echo # defconfig silently reverts a removal whose package something else selects,
echo "✅ Done!" # and can demote =y to =m. Without this check that stays invisible.
echo "📋 Packages newly added: ${#ADDED[@]}" declare -a REVERTED=()
[ ${#ADDED[@]} -gt 0 ] && echo "${ADDED[*]}" declare -A FINAL=()
echo "📋 Packages upgraded to built-in: ${#UPGRADED[@]}" while IFS= read -r line; do
[ ${#UPGRADED[@]} -gt 0 ] && echo "${UPGRADED[*]}" case "$line" in
if [ ${#MISSING[@]} -gt 0 ]; then CONFIG_PACKAGE_*=*) FINAL["${line%%=*}"]="${line#*=}" ;;
echo "⚠️ Packages not found (check feeds or spelling):" "# CONFIG_PACKAGE_"*" is not set")
for m in "${MISSING[@]}"; do s="${line#\# }"
echo " - $m" FINAL["${s% is not set}"]="n"
done ;;
esac
done < .config
while IFS=$'\t' read -r sym mode; do
got="${FINAL[$sym]:-unset}"
[ "$got" = "$mode" ] || REVERTED+=("${sym#CONFIG_PACKAGE_}: wanted =$mode, got =$got")
done < "$DESIRED"
if [ ${#REVERTED[@]} -gt 0 ]; then
warn "make defconfig changed ${#REVERTED[@]} of the requested states:"
printf ' %s\n' "${REVERTED[@]}"
warn "Usually a dependency pulled the package back in."
fi
fi
echo
echo "========================================"
echo "OK Applied ${#PLANNED[@]} change(s) from $PKG_LABEL"
[ "$KEEP_BACKUP" = true ] && echo " Backup: .config.bak"
echo "========================================"
if [ "$STRICT" = true ] && [ ${#MISSING[@]} -gt 0 ]; then
exit 1
fi fi

View File

@@ -1,134 +0,0 @@
#!/bin/bash
# Add specified OpenWRT packages (from a text file) to .config without removing existing ones
# Author: Zhe Yuan
# Usage:
# ./add-openwrt-packages.sh packages.txt
# Description:
# - Reads package names from a text file (supports both newline or space separation)
# - Adds missing CONFIG_PACKAGE_xxx=y entries to .config
# - Keeps existing settings intact
# - Reports missing packages
# - Runs make defconfig at the end to sync dependencies
set -e
umask 022
# Check argument
if [ "$#" -ne 1 ]; then
echo "❌ Usage: $0 <package_list.txt>"
exit 1
fi
PKG_FILE="$1"
# Verify file exists
if [ ! -f "$PKG_FILE" ]; then
echo "❌ Error: File not found: $PKG_FILE"
exit 1
fi
# Verify we are in OpenWRT source root
if [ ! -d "package" ] || [ ! -f "Makefile" ]; then
echo "❌ Error: Please run this script in the OpenWRT source root directory."
exit 1
fi
# Ensure .config exists
if [ ! -f ".config" ]; then
echo "⚙️ Generating initial .config..."
make defconfig
fi
PACKAGE_INDEX="tmp/.config-package.in"
PACKAGE_INFO="tmp/.packageinfo"
# Ensure package metadata exists for reliable package name lookups.
if [ ! -f "$PACKAGE_INDEX" ] && [ ! -f "$PACKAGE_INFO" ]; then
echo "⚙️ Generating package metadata..."
make defconfig >/dev/null
fi
is_already_builtin() {
local pkg="$1"
awk -v package_y="CONFIG_PACKAGE_${pkg}=y" \
-v default_y="CONFIG_DEFAULT_${pkg}=y" \
'$0 == package_y || $0 == default_y { found=1; exit }
END { exit !found }' .config
}
is_module() {
local pkg="$1"
grep -qx "CONFIG_PACKAGE_${pkg}=m" .config
}
package_exists() {
local pkg="$1"
if [ -f "$PACKAGE_INDEX" ] &&
awk -v key="PACKAGE_${pkg}" '$1 == "config" && $2 == key { found=1; exit }
END { exit !found }' "$PACKAGE_INDEX"; then
return 0
fi
if [ -f "$PACKAGE_INFO" ] && grep -Fqx "Package: $pkg" "$PACKAGE_INFO"; then
return 0
fi
return 1
}
echo "📦 Reading packages from $PKG_FILE ..."
# Read package tokens from file, while allowing inline comments.
PACKAGES=$(awk '{ sub(/#.*/, ""); for (i = 1; i <= NF; i++) print $i }' "$PKG_FILE")
ADDED=()
MISSING=()
for pkg in $PACKAGES; do
CONFIG_NAME="CONFIG_PACKAGE_${pkg}"
# Skip if already built-in
if is_already_builtin "$pkg"; then
echo "✅ Already built-in: $pkg"
continue
fi
# Upgrade module to built-in
if is_module "$pkg"; then
sed -i "s/^CONFIG_PACKAGE_${pkg}=m$/CONFIG_PACKAGE_${pkg}=y/" .config
ADDED+=("$pkg")
echo "⬆️ Module → built-in: $pkg"
continue
fi
# Check if package exists in OpenWrt package metadata.
if ! package_exists "$pkg"; then
echo "⚠️ Package not found: $pkg"
MISSING+=("$pkg")
continue
fi
# Append package config
echo "${CONFIG_NAME}=y" >> .config
ADDED+=("$pkg")
echo " Added: $pkg"
done
# Run defconfig to sync dependencies
echo
echo "⚙️ Running make defconfig..."
make defconfig >/dev/null
echo
echo "✅ Done!"
echo "📋 Packages newly added: ${#ADDED[@]}"
[ ${#ADDED[@]} -gt 0 ] && echo "${ADDED[*]}"
if [ ${#MISSING[@]} -gt 0 ]; then
echo "⚠️ Packages not found (check feeds or spelling):"
for m in "${MISSING[@]}"; do
echo " - $m"
done
fi

View File

@@ -1,148 +0,0 @@
#!/bin/bash
# Apply DAHDI driver patch for OpenWRT 24.10.4
# Fixes MAX macro naming issues in dahdi-linux modules
# Author: ChatGPT (for Zhe Yuan)
set -e
umask 022
PATCH_DIR="package/feeds/telephony/dahdi-linux/patches"
PATCH_FILE="$PATCH_DIR/300-fix-dahdi-max-attempts.patch"
echo "========================================"
echo " DAHDI Linux Patch Applier for OpenWRT"
echo "========================================"
# --- Check if inside OpenWRT source directory ---
if [ ! -f "feeds.conf.default" ] || [ ! -d "package" ]; then
echo "[ERROR] This script must be run inside the OpenWRT root directory."
echo "Please 'cd' to your OpenWRT source root and run again."
exit 1
fi
# --- Check if dahdi patch directory exists ---
if [ ! -d "$PATCH_DIR" ]; then
echo "[ERROR] Patch directory not found:"
echo " $PATCH_DIR"
echo
echo "Please make sure telephony feed is installed:"
echo " ./scripts/feeds update telephony"
echo " ./scripts/feeds install -a"
exit 1
fi
# --- Create patch file ---
echo "[INFO] Writing DAHDI patch to: $PATCH_FILE"
cat > "$PATCH_FILE" <<'EOF'
--- a/drivers/dahdi/wctdm24xxp/base.c
+++ b/drivers/dahdi/wctdm24xxp/base.c
@@ -1516,16 +1516,16 @@ static int wait_access(struct wctdm *wc, struct wctdm_module *const mod)
{
unsigned char data = 0;
int count = 0;
- #define MAX 10 /* attempts */
+ #define MAX_ATTEMPTS 10 /* attempts */
/* Wait for indirect access */
- while (count++ < MAX) {
+ while (count++ < MAX_ATTEMPTS) {
data = wctdm_getreg(wc, mod, I_STATUS);
if (!data)
return 0;
}
- if (count > (MAX-1)) {
+ if (count > (MAX_ATTEMPTS-1)) {
dev_notice(&wc->vb.pdev->dev,
" ##### Loop error (%02x) #####\n", data);
}
--- a/drivers/dahdi/opvxa1200/base.c
+++ b/drivers/dahdi/opvxa1200/base.c
@@ -868,12 +868,12 @@ static int wait_access(struct wctdm *wc, int card)
long origjiffies;
int count = 0;
- #define MAX 6000 /* attempts */
+ #define MAX_ATTEMPTS 6000 /* attempts */
origjiffies = jiffies;
/* Wait for indirect access */
- while (count++ < MAX)
+ while (count++ < MAX_ATTEMPTS)
{
data = __wctdm_getreg(wc, card, I_STATUS);
@@ -882,7 +882,7 @@ static int wait_access(struct wctdm *wc, int card)
}
- if(count > (MAX-1)) printk(KERN_NOTICE " ##### Loop error (%02x) #####\n", data);
+ if(count > (MAX_ATTEMPTS-1)) printk(KERN_NOTICE " ##### Loop error (%02x) #####\n", data);
return 0;
}
--- a/drivers/dahdi/wcaxx-base.c
+++ b/drivers/dahdi/wcaxx-base.c
@@ -1066,16 +1066,16 @@ static int wait_access(struct wcaxx *wc, struct wcaxx_module *const mod)
unsigned char data = 0;
int count = 0;
- #define MAX 10 /* attempts */
+ #define MAX_ATTEMPTS 10 /* attempts */
/* Wait for indirect access */
- while (count++ < MAX) {
+ while (count++ < MAX_ATTEMPTS) {
data = wcaxx_getreg(wc, mod, I_STATUS);
if (!data)
return 0;
}
- if (count > (MAX-1)) {
+ if (count > (MAX_ATTEMPTS-1)) {
dev_notice(&wc->xb.pdev->dev,
" ##### Loop error (%02x) #####\n", data);
}
--- a/drivers/dahdi/wctdm.c
+++ b/drivers/dahdi/wctdm.c
@@ -623,11 +623,11 @@ static int wait_access(struct wctdm *wc, int card)
unsigned char data = 0;
int count = 0;
- #define MAX 6000 /* attempts */
+ #define MAX_ATTEMPTS 6000 /* attempts */
/* Wait for indirect access */
- while (count++ < MAX)
+ while (count++ < MAX_ATTEMPTS)
{
data = __wctdm_getreg(wc, card, I_STATUS);
@@ -636,7 +636,7 @@ static int wait_access(struct wctdm *wc, int card)
}
- if(count > (MAX-1)) printk(KERN_NOTICE " ##### Loop error (%02x) #####\n", data);
+ if(count > (MAX_ATTEMPTS-1)) printk(KERN_NOTICE " ##### Loop error (%02x) #####\n", data);
return 0;
}
EOF
echo "[SUCCESS] Patch written successfully!"
echo
echo "========================================"
echo "✅ DAHDI patch applied successfully!"
echo " File: $PATCH_FILE"
echo "========================================"
echo
echo "👉 Next step:"
echo " make package/feeds/telephony/dahdi-linux/{clean,prepare} V=s"
echo " make package/feeds/telephony/dahdi-linux/compile V=s"
echo

View File

@@ -1,193 +0,0 @@
CONFIG_TARGET_mediatek=y
CONFIG_TARGET_mediatek_filogic=y
CONFIG_TARGET_mediatek_filogic_DEVICE_cudy_tr3000-v1=y
CONFIG_ALL_KMODS=y
CONFIG_ALL_NONSHARED=y
CONFIG_DEVEL=y
CONFIG_AUTOREMOVE=y
CONFIG_BUILDBOT=y
CONFIG_COLLECT_KERNEL_DEBUG=y
CONFIG_EASYTIER_INCLUDE_WEBCONSOLE=y
CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT="/usr/lib/go-1.22/"
CONFIG_IB=y
CONFIG_IMAGEOPT=y
CONFIG_JSON_CYCLONEDX_SBOM=y
CONFIG_KERNEL_BUILD_DOMAIN="buildhost"
CONFIG_KERNEL_BUILD_USER="builder"
# CONFIG_KERNEL_KALLSYMS is not set
CONFIG_LIBCURL_COOKIES=y
CONFIG_LIBCURL_FILE=y
CONFIG_LIBCURL_FTP=y
CONFIG_LIBCURL_HTTP=y
CONFIG_LIBCURL_MBEDTLS=y
CONFIG_LIBCURL_NGHTTP2=y
CONFIG_LIBCURL_NO_SMB="!"
CONFIG_LIBCURL_PROXY=y
CONFIG_LIBCURL_UNIX_SOCKETS=y
CONFIG_MAKE_TOOLCHAIN=y
CONFIG_OPENSSL_ENGINE=y
CONFIG_OPENSSL_WITH_ASM=y
CONFIG_OPENSSL_WITH_CHACHA_POLY1305=y
CONFIG_OPENSSL_WITH_CMS=y
CONFIG_OPENSSL_WITH_DEPRECATED=y
CONFIG_OPENSSL_WITH_ERROR_MESSAGES=y
CONFIG_OPENSSL_WITH_IDEA=y
CONFIG_OPENSSL_WITH_MDC2=y
CONFIG_OPENSSL_WITH_PSK=y
CONFIG_OPENSSL_WITH_SEED=y
CONFIG_OPENSSL_WITH_SRP=y
CONFIG_OPENSSL_WITH_TLS13=y
CONFIG_OPENSSL_WITH_WHIRLPOOL=y
CONFIG_PACKAGE_adguardhome=y
CONFIG_PACKAGE_ca-certificates=y
CONFIG_PACKAGE_cgi-io=y
CONFIG_PACKAGE_curl=y
CONFIG_PACKAGE_e2fsprogs=m
CONFIG_PACKAGE_easytier=y
CONFIG_PACKAGE_etherwake=y
CONFIG_PACKAGE_f2fsck=m
CONFIG_PACKAGE_fdisk=m
CONFIG_PACKAGE_ip-full=y
CONFIG_PACKAGE_ip6tables-nft=y
CONFIG_PACKAGE_iperf3=y
CONFIG_PACKAGE_ipset=y
CONFIG_PACKAGE_iptables-mod-conntrack-extra=y
CONFIG_PACKAGE_iptables-mod-ipopt=y
CONFIG_PACKAGE_iptables-nft=y
CONFIG_PACKAGE_kmod-inet-diag=y
CONFIG_PACKAGE_kmod-ip6tables=y
CONFIG_PACKAGE_kmod-ipt-conntrack=y
CONFIG_PACKAGE_kmod-ipt-conntrack-extra=y
CONFIG_PACKAGE_kmod-ipt-core=y
CONFIG_PACKAGE_kmod-ipt-ipopt=y
CONFIG_PACKAGE_kmod-ipt-ipset=y
CONFIG_PACKAGE_kmod-nf-conncount=y
CONFIG_PACKAGE_kmod-nf-ipt=y
CONFIG_PACKAGE_kmod-nf-ipt6=y
CONFIG_PACKAGE_kmod-nf-socket=y
CONFIG_PACKAGE_kmod-nf-tproxy=y
CONFIG_PACKAGE_kmod-nft-compat=y
CONFIG_PACKAGE_kmod-nft-socket=y
CONFIG_PACKAGE_kmod-nft-tproxy=y
CONFIG_PACKAGE_kmod-tun=y
CONFIG_PACKAGE_libatomic=y
CONFIG_PACKAGE_libblkid=m
CONFIG_PACKAGE_libbpf=y
CONFIG_PACKAGE_libbz2=y
CONFIG_PACKAGE_libcap=y
CONFIG_PACKAGE_libcap-ng=y
CONFIG_PACKAGE_libcomerr=m
CONFIG_PACKAGE_libcurl=y
CONFIG_PACKAGE_libe2p=m
CONFIG_PACKAGE_libelf=y
CONFIG_PACKAGE_libext2fs=m
CONFIG_PACKAGE_libf2fs=m
CONFIG_PACKAGE_libfdisk=m
CONFIG_PACKAGE_libiperf3=y
CONFIG_PACKAGE_libipset=y
CONFIG_PACKAGE_libiptext=y
CONFIG_PACKAGE_libiptext-nft=y
CONFIG_PACKAGE_libiptext6=y
CONFIG_PACKAGE_liblua=y
CONFIG_PACKAGE_liblucihttp=y
CONFIG_PACKAGE_liblucihttp-lua=y
CONFIG_PACKAGE_liblucihttp-ucode=y
CONFIG_PACKAGE_libminiupnpc=y
CONFIG_PACKAGE_libnatpmp=y
CONFIG_PACKAGE_libncurses=m
CONFIG_PACKAGE_libnghttp2=y
CONFIG_PACKAGE_libopenssl=y
CONFIG_PACKAGE_libpython3=y
CONFIG_PACKAGE_librespeed-go=y
CONFIG_PACKAGE_librt=y
CONFIG_PACKAGE_libsmartcols=m
CONFIG_PACKAGE_libss=m
CONFIG_PACKAGE_libstdcpp=y
CONFIG_PACKAGE_libubus-lua=y
CONFIG_PACKAGE_libuuid=y
CONFIG_PACKAGE_libuv=y
CONFIG_PACKAGE_libwebsockets-full=y
CONFIG_PACKAGE_libxtables=y
CONFIG_PACKAGE_lua=y
CONFIG_PACKAGE_luci=y
CONFIG_PACKAGE_luci-app-easytier=y
CONFIG_PACKAGE_luci-app-filemanager=y
CONFIG_PACKAGE_luci-app-firewall=y
CONFIG_PACKAGE_luci-app-mwan3=y
CONFIG_PACKAGE_luci-app-netspeedtest=y
CONFIG_PACKAGE_luci-app-package-manager=y
CONFIG_PACKAGE_luci-app-ttyd=y
CONFIG_PACKAGE_luci-app-upnp=y
CONFIG_PACKAGE_luci-app-wol=y
CONFIG_PACKAGE_luci-base=y
CONFIG_PACKAGE_luci-compat=y
CONFIG_PACKAGE_luci-lib-base=y
CONFIG_PACKAGE_luci-lib-ip=y
CONFIG_PACKAGE_luci-lib-jsonc=y
CONFIG_PACKAGE_luci-lib-nixio=y
CONFIG_PACKAGE_luci-light=y
CONFIG_PACKAGE_luci-lua-runtime=y
CONFIG_PACKAGE_luci-mod-admin-full=y
CONFIG_PACKAGE_luci-mod-network=y
CONFIG_PACKAGE_luci-mod-status=y
CONFIG_PACKAGE_luci-mod-system=y
CONFIG_PACKAGE_luci-proto-ipv6=y
CONFIG_PACKAGE_luci-proto-ppp=y
CONFIG_PACKAGE_luci-ssl=y
CONFIG_PACKAGE_luci-theme-bootstrap=y
CONFIG_PACKAGE_miniupnpd-nftables=y
CONFIG_PACKAGE_mkf2fs=m
CONFIG_PACKAGE_mt7986-wo-firmware=m
CONFIG_PACKAGE_mt7988-2p5g-phy-firmware=m
CONFIG_PACKAGE_mt7988-wo-firmware=m
CONFIG_PACKAGE_mwan3=y
CONFIG_PACKAGE_partx-utils=m
CONFIG_PACKAGE_px5g-mbedtls=y
CONFIG_PACKAGE_python3-base=y
CONFIG_PACKAGE_python3-codecs=y
CONFIG_PACKAGE_python3-email=y
CONFIG_PACKAGE_python3-light=y
CONFIG_PACKAGE_python3-logging=y
CONFIG_PACKAGE_python3-openssl=y
CONFIG_PACKAGE_python3-pkg-resources=y
CONFIG_PACKAGE_python3-speedtest-cli=y
CONFIG_PACKAGE_python3-urllib=y
CONFIG_PACKAGE_python3-xml=y
CONFIG_PACKAGE_rpcd=y
CONFIG_PACKAGE_rpcd-mod-file=y
CONFIG_PACKAGE_rpcd-mod-iwinfo=y
CONFIG_PACKAGE_rpcd-mod-luci=y
CONFIG_PACKAGE_rpcd-mod-rrdns=y
CONFIG_PACKAGE_rpcd-mod-ucode=y
CONFIG_PACKAGE_tailscale=y
CONFIG_PACKAGE_terminfo=m
CONFIG_PACKAGE_ttyd=y
CONFIG_PACKAGE_ucode-mod-html=y
CONFIG_PACKAGE_ucode-mod-lua=y
CONFIG_PACKAGE_ucode-mod-math=y
CONFIG_PACKAGE_uhttpd=y
CONFIG_PACKAGE_uhttpd-mod-ubus=y
CONFIG_PACKAGE_unzip=y
CONFIG_PACKAGE_wpad=y
# CONFIG_PACKAGE_wpad-basic-mbedtls is not set
CONFIG_PACKAGE_xtables-nft=y
CONFIG_PACKAGE_yq=y
CONFIG_PACKAGE_zerotier=y
CONFIG_PACKAGE_zlib=y
CONFIG_PACKAGE_zyxel-bootconfig=m
CONFIG_REPRODUCIBLE_DEBUG_INFO=y
CONFIG_SDK=y
CONFIG_SDK_LLVM_BPF=y
CONFIG_VERSIONOPT=y
CONFIG_VERSION_BUG_URL=""
CONFIG_VERSION_CODE=""
CONFIG_VERSION_DIST="OpenWrt"
CONFIG_VERSION_FILENAMES=y
CONFIG_VERSION_HOME_URL=""
CONFIG_VERSION_HWREV=""
CONFIG_VERSION_MANUFACTURER=""
CONFIG_VERSION_MANUFACTURER_URL=""
CONFIG_VERSION_NUMBER=""
CONFIG_VERSION_PRODUCT=""
CONFIG_VERSION_REPO="https://downloads.openwrt.org/releases/24.10.4"
CONFIG_VERSION_SUPPORT_URL=""

View File

@@ -1,196 +0,0 @@
CONFIG_TARGET_qualcommax=y
CONFIG_TARGET_qualcommax_ipq807x=y
CONFIG_TARGET_qualcommax_ipq807x_DEVICE_linksys_mx8500=y
CONFIG_ALL_KMODS=y
CONFIG_ALL_NONSHARED=y
CONFIG_DEVEL=y
CONFIG_AUTOREMOVE=y
CONFIG_BUILDBOT=y
CONFIG_COLLECT_KERNEL_DEBUG=y
CONFIG_EASYTIER_INCLUDE_WEBCONSOLE=y
CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT="/usr/lib/go-1.22/"
CONFIG_IB=y
CONFIG_IMAGEOPT=y
CONFIG_JSON_CYCLONEDX_SBOM=y
CONFIG_KERNEL_BUILD_DOMAIN="buildhost"
CONFIG_KERNEL_BUILD_USER="builder"
# CONFIG_KERNEL_KALLSYMS is not set
CONFIG_LIBCURL_COOKIES=y
CONFIG_LIBCURL_FILE=y
CONFIG_LIBCURL_FTP=y
CONFIG_LIBCURL_HTTP=y
CONFIG_LIBCURL_MBEDTLS=y
CONFIG_LIBCURL_NGHTTP2=y
CONFIG_LIBCURL_NO_SMB="!"
CONFIG_LIBCURL_PROXY=y
CONFIG_LIBCURL_UNIX_SOCKETS=y
CONFIG_MAKE_TOOLCHAIN=y
CONFIG_OPENSSL_ENGINE=y
CONFIG_OPENSSL_WITH_ASM=y
CONFIG_OPENSSL_WITH_CHACHA_POLY1305=y
CONFIG_OPENSSL_WITH_CMS=y
CONFIG_OPENSSL_WITH_DEPRECATED=y
CONFIG_OPENSSL_WITH_ERROR_MESSAGES=y
CONFIG_OPENSSL_WITH_IDEA=y
CONFIG_OPENSSL_WITH_MDC2=y
CONFIG_OPENSSL_WITH_PSK=y
CONFIG_OPENSSL_WITH_SEED=y
CONFIG_OPENSSL_WITH_SRP=y
CONFIG_OPENSSL_WITH_TLS13=y
CONFIG_OPENSSL_WITH_WHIRLPOOL=y
CONFIG_PACKAGE_adguardhome=y
CONFIG_PACKAGE_ath10k-board-qca9887=m
CONFIG_PACKAGE_ath10k-board-qca9984=m
CONFIG_PACKAGE_ath10k-firmware-qca9887-ct=m
CONFIG_PACKAGE_ath10k-firmware-qca9984-ct=m
CONFIG_PACKAGE_ca-certificates=y
CONFIG_PACKAGE_cgi-io=y
CONFIG_PACKAGE_cloudflared=y
CONFIG_PACKAGE_curl=y
CONFIG_PACKAGE_easytier=y
CONFIG_PACKAGE_etherwake=y
CONFIG_PACKAGE_f2fs-tools=m
CONFIG_PACKAGE_f2fsck=m
CONFIG_PACKAGE_ip-full=y
CONFIG_PACKAGE_ip-tiny=y
CONFIG_PACKAGE_ip6tables-nft=y
CONFIG_PACKAGE_iperf3=y
CONFIG_PACKAGE_ipset=y
CONFIG_PACKAGE_iptables-mod-conntrack-extra=y
CONFIG_PACKAGE_iptables-mod-ipopt=y
CONFIG_PACKAGE_iptables-nft=y
CONFIG_PACKAGE_kmod-crypto-lib-chacha20=y
CONFIG_PACKAGE_kmod-crypto-lib-chacha20poly1305=y
CONFIG_PACKAGE_kmod-crypto-lib-curve25519=y
CONFIG_PACKAGE_kmod-crypto-lib-poly1305=y
CONFIG_PACKAGE_kmod-inet-diag=y
CONFIG_PACKAGE_kmod-ip6tables=y
CONFIG_PACKAGE_kmod-ipt-conntrack=y
CONFIG_PACKAGE_kmod-ipt-conntrack-extra=y
CONFIG_PACKAGE_kmod-ipt-core=y
CONFIG_PACKAGE_kmod-ipt-ipopt=y
CONFIG_PACKAGE_kmod-ipt-ipset=y
CONFIG_PACKAGE_kmod-mtd-rw=y
CONFIG_PACKAGE_kmod-nf-conncount=y
CONFIG_PACKAGE_kmod-nf-ipt=y
CONFIG_PACKAGE_kmod-nf-ipt6=y
CONFIG_PACKAGE_kmod-nf-socket=y
CONFIG_PACKAGE_kmod-nf-tproxy=y
CONFIG_PACKAGE_kmod-nft-compat=y
CONFIG_PACKAGE_kmod-nft-socket=y
CONFIG_PACKAGE_kmod-nft-tproxy=y
CONFIG_PACKAGE_kmod-tun=y
CONFIG_PACKAGE_kmod-udptunnel4=y
CONFIG_PACKAGE_kmod-udptunnel6=y
CONFIG_PACKAGE_kmod-wireguard=y
CONFIG_PACKAGE_libatomic=y
CONFIG_PACKAGE_libbpf=y
CONFIG_PACKAGE_libbz2=y
CONFIG_PACKAGE_libcap=y
CONFIG_PACKAGE_libcap-ng=y
CONFIG_PACKAGE_libcurl=y
CONFIG_PACKAGE_libelf=y
CONFIG_PACKAGE_libf2fs=m
CONFIG_PACKAGE_libiperf3=y
CONFIG_PACKAGE_libipset=y
CONFIG_PACKAGE_libiptext=y
CONFIG_PACKAGE_libiptext-nft=y
CONFIG_PACKAGE_libiptext6=y
CONFIG_PACKAGE_liblua=y
CONFIG_PACKAGE_liblucihttp=y
CONFIG_PACKAGE_liblucihttp-lua=y
CONFIG_PACKAGE_liblucihttp-ucode=y
CONFIG_PACKAGE_libminiupnpc=y
CONFIG_PACKAGE_libnatpmp=y
CONFIG_PACKAGE_libnghttp2=y
CONFIG_PACKAGE_libopenssl=y
CONFIG_PACKAGE_libpython3=y
CONFIG_PACKAGE_libqrencode=y
CONFIG_PACKAGE_librespeed-go=y
CONFIG_PACKAGE_libstdcpp=y
CONFIG_PACKAGE_libubus-lua=y
CONFIG_PACKAGE_libuv=y
CONFIG_PACKAGE_libwebsockets-full=y
CONFIG_PACKAGE_libxtables=y
CONFIG_PACKAGE_lua=y
CONFIG_PACKAGE_luci=y
CONFIG_PACKAGE_luci-app-cloudflared=y
CONFIG_PACKAGE_luci-app-easytier=y
CONFIG_PACKAGE_luci-app-filemanager=y
CONFIG_PACKAGE_luci-app-firewall=y
CONFIG_PACKAGE_luci-app-mwan3=y
CONFIG_PACKAGE_luci-app-netspeedtest=y
CONFIG_PACKAGE_luci-app-openvpn=y
CONFIG_PACKAGE_luci-app-package-manager=y
CONFIG_PACKAGE_luci-app-ttyd=y
CONFIG_PACKAGE_luci-app-upnp=y
CONFIG_PACKAGE_luci-app-wol=y
CONFIG_PACKAGE_luci-base=y
CONFIG_PACKAGE_luci-compat=y
CONFIG_PACKAGE_luci-lib-base=y
CONFIG_PACKAGE_luci-lib-ip=y
CONFIG_PACKAGE_luci-lib-jsonc=y
CONFIG_PACKAGE_luci-lib-nixio=y
CONFIG_PACKAGE_luci-lib-uqr=y
CONFIG_PACKAGE_luci-light=y
CONFIG_PACKAGE_luci-lua-runtime=y
CONFIG_PACKAGE_luci-mod-admin-full=y
CONFIG_PACKAGE_luci-mod-network=y
CONFIG_PACKAGE_luci-mod-status=y
CONFIG_PACKAGE_luci-mod-system=y
CONFIG_PACKAGE_luci-proto-ipv6=y
CONFIG_PACKAGE_luci-proto-ppp=y
CONFIG_PACKAGE_luci-proto-wireguard=y
CONFIG_PACKAGE_luci-ssl=y
CONFIG_PACKAGE_luci-theme-bootstrap=y
CONFIG_PACKAGE_miniupnpd-nftables=y
CONFIG_PACKAGE_mkf2fs=m
CONFIG_PACKAGE_mwan3=y
CONFIG_PACKAGE_px5g-mbedtls=y
CONFIG_PACKAGE_python3-base=y
CONFIG_PACKAGE_python3-codecs=y
CONFIG_PACKAGE_python3-email=y
CONFIG_PACKAGE_python3-light=y
CONFIG_PACKAGE_python3-logging=y
CONFIG_PACKAGE_python3-openssl=y
CONFIG_PACKAGE_python3-pkg-resources=y
CONFIG_PACKAGE_python3-speedtest-cli=y
CONFIG_PACKAGE_python3-urllib=y
CONFIG_PACKAGE_python3-xml=y
CONFIG_PACKAGE_qrencode=y
CONFIG_PACKAGE_resolveip=y
CONFIG_PACKAGE_rpcd=y
CONFIG_PACKAGE_rpcd-mod-file=y
CONFIG_PACKAGE_rpcd-mod-iwinfo=y
CONFIG_PACKAGE_rpcd-mod-luci=y
CONFIG_PACKAGE_rpcd-mod-rrdns=y
CONFIG_PACKAGE_rpcd-mod-ucode=y
CONFIG_PACKAGE_tailscale=y
CONFIG_PACKAGE_ttyd=y
CONFIG_PACKAGE_ucode-mod-html=y
CONFIG_PACKAGE_ucode-mod-lua=y
CONFIG_PACKAGE_ucode-mod-math=y
CONFIG_PACKAGE_uhttpd=y
CONFIG_PACKAGE_uhttpd-mod-ubus=y
CONFIG_PACKAGE_unzip=y
CONFIG_PACKAGE_wireguard-tools=y
CONFIG_PACKAGE_xtables-nft=y
CONFIG_PACKAGE_yq=y
CONFIG_PACKAGE_zerotier=y
CONFIG_PACKAGE_zlib=y
CONFIG_REPRODUCIBLE_DEBUG_INFO=y
CONFIG_SDK=y
CONFIG_SDK_LLVM_BPF=y
CONFIG_VERSIONOPT=y
CONFIG_VERSION_BUG_URL=""
CONFIG_VERSION_CODE=""
CONFIG_VERSION_DIST="OpenWrt"
CONFIG_VERSION_FILENAMES=y
CONFIG_VERSION_HOME_URL=""
CONFIG_VERSION_HWREV=""
CONFIG_VERSION_MANUFACTURER=""
CONFIG_VERSION_MANUFACTURER_URL=""
CONFIG_VERSION_NUMBER=""
CONFIG_VERSION_PRODUCT=""
CONFIG_VERSION_REPO="https://downloads.openwrt.org/releases/24.10.4"
CONFIG_VERSION_SUPPORT_URL=""

View File

@@ -1,3 +0,0 @@
base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware luci
curl yq unzip ip-full kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -1,3 +0,0 @@
base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware luci
curl yq unzip ip-full kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -1,3 +0,0 @@
ath11k-firmware-ipq8074 base-files ca-bundle dnsmasq dropbear e2fsprogs firewall4 fstools kmod-ath11k-ahb kmod-fs-ext4 kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia kmod-qca-nss-dp kmod-usb-dwc3 kmod-usb-dwc3-qcom kmod-usb3 libc libgcc libustream-mbedtls logd losetup mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-leds-pca963x ipq-wifi-linksys_mx8500 kmod-ath11k-pci ath11k-firmware-qcn9074 kmod-bluetooth luci
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
apk-mbedtls base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware luci
curl yq unzip ip-full kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -1,3 +0,0 @@
apk-mbedtls ath11k-firmware-ipq8074 base-files ca-bundle dnsmasq dropbear e2fsprogs firewall4 fstools kmod-ath11k-ahb kmod-fs-ext4 kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia kmod-qca-nss-dp kmod-usb-dwc3 kmod-usb-dwc3-qcom kmod-usb3 libc libgcc libustream-mbedtls logd losetup mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-leds-pca963x ipq-wifi-linksys_mx8500 kmod-ath11k-pci ath11k-firmware-qcn9074 kmod-hci-uart luci
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
##built-in
apk-mbedtls base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware luci luci-app-attendedsysupgrade
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft
luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier
##module

View File

@@ -1,9 +0,0 @@
##built-in
apk-mbedtls base-files ca-bundle dnsmasq dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload libc libgcc libustream-mbedtls logd mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware luci luci-app-attendedsysupgrade
iptables-nft ip6tables-nft curl yq unzip ip-full kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun
luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier
##module

View File

@@ -1,8 +0,0 @@
##built-in
apk-mbedtls ath11k-firmware-ipq8074 base-files ca-bundle dnsmasq dropbear e2fsprogs firewall4 fstools kmod-ath11k-ahb kmod-fs-ext4 kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia kmod-qca-nss-dp kmod-usb-dwc3 kmod-usb-dwc3-qcom kmod-usb3 libc libgcc libustream-mbedtls logd losetup mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-leds-pca963x ipq-wifi-linksys_mx8500 kmod-ath11k-pci ath11k-firmware-qcn9074 kmod-hci-uart luci luci-app-attendedsysupgrade
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft
luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier
##module

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
##built-in
apk-mbedtls ath11k-firmware-ipq8074 base-files ca-bundle dnsmasq dropbear e2fsprogs firewall4 fstools kmod-ath11k-ahb kmod-fs-ext4 kmod-gpio-button-hotplug kmod-leds-gpio kmod-nft-offload kmod-phy-aquantia kmod-qca-nss-dp kmod-usb-dwc3 kmod-usb-dwc3-qcom kmod-usb3 libc libgcc libustream-mbedtls logd losetup mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-basic-mbedtls kmod-leds-pca963x ipq-wifi-linksys_mx8500 kmod-ath11k-pci ath11k-firmware-qcn9074 kmod-hci-uart luci luci-app-attendedsysupgrade
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft
luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier
##module

View File

@@ -1,96 +1,277 @@
#!/bin/bash #!/bin/bash
# Download OpenWRT config.buildinfo, generate .config and run defconfig # Fetch the official config.buildinfo for a device and install it as .config.
#
# The device is looked up in the official index, so any of the ~1900 supported
# devices can be selected by name; nothing is hardcoded.
#
# Two things are changed in the downloaded config by default, because it is
# the buildbot's configuration rather than a personal one:
# - only the selected device is built, instead of every model in the target
# - the buildbot flags (ALL_KMODS, ALL_NONSHARED, SDK, IB, MAKE_TOOLCHAIN,
# COLLECT_KERNEL_DEBUG, AUTOREMOVE, ...) are turned off
# Neither changes the contents of the firmware image; they only stop the build
# from also producing every optional module, the SDK and the ImageBuilder.
#
# Usage: ./download-config.sh [options]
#
# --device <id> profile id, e.g. linksys_mx8500
# --target <t> disambiguate an id present in several targets
# --version <ver> override version detection (e.g. 25.12.5)
# --snapshot shorthand for --version SNAPSHOT
# --with-packages also refresh the device's package list file
# --all-profiles keep every device in the target (upstream default)
# --keep-buildbot-flags keep ALL_KMODS / SDK / IB / ... as upstream ships
# --save-buildinfo keep a copy in .generated/<id>_<ver>.buildinfo
# --offline use a previously saved buildinfo, no network
# --output <file> write here instead of ./.config (for testing)
# --refresh ignore the cached JSON indexes
# --skip-defconfig do not run make defconfig afterwards
# -y, --yes do not ask for confirmation
# -n, --dry-run show the plan, change nothing
# -h, --help show this help
#
# Run it from the OpenWrt root: ./helper/download-config.sh
#
# Author: Zhe Yuan # Author: Zhe Yuan
set -e set -euo pipefail
umask 022 umask 022
# --- Check if inside OpenWRT source directory --- SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
if [ ! -f "feeds.conf.default" ] || [ ! -d "package" ]; then LIB_SCRIPT_DIR="$SCRIPT_DIR"
echo "[ERROR] Must run inside OpenWRT root directory." # shellcheck source=lib-openwrt-upstream.sh
source "$SCRIPT_DIR/lib-openwrt-upstream.sh"
DEVICE_ID=""
DEVICE_TARGET=""
VERSION_OVERRIDE=""
WITH_PACKAGES=false
SINGLE_PROFILE=true
STRIP_BUILDBOT=true
SAVE_BUILDINFO=false
OFFLINE=false
OUTPUT=""
RUN_DEFCONFIG=true
usage() { sed -n '2,37p' "$0" | sed 's/^# \{0,1\}//;s/^#$//'; }
need_value() {
if [ "$2" -lt 2 ] || [ -z "${3:-}" ]; then
err "$1 requires a value."
exit 1 exit 1
fi fi
}
# --- Detect Git version --- while [ $# -gt 0 ]; do
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "") case "$1" in
VERSION="${VERSION#v}" --device) need_value "$1" $# "${2:-}"; DEVICE_ID="$2"; shift ;;
IS_LATEST=false --target) need_value "$1" $# "${2:-}"; DEVICE_TARGET="$2"; shift ;;
--version) need_value "$1" $# "${2:-}"; VERSION_OVERRIDE="$2"; shift ;;
if [ -z "$VERSION" ]; then --snapshot) VERSION_OVERRIDE="SNAPSHOT" ;;
BRANCH=$(git rev-parse --abbrev-ref HEAD) --with-packages) WITH_PACKAGES=true ;;
if [[ "$BRANCH" =~ openwrt-([0-9]+\.[0-9]+) ]]; then --all-profiles) SINGLE_PROFILE=false ;;
PARTIAL_VERSION="${BASH_REMATCH[1]}" --keep-buildbot-flags) STRIP_BUILDBOT=false ;;
# Find latest patch tag --save-buildinfo) SAVE_BUILDINFO=true ;;
LATEST_TAG=$(git tag -l "v${PARTIAL_VERSION}.*" | sort -V | tail -n1) --offline) OFFLINE=true ;;
if [ -z "$LATEST_TAG" ]; then --output) need_value "$1" $# "${2:-}"; OUTPUT="$2"; shift ;;
echo "[ERROR] Cannot find latest tag for $PARTIAL_VERSION.*" --refresh) REFRESH=true ;;
exit 1 --skip-defconfig) RUN_DEFCONFIG=false ;;
fi -y|--yes) ASSUME_YES=true ;;
VERSION="${LATEST_TAG#v}" -n|--dry-run) DRY_RUN=true ;;
IS_LATEST=true -h|--help) usage; exit 0 ;;
elif [[ "$BRANCH" == *SNAPSHOT* ]]; then *) err "Unknown option: $1"; echo "Run '$0 --help' for usage." >&2; exit 1 ;;
VERSION="SNAPSHOT" esac
else shift
read -rp "Cannot detect version, input manually (e.g., 24.10.4 or SNAPSHOT): " VERSION
fi
fi
echo "[INFO] OpenWRT version: $VERSION"
# --- Devices list ---
# Format: Name Target FirmwareDevice
DEVICES=(
"Linksys_MX8500 qualcommax/ipq807x linksys_mx8500"
"Cudy_TR3000_128MB mediatek/filogic cudy_tr3000-v1"
"Cudy_TR3000_256MB mediatek/filogic cudy_tr3000-256mb-v1"
)
echo "Available devices:"
for i in "${!DEVICES[@]}"; do
NAME=$(echo "${DEVICES[$i]}" | awk '{print $1}')
echo " $i) $NAME"
done done
read -rp "Select device number: " DEV_INDEX [ -t 0 ] || ASSUME_YES=true
if [[ -z "${DEVICES[$DEV_INDEX]}" ]]; then
echo "[ERROR] Invalid selection." require_python3
if [ -n "$OUTPUT" ]; then
DEST="$OUTPUT"
RUN_DEFCONFIG=false
else
require_openwrt_root
DEST=".config"
fi
DEST_DIR="$(dirname -- "$DEST")"
VER="$(detect_version)"
if [ "$OFFLINE" != true ] && ! version_exists "$VER"; then
err "Version '$VER' is not published at $BASE_URL."
err " $(url_overview "$VER")"
exit 1 exit 1
fi fi
# --- Extract device info --- select_device "$VER"
read -r DEV_NAME DEV_TARGET DEV_FW <<<"${DEVICES[$DEV_INDEX]}"
echo "[INFO] Selected device: $DEV_NAME"
# --- Construct URLs --- SAVED_BUILDINFO="$SCRIPT_DIR/.generated/${DEV_ID}_${VER}.buildinfo"
if [[ "$VERSION" == "SNAPSHOT" ]]; then
CONFIG_URL="https://downloads.openwrt.org/snapshots/targets/$DEV_TARGET/config.buildinfo" SYSUPGRADE_URL=""
FIRMWARE_URL="https://downloads.openwrt.org/snapshots/targets/$DEV_TARGET/openwrt-SNAPSHOT-$DEV_TARGET-$DEV_FW-squashfs-sysupgrade.bin" FACTORY_URL=""
else PKG_COUNT="?"
CONFIG_URL="https://downloads.openwrt.org/releases/$VERSION/targets/$DEV_TARGET/config.buildinfo" if [ "$OFFLINE" != true ]; then
FIRMWARE_URL="https://downloads.openwrt.org/releases/$VERSION/targets/$DEV_TARGET/openwrt-$VERSION-$DEV_TARGET-$DEV_FW-squashfs-sysupgrade.bin" PROFILES_JSON="$(fetch_profiles "$VER" "$DEV_TARGET")"
PKG_COUNT="$(profile_packages "$PROFILES_JSON" "$DEV_ID" | grep -c . || true)"
while IFS= read -r img; do
case "$img" in
*squashfs-sysupgrade*) SYSUPGRADE_URL="$(url_image "$VER" "$DEV_TARGET" "$img")" ;;
*squashfs-factory*) FACTORY_URL="$(url_image "$VER" "$DEV_TARGET" "$img")" ;;
esac
done < <(profile_images "$PROFILES_JSON" "$DEV_ID")
fi fi
echo "[INFO] Config URL: $CONFIG_URL" CONFIG_URL="$(url_buildinfo "$VER" "$DEV_TARGET")"
echo "[INFO] Firmware URL: $FIRMWARE_URL"
# --- Download config.buildinfo --- echo
echo "[INFO] Downloading config.buildinfo..." echo "========================================"
wget -q --show-progress -O ".config" "$CONFIG_URL" echo " Planned actions"
echo "[INFO] Saved as .config" echo "========================================"
echo " Device : $DEV_ID ($DEV_LABEL)"
echo " Target : $DEV_TARGET"
echo " Version : $VER"
if [ "$OFFLINE" = true ]; then
echo " Source : $SAVED_BUILDINFO (offline)"
else
echo " Source : $CONFIG_URL"
fi
echo " Destination : $DEST$([ -f "$DEST" ] && echo ' (backed up first)')"
echo " Device scope : $([ "$SINGLE_PROFILE" = true ] && echo 'this device only' || echo 'all devices in target')"
echo " Buildbot flags: $([ "$STRIP_BUILDBOT" = true ] && echo 'stripped' || echo 'kept')"
echo " Built-in pkgs : $PKG_COUNT (per profiles.json)"
[ -n "$SYSUPGRADE_URL" ] && echo " Sysupgrade : $SYSUPGRADE_URL"
echo " Post : $([ "$RUN_DEFCONFIG" = true ] && echo 'make defconfig' || echo 'skipped')"
echo "========================================"
# --- Run make defconfig --- if [ "$DRY_RUN" != true ]; then
echo "[INFO] Running make defconfig..." echo
if ! confirm "Proceed?" yes; then
info "Aborted by user."
exit 0
fi
fi
# --- Download into a temp file next to the destination -------------------
# Same directory keeps the final mv atomic; `curl -f` plus this staging is
# what stops a 404 from truncating an existing .config.
TMP="$(mktemp "$DEST_DIR/.config.dl.XXXXXX")"
trap 'rm -f "$TMP" "$TMP.pp"' EXIT
if [ "$OFFLINE" = true ]; then
if [ ! -f "$SAVED_BUILDINFO" ]; then
err "No saved buildinfo at $SAVED_BUILDINFO"
err "Run once without --offline (add --save-buildinfo) to create it."
exit 1
fi
info "Using the saved buildinfo (offline)."
cp "$SAVED_BUILDINFO" "$TMP"
else
info "Downloading $CONFIG_URL"
if ! fetch_url "$CONFIG_URL" "$TMP"; then
err "Download failed; $DEST was not touched."
exit 1
fi
fi
# --- Validate before letting it near .config ------------------------------
if [ ! -s "$TMP" ] || [ "$(wc -l < "$TMP")" -lt 5 ]; then
err "Downloaded config looks empty or truncated; $DEST was not touched."
exit 1
fi
if ! grep -qx "CONFIG_TARGET_${DEV_TSYM}=y" "$TMP"; then
err "Downloaded config is not for target $DEV_TARGET; $DEST was not touched."
exit 1
fi
if ! grep -q "_DEVICE_${DEV_ID}=y" "$TMP"; then
err "Downloaded config does not contain device $DEV_ID; $DEST was not touched."
exit 1
fi
# --- Post-process ---------------------------------------------------------
# One awk pass: a chain of `sed -i` would rewrite the file repeatedly and the
# symbol names contain '-' and '+', which are awkward in sed replacements.
awk -v tsym="$DEV_TSYM" -v prof="$DEV_ID" \
-v single="$SINGLE_PROFILE" -v strip="$STRIP_BUILDBOT" '
# Matches both CONFIG_TARGET_DEVICE_<t>_DEVICE_<id>=y and
# CONFIG_TARGET_DEVICE_PACKAGES_<t>_DEVICE_<id>="". It cannot match the
# single-profile symbol appended below, which has no DEVICE_ prefix.
single == "true" && /^CONFIG_TARGET_DEVICE_/ { next }
single == "true" && /^CONFIG_TARGET_MULTI_PROFILE=y$/ { print "# CONFIG_TARGET_MULTI_PROFILE is not set"; next }
single == "true" && /^CONFIG_TARGET_ALL_PROFILES=y$/ { print "# CONFIG_TARGET_ALL_PROFILES is not set"; next }
single == "true" && /^CONFIG_TARGET_PER_DEVICE_ROOTFS=y$/ { print "# CONFIG_TARGET_PER_DEVICE_ROOTFS is not set"; next }
strip == "true" && $0 ~ /^CONFIG_(ALL_KMODS|ALL_NONSHARED|BUILDBOT|SDK|SDK_LLVM_BPF|IB|MAKE_TOOLCHAIN|COLLECT_KERNEL_DEBUG|JSON_CYCLONEDX_SBOM|AUTOREMOVE)=y$/ {
sym = $0; sub(/=y$/, "", sym); print "# " sym " is not set"; next
}
{ print }
END {
# CONFIG_TARGET_PROFILE is deliberately not written: Kconfig derives it.
if (single == "true") print "CONFIG_TARGET_" tsym "_DEVICE_" prof "=y"
}
' "$TMP" > "$TMP.pp"
if [ "$DRY_RUN" = true ]; then
echo
info "Resulting config (first differences against the download):"
diff -u "$TMP" "$TMP.pp" | head -40 || true
echo
info "Dry run complete; $DEST was not touched."
exit 0
fi
if [ "$SAVE_BUILDINFO" = true ]; then
mkdir -p "$(dirname "$SAVED_BUILDINFO")"
cp "$TMP" "$SAVED_BUILDINFO"
info "Saved the raw buildinfo to $SAVED_BUILDINFO"
fi
if [ -f "$DEST" ]; then
cp -a "$DEST" "$DEST.download.bak"
info "Previous config kept at $DEST.download.bak"
fi
mv -f "$TMP.pp" "$DEST"
chmod 644 "$DEST"
info "Wrote $DEST"
if [ "$RUN_DEFCONFIG" = true ]; then
info "Running make defconfig..."
make defconfig >/dev/null make defconfig >/dev/null
echo "[INFO] defconfig done."
# --- Check firmware existence --- # A config whose symbols do not exist in this tree is silently dropped by
if curl --head --silent --fail "$FIRMWARE_URL" >/dev/null; then # Kconfig, leaving a config that looks fine and builds the wrong thing.
echo "[INFO] Firmware exists: $FIRMWARE_URL" if [ "$SINGLE_PROFILE" = true ]; then
else if ! grep -qx "CONFIG_TARGET_${DEV_TSYM}_DEVICE_${DEV_ID}=y" .config ||
echo "[WARN] Firmware not found or URL invalid." ! grep -qx "CONFIG_TARGET_PROFILE=\"DEVICE_${DEV_ID}\"" .config; then
err "The device symbol did not survive make defconfig."
err "Your tree ($(git describe --tags 2>/dev/null || echo unknown)) probably"
err "does not match release $VER. Restore with: cp $DEST.download.bak $DEST"
exit 1
fi
fi
fi fi
# --- Print Firmware Selector URL --- echo
TARGET_ESC=$(echo $DEV_TARGET | sed 's|/|%2F|g') echo "========================================"
FS_URL="https://firmware-selector.openwrt.org/?version=$VERSION&target=$TARGET_ESC&id=$DEV_FW" echo "OK Configuration installed."
echo "[INFO] Check Installed Packages at: $FS_URL" echo " Device: $DEV_ID ($DEV_LABEL)"
echo " Target: $DEV_TARGET"
echo " Version: $VER"
echo "========================================"
[ -n "$SYSUPGRADE_URL" ] && echo " Official sysupgrade: $SYSUPGRADE_URL"
[ -n "$FACTORY_URL" ] && echo " Official factory: $FACTORY_URL"
echo " Firmware Selector: $(selector_url "$VER" "$DEV_TARGET" "$DEV_ID")"
echo
if [ "$WITH_PACKAGES" = true ]; then
GEN_ARGS=(--device "$DEV_ID" --target "$DEV_TARGET" --version "$VER")
[ "$ASSUME_YES" = true ] && GEN_ARGS+=(-y)
"$SCRIPT_DIR/gen-package-list.sh" "${GEN_ARGS[@]}"
else
echo "Next steps:"
echo " ./helper/gen-package-list.sh --device $DEV_ID --apply"
echo " (or --out/-n first if you want to review the list before applying)"
echo " make menuconfig"
echo " make -j\$(nproc) download world"
echo
fi

236
gen-package-list.sh Executable file
View File

@@ -0,0 +1,236 @@
#!/bin/bash
# Build a device's complete package list.
#
# The built-in part comes from the target's profiles.json (default_packages +
# device_packages, plus the packages the Firmware Selector adds on top), so it
# never has to be copied by hand for a new release. Your own packages live in
# packages/<device-id>.txt and are version independent.
#
# packages/<device-id>.txt your packages, kept in git, edited by hand
# .generated/ the combined list, not kept in git
#
# Usage: ./gen-package-list.sh [options]
#
# --device <id> profile id, e.g. linksys_mx8500
# --target <t> disambiguate an id present in several targets
# --version <ver> override version detection (e.g. 25.12.5)
# --snapshot shorthand for --version SNAPSHOT
# --extras-file <f> use this instead of packages/<device-id>.txt
# --no-extras-file built-in packages only
# --init-extras create packages/<device-id>.txt if it is missing
# --extras "<pkgs>" override the Firmware Selector additions
# --no-extras omit the Firmware Selector additions
# --wrap <cols> wrap the built-in list (default 0 = one line)
# --out <path> write here instead of .generated/
# --stdout print the built-in list only, one per line
# --full-stdout print the complete list and exit
# --apply apply the complete list to .config
# --refresh ignore the cached JSON indexes
# -y, --yes do not ask for confirmation
# -n, --dry-run show what would happen, write nothing
# -h, --help show this help
#
# Author: Zhe Yuan
set -euo pipefail
umask 022
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
LIB_SCRIPT_DIR="$SCRIPT_DIR"
# shellcheck source=lib-openwrt-upstream.sh
source "$SCRIPT_DIR/lib-openwrt-upstream.sh"
DEVICE_ID=""
DEVICE_TARGET=""
VERSION_OVERRIDE=""
EXTRAS="$SELECTOR_EXTRAS"
EXTRAS_FILE=""
USE_EXTRAS_FILE=true
INIT_EXTRAS=false
WRAP=0
OUT_FILE=""
MODE="file" # file | stdout | full-stdout | apply
usage() { sed -n '2,34p' "$0" | sed 's/^# \{0,1\}//;s/^#$//'; }
need_value() {
if [ "$2" -lt 2 ] || [ -z "${3:-}" ]; then
err "$1 requires a value."
exit 1
fi
}
while [ $# -gt 0 ]; do
case "$1" in
--device) need_value "$1" $# "${2:-}"; DEVICE_ID="$2"; shift ;;
--target) need_value "$1" $# "${2:-}"; DEVICE_TARGET="$2"; shift ;;
--version) need_value "$1" $# "${2:-}"; VERSION_OVERRIDE="$2"; shift ;;
--snapshot) VERSION_OVERRIDE="SNAPSHOT" ;;
--extras-file) need_value "$1" $# "${2:-}"; EXTRAS_FILE="$2"; shift ;;
--no-extras-file) USE_EXTRAS_FILE=false ;;
--init-extras) INIT_EXTRAS=true ;;
--extras) need_value "$1" $# "${2:-}"; EXTRAS="$2"; shift ;;
--no-extras) EXTRAS="" ;;
--wrap) need_value "$1" $# "${2:-}"; WRAP="$2"; shift ;;
--out) need_value "$1" $# "${2:-}"; OUT_FILE="$2"; shift ;;
--stdout) MODE="stdout" ;;
--full-stdout) MODE="full-stdout" ;;
--apply) MODE="apply" ;;
--refresh) REFRESH=true ;;
-y|--yes) ASSUME_YES=true ;;
-n|--dry-run) DRY_RUN=true ;;
-h|--help) usage; exit 0 ;;
*) err "Unknown option: $1"; echo "Run '$0 --help' for usage." >&2; exit 1 ;;
esac
shift
done
[ -t 0 ] || ASSUME_YES=true
if ! [[ "$WRAP" =~ ^[0-9]+$ ]]; then
err "--wrap needs a non-negative number."
exit 1
fi
require_python3
VER="$(detect_version)"
select_device "$VER"
PROFILES_JSON="$(fetch_profiles "$VER" "$DEV_TARGET")"
PKGS="$(profile_packages "$PROFILES_JSON" "$DEV_ID")"
# Selector additions go last and never duplicate what the target provides.
for extra in $EXTRAS; do
if ! printf '%s\n' "$PKGS" | grep -qxF "$extra"; then
PKGS="$PKGS"$'\n'"$extra"
fi
done
BUILTIN_COUNT="$(printf '%s\n' "$PKGS" | grep -c . || true)"
if [ "$MODE" = "stdout" ]; then
printf '%s\n' "$PKGS"
exit 0
fi
# --- Extras file ----------------------------------------------------------
if [ "$USE_EXTRAS_FILE" = true ] && [ -z "$EXTRAS_FILE" ]; then
EXTRAS_FILE="$SCRIPT_DIR/packages/$DEV_ID.txt"
fi
init_extras_file() {
mkdir -p "$(dirname "$EXTRAS_FILE")"
cat > "$EXTRAS_FILE" <<EOF
# Extra packages for $DEV_ID, on top of the built-in list that
# gen-package-list.sh pulls from the official profiles.json.
#
# Whitespace separated, any number per line. '#' starts a comment.
# '-pkg' removes a package; '##module' / '##remove' switch modes.
EOF
info "Created $EXTRAS_FILE"
}
EXTRAS_COUNT=0
if [ "$USE_EXTRAS_FILE" = true ]; then
if [ ! -f "$EXTRAS_FILE" ]; then
if [ "$INIT_EXTRAS" = true ] && [ "$DRY_RUN" != true ]; then
init_extras_file
else
warn "No extras file at $EXTRAS_FILE"
warn "Only the built-in packages will be included (--init-extras creates one)."
EXTRAS_FILE=""
fi
fi
if [ -n "$EXTRAS_FILE" ] && [ -f "$EXTRAS_FILE" ]; then
EXTRAS_COUNT="$(grep -v '^[[:space:]]*#' "$EXTRAS_FILE" | tr -s ' \t\n' '\n' |
grep -c . || true)"
fi
fi
# --- Render the complete list ---------------------------------------------
# The generated file is entirely machine-owned, so it is simply rewritten;
# everything hand-written lives in the extras file instead.
render_full() {
echo "##built-in"
echo "# Generated from profiles.json for $DEV_ID ($DEV_TARGET) at $VER."
echo "# Do not edit: regenerate with gen-package-list.sh."
if [ "$WRAP" -gt 0 ]; then
printf '%s\n' "$PKGS" | tr '\n' ' ' | fold -s -w "$WRAP" | sed 's/[[:space:]]*$//'
else
printf '%s\n' "$PKGS" | tr '\n' ' ' | sed 's/[[:space:]]*$//'
echo
fi
if [ -n "$EXTRAS_FILE" ] && [ -f "$EXTRAS_FILE" ]; then
echo
echo "# --- extras from ${EXTRAS_FILE#"$SCRIPT_DIR"/} ---"
# Appended verbatim: section headers are positional, so a ##module in
# the extras file keeps working after the built-in block.
cat "$EXTRAS_FILE"
fi
}
if [ "$MODE" = "full-stdout" ]; then
render_full
exit 0
fi
if [ "$MODE" = "apply" ]; then
info "Applying $BUILTIN_COUNT built-in + $EXTRAS_COUNT extra packages to .config"
APPLY_ARGS=()
[ "$ASSUME_YES" = true ] && APPLY_ARGS+=(-y)
[ "$DRY_RUN" = true ] && APPLY_ARGS+=(-n)
render_full | "$SCRIPT_DIR/add-openwrt-packages.sh" "${APPLY_ARGS[@]}" -
exit $?
fi
# --- File mode ------------------------------------------------------------
if [ -z "$OUT_FILE" ]; then
OUT_FILE="$SCRIPT_DIR/.generated/packages_${DEV_ID}_${VER}.txt"
fi
echo
echo "========================================"
echo " Planned actions"
echo "========================================"
echo " Device : $DEV_ID ($DEV_LABEL)"
echo " Target : $DEV_TARGET"
echo " Version : $VER"
echo " Built-in : $BUILTIN_COUNT packages (from profiles.json)"
echo " Extras : $EXTRAS_COUNT packages${EXTRAS_FILE:+ from ${EXTRAS_FILE#"$SCRIPT_DIR"/}}"
echo " Output : $OUT_FILE"
echo "========================================"
if [ "$DRY_RUN" = true ]; then
echo
info "Complete list that would be written:"
render_full | sed 's/^/ /'
echo
info "Dry run complete; nothing was written."
exit 0
fi
echo
if ! confirm "Write $OUT_FILE?" yes; then
info "Aborted by user."
exit 0
fi
mkdir -p "$(dirname -- "$OUT_FILE")"
render_full > "$OUT_FILE"
chmod 644 "$OUT_FILE"
echo
echo "========================================"
echo "OK Package list written."
echo " File: $OUT_FILE"
echo " Packages: $BUILTIN_COUNT built-in + $EXTRAS_COUNT extra"
echo "========================================"
echo
echo "Next steps:"
[ -n "$EXTRAS_FILE" ] && echo " Edit ${EXTRAS_FILE#"$SCRIPT_DIR"/} to change your own packages"
echo " cd $(dirname "$SCRIPT_DIR")"
echo " ./helper/add-openwrt-packages.sh helper/${OUT_FILE#"$SCRIPT_DIR"/}"
echo

451
lib-openwrt-upstream.sh Normal file
View File

@@ -0,0 +1,451 @@
#!/bin/bash
# Shared helpers for talking to downloads.openwrt.org.
#
# Sourced by download-config.sh and gen-package-list.sh. Not executable on its
# own. Callers are expected to set `set -euo pipefail`; this file does not.
#
# Author: Zhe Yuan
[ -n "${_OWRT_LIB_LOADED:-}" ] && return 0
_OWRT_LIB_LOADED=1
BASE_URL="${BASE_URL:-https://downloads.openwrt.org}"
CACHE_DIR="${CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/openwrt-build-helper}"
CACHE_TTL="${CACHE_TTL:-86400}"
# What the Firmware Selector adds on top of the target's own package set.
# This is a UI convention, not an API, so keep it overridable.
SELECTOR_EXTRAS="${SELECTOR_EXTRAS:-luci luci-app-attendedsysupgrade}"
# Devices offered when an interactive search is left empty. Only a convenience
# shortcut -- the target still comes from .overview.json, never hardcoded.
FAVOURITE_DEVICES="${FAVOURITE_DEVICES:-linksys_mx8500 cudy_tr3000-v1 cudy_tr3000-256mb-v1}"
REFRESH="${REFRESH:-false}"
DRY_RUN="${DRY_RUN:-false}"
ASSUME_YES="${ASSUME_YES:-false}"
# --- Output helpers -------------------------------------------------------
# All logging goes to stderr so that functions returning a value on stdout
# (detect_version, profile_packages, ...) can be captured safely.
info() { echo "[INFO] $*" >&2; }
warn() { echo "[WARN] $*" >&2; }
err() { echo "[ERROR] $*" >&2; }
# Wrap every mutating command so --dry-run prints instead of running.
run() {
if [ "$DRY_RUN" = true ]; then
printf '[DRY-RUN]'
printf ' %q' "$@"
echo
return 0
fi
"$@"
}
confirm() {
# confirm "<question>" <default: yes|no>
local question="$1" default="$2" reply=""
[ "$ASSUME_YES" = true ] && return 0
if [ "$default" = "yes" ]; then
read -r -p "$question [Y/n]: " reply || reply=""
case "${reply:-}" in
[nN] | [nN][oO]) return 1 ;;
*) return 0 ;;
esac
else
read -r -p "$question [y/N]: " reply || reply=""
case "${reply:-}" in
[yY] | [yY][eE][sS]) return 0 ;;
*) return 1 ;;
esac
fi
}
require_python3() {
if ! command -v python3 >/dev/null 2>&1; then
err "python3 is required to parse the upstream JSON indexes."
exit 1
fi
}
# The helper checkout is itself a git repository living inside the OpenWrt
# worktree, so plain git commands run from here describe the HELPER repo.
# Every version query must be anchored to the OpenWrt tree instead.
find_openwrt_root() {
local d
for d in "$PWD" "$(dirname -- "${LIB_SCRIPT_DIR:-$PWD}")"; do
[ -n "$d" ] || continue
if [ -f "$d/feeds.conf.default" ] && [ -f "$d/Makefile" ] && [ -d "$d/scripts" ]; then
( cd -- "$d" && pwd -P )
return 0
fi
done
return 1
}
require_openwrt_root() {
if [ ! -f feeds.conf.default ] || [ ! -f Makefile ] || [ ! -d scripts ]; then
err "Must run inside the OpenWrt root directory."
err "From the OpenWrt root, run it as: ./helper/$(basename "$0")"
exit 1
fi
}
# --- URL construction -----------------------------------------------------
# Every upstream URL is built from this one function.
release_path() {
local ver="$1"
if [ "$ver" = "SNAPSHOT" ]; then
echo "snapshots"
else
echo "releases/$ver"
fi
}
url_overview() { echo "$BASE_URL/$(release_path "$1")/.overview.json"; }
url_profiles() { echo "$BASE_URL/$(release_path "$1")/targets/$2/profiles.json"; }
url_buildinfo() { echo "$BASE_URL/$(release_path "$1")/targets/$2/config.buildinfo"; }
url_image() { echo "$BASE_URL/$(release_path "$1")/targets/$2/$3"; }
selector_url() {
local ver="$1" target="$2" id="$3"
echo "https://firmware-selector.openwrt.org/?version=$ver&target=${target//\//%2F}&id=$id"
}
# --- Fetching -------------------------------------------------------------
# `curl -f` is the key flag: a 404 exits non-zero and never writes a body, so
# a failed download can never truncate the destination.
fetch_url() {
local url="$1" dest="$2"
if ! curl -fsSL --retry 3 --retry-delay 2 --max-time 60 -o "$dest.part" "$url"; then
rm -f "$dest.part"
return 1
fi
mv -f "$dest.part" "$dest"
}
fetch_cached() {
local url="$1" cache="$2" age=0
mkdir -p "$(dirname "$cache")"
if [ "$REFRESH" != true ] && [ -s "$cache" ]; then
age=$(( $(date +%s) - $(stat -c %Y "$cache" 2>/dev/null || echo 0) ))
if [ "$age" -lt "$CACHE_TTL" ]; then
return 0
fi
fi
if fetch_url "$url" "$cache"; then
return 0
fi
# A stale cache beats no data when the network is down.
if [ -s "$cache" ]; then
warn "Could not refresh $url; using the cached copy."
return 0
fi
return 1
}
version_exists() {
curl -fsI --max-time 30 "$(url_overview "$1")" >/dev/null 2>&1
}
# --- Version detection ----------------------------------------------------
# Replaces `git describe --tags --abbrev=0`, which returns a tag whenever ANY
# tag is reachable -- so the old script's whole fallback chain was dead code
# and a branch tip past a tag silently produced the wrong release URL.
detect_version() {
local ver="" branch="" tag="" drift=0
if [ -n "${VERSION_OVERRIDE:-}" ]; then
ver="${VERSION_OVERRIDE#v}"
info "Version from the command line: $ver"
echo "$ver"
return 0
fi
local root=""
if ! root="$(find_openwrt_root)"; then
err "Cannot locate the OpenWrt source tree; pass --version <ver> explicitly."
exit 1
fi
if ! git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
err "'$root' is not a Git repository and no --version was given."
exit 1
fi
if tag="$(git -C "$root" describe --tags --exact-match HEAD 2>/dev/null)"; then
ver="${tag#v}"
info "HEAD is exactly at tag $tag; using version $ver."
echo "$ver"
return 0
fi
if branch="$(git -C "$root" symbolic-ref --quiet --short HEAD 2>/dev/null)"; then
case "$branch" in
main | master)
info "On branch '$branch'; using SNAPSHOT."
echo "SNAPSHOT"
return 0
;;
openwrt-* | [0-9]*)
local series="${branch#openwrt-}"
# --merged HEAD keeps tags from unrelated branches out of the picture.
tag="$(git -C "$root" tag --merged HEAD -l "v${series}.*" 2>/dev/null |
grep -v -- '-rc' | sort -V | tail -n 1)"
if [ -n "$tag" ]; then
drift="$(git -C "$root" rev-list --count "$tag..HEAD" 2>/dev/null || echo 0)"
if [ "$drift" -gt 0 ]; then
warn "HEAD is $drift commit(s) past $tag."
warn "The downloaded config describes the release, not your tree."
fi
ver="${tag#v}"
info "Branch '$branch' -> newest merged tag $tag; using version $ver."
echo "$ver"
return 0
fi
;;
esac
fi
if tag="$(git -C "$root" describe --tags --long 2>/dev/null)"; then
tag="${tag%-*-g*}"
drift="$(git -C "$root" rev-list --count "$tag..HEAD" 2>/dev/null || echo 0)"
warn "HEAD is not at a tag; nearest is $tag ($drift commit(s) behind HEAD)."
if [ "$ASSUME_YES" != true ] &&
! confirm "Use version ${tag#v}?" no; then
err "Aborted; pass --version explicitly."
exit 1
fi
ver="${tag#v}"
info "Using version $ver."
echo "$ver"
return 0
fi
if [ "$ASSUME_YES" = true ] || [ ! -t 0 ]; then
err "Cannot determine the OpenWrt version; pass --version <ver> or --snapshot."
exit 1
fi
read -r -p "Enter version (e.g. 25.12.5 or SNAPSHOT): " ver || ver=""
if [ -z "$ver" ]; then
err "No version given."
exit 1
fi
echo "${ver#v}"
}
# --- Device index ---------------------------------------------------------
fetch_overview() {
local ver="$1"
# Separate statement on purpose: `local a=$1 b=$a` expands every word before
# assigning, so $a would be unbound under `set -u`.
local cache="$CACHE_DIR/overview-$ver.json"
# Snapshots move daily, so never serve them from a day-old cache.
[ "$ver" = "SNAPSHOT" ] && REFRESH=true
if ! fetch_cached "$(url_overview "$ver")" "$cache"; then
err "Cannot fetch the device index for version '$ver'."
err " $(url_overview "$ver")"
exit 1
fi
echo "$cache"
}
# Emits TSV: id <tab> target <tab> label
_overview_py() {
python3 - "$@" <<'PY'
import json, sys, difflib
path, mode, arg = sys.argv[1], sys.argv[2], sys.argv[3]
data = json.load(open(path, encoding="utf-8"))
profiles = data.get("profiles") or []
def label(p):
parts = []
for t in p.get("titles") or []:
if not isinstance(t, dict):
continue
# Upstream entries come in three shapes: {title}, {vendor,model},
# and {vendor,model,variant}.
if t.get("title"):
parts.append(t["title"])
else:
parts.append(" ".join(
str(x) for x in (t.get("vendor"), t.get("model"), t.get("variant")) if x
))
parts = [p for p in parts if p]
return " / ".join(parts) or p.get("id", "")
def emit(rows):
for p in rows:
print("%s\t%s\t%s" % (p.get("id", ""), p.get("target", ""), label(p)))
if mode == "id":
rows = [p for p in profiles if p.get("id") == arg]
if not rows:
names = [p.get("id", "") for p in profiles]
close = difflib.get_close_matches(arg, names, n=5, cutoff=0.5)
sys.stderr.write("no device with id '%s'\n" % arg)
if close:
sys.stderr.write("closest matches: %s\n" % ", ".join(close))
sys.exit(2)
emit(rows)
elif mode == "search":
terms = [t.lower() for t in arg.split() if t]
rows = []
for p in profiles:
hay = (p.get("id", "") + " " + label(p)).lower()
if all(t in hay for t in terms):
rows.append(p)
rows.sort(key=lambda p: p.get("id", ""))
emit(rows)
elif mode == "list":
wanted = arg.split()
by_id = {p.get("id"): p for p in profiles}
emit([by_id[i] for i in wanted if i in by_id])
PY
}
# Sets DEV_ID, DEV_TARGET, DEV_TSYM, DEV_LABEL
select_device() {
local ver="$1" json="" rows="" n=0 choice="" tries=0 query=""
json="$(fetch_overview "$ver")"
if [ -n "${DEVICE_ID:-}" ]; then
if ! rows="$(_overview_py "$json" id "$DEVICE_ID" 2>&1)"; then
err "$rows"
exit 1
fi
if [ -n "${DEVICE_TARGET:-}" ]; then
rows="$(printf '%s\n' "$rows" | awk -F'\t' -v t="$DEVICE_TARGET" '$2 == t')"
fi
n="$(printf '%s\n' "$rows" | grep -c . || true)"
if [ "$n" -eq 0 ]; then
err "Device '$DEVICE_ID' exists but not for target '${DEVICE_TARGET:-}'."
exit 1
fi
if [ "$n" -gt 1 ]; then
err "Device id '$DEVICE_ID' is present in more than one target:"
printf '%s\n' "$rows" | awk -F'\t' '{print " " $2 " (" $3 ")"}' >&2
err "Disambiguate with --target <target>."
exit 1
fi
else
if [ "$ASSUME_YES" = true ] || [ ! -t 0 ]; then
err "No device selected. Pass --device <id> for non-interactive use."
exit 1
fi
while [ "$tries" -lt 3 ]; do
tries=$((tries + 1))
read -r -p "Search device (vendor, model or id; empty for favourites): " query || query=""
if [ -z "${query:-}" ]; then
rows="$(_overview_py "$json" list "$FAVOURITE_DEVICES")"
else
rows="$(_overview_py "$json" search "$query")"
fi
n="$(printf '%s\n' "$rows" | grep -c . || true)"
if [ "$n" -eq 0 ]; then
warn "No device matched '$query'."
continue
fi
if [ "$n" -gt 25 ]; then
warn "$n devices matched; please narrow the search."
continue
fi
echo
printf '%s\n' "$rows" | awk -F'\t' '{printf " %2d) %-32s %-22s %s\n", NR, $1, $2, $3}'
echo
read -r -p "Select device number [1-$n]: " choice || choice=""
# 1-based plus an explicit numeric test: an unset/non-numeric index in
# an array subscript is an arithmetic context that silently yields 0.
if [[ "${choice:-}" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$n" ]; then
rows="$(printf '%s\n' "$rows" | sed -n "${choice}p")"
break
fi
warn "Invalid selection."
rows=""
done
if [ -z "$rows" ]; then
err "No device selected."
exit 1
fi
fi
IFS=$'\t' read -r DEV_ID DEV_TARGET DEV_LABEL <<<"$rows"
DEV_TSYM="${DEV_TARGET//\//_}"
if [ -z "$DEV_ID" ] || [ -z "$DEV_TARGET" ]; then
err "Could not resolve the device selection."
exit 1
fi
}
# --- profiles.json --------------------------------------------------------
fetch_profiles() {
local ver="$1" target="$2"
local cache="$CACHE_DIR/profiles-$ver-${target//\//_}.json"
[ "$ver" = "SNAPSHOT" ] && REFRESH=true
if ! fetch_cached "$(url_profiles "$ver" "$target")" "$cache"; then
err "Cannot fetch profiles.json for $target at version $ver."
err " $(url_profiles "$ver" "$target")"
exit 1
fi
echo "$cache"
}
# default_packages + device_packages, order preserved, deduplicated.
profile_packages() {
python3 - "$1" "$2" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
pid = sys.argv[2]
prof = (data.get("profiles") or {}).get(pid)
if prof is None:
sys.stderr.write("profile '%s' is not in this target's profiles.json\n" % pid)
sys.exit(2)
seen, out = set(), []
for p in list(data.get("default_packages") or []) + list(prof.get("device_packages") or []):
if p not in seen:
seen.add(p)
out.append(p)
print("\n".join(out))
PY
}
# Image filenames straight from profiles.json -- no string building, which is
# what made the old firmware URL wrong (the target still contained a '/').
profile_images() {
python3 - "$1" "$2" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
prof = (data.get("profiles") or {}).get(sys.argv[2]) or {}
for img in prof.get("images") or []:
name = img.get("name")
if name:
print(name)
PY
}

View File

@@ -0,0 +1,9 @@
# Extra packages for cudy_tr3000-256mb-v1, on top of the built-in list that
# gen-package-list.sh pulls from the official profiles.json.
#
# Whitespace separated, any number per line. '#' starts a comment.
# '-pkg' removes a package; '##module' / '##remove' switch modes.
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft
luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -0,0 +1,9 @@
# Extra packages for cudy_tr3000-v1, on top of the built-in list that
# gen-package-list.sh pulls from the official profiles.json.
#
# Whitespace separated, any number per line. '#' starts a comment.
# '-pkg' removes a package; '##module' / '##remove' switch modes.
iptables-nft ip6tables-nft curl yq unzip ip-full kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun
luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -0,0 +1,9 @@
# Extra packages for linksys_mx8500, on top of the built-in list that
# gen-package-list.sh pulls from the official profiles.json.
#
# Whitespace separated, any number per line. '#' starts a comment.
# '-pkg' removes a package; '##module' / '##remove' switch modes.
curl yq unzip ip-full kmod-mtd-rw kmod-inet-diag kmod-nft-socket kmod-nft-tproxy kmod-tun luci-app-upnp luci-app-ttyd luci-app-filemanager luci-app-wol luci-app-mwan3 iptables-nft ip6tables-nft
luci-app-openvpn luci-proto-wireguard luci-app-cloudflared qrencode tailscale zerotier adguardhome
easytier luci-app-netspeedtest luci-app-easytier

View File

@@ -1,154 +1,370 @@
#!/bin/bash #!/bin/bash
# Automatically prepare OpenWRT build dependencies for Debian/Ubuntu # Prepare the OpenWrt build dependencies.
# Supports both x86_64 and arm64 #
# Detects Ubuntu 22.04, 24.04, 26.04, Debian 12/13 and newer # Supported distributions:
# - Debian / Ubuntu / Linux Mint / Raspberry Pi OS (apt)
# - Fedora / RHEL / CentOS / Rocky / AlmaLinux (dnf, yum)
# - Arch / Manjaro / EndeavourOS (pacman)
# - openSUSE / SLES (zypper)
# - Alpine (apk)
#
# Package lists follow https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
# Packages that do not exist on the running release are skipped instead of
# aborting the whole installation.
#
# Usage: ./prepare-openwrt-env.sh [-y] [-n] [--with-go]
# -y, --yes do not ask for confirmation
# -n, --dry-run only print what would be installed
# --with-go also install the Go toolchain
#
# Author: Zhe Yuan # Author: Zhe Yuan
set -e set -euo pipefail
umask 022 umask 022
ASSUME_YES=false
DRY_RUN=false
WITH_GO=false
while [ $# -gt 0 ]; do
case "$1" in
-y|--yes) ASSUME_YES=true ;;
-n|--dry-run) DRY_RUN=true ;;
--with-go) WITH_GO=true ;;
-h|--help)
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "[ERROR] Unknown option: $1" >&2
exit 1
;;
esac
shift
done
echo "========================================" echo "========================================"
echo " OpenWRT Build Environment Preparation" echo " OpenWrt Build Environment Preparation"
echo "========================================" echo "========================================"
# --- Detect architecture --- # --- Detect architecture ---
ARCH=$(uname -m) ARCH="$(uname -m)"
echo "[INFO] Detected architecture: $ARCH" echo "[INFO] Architecture: $ARCH"
# --- Detect OS version --- # --- Detect distribution ---
if [ -f /etc/os-release ]; then DISTRO="unknown"
DISTRO_LIKE=""
VERSION_ID="unknown"
PRETTY_NAME="unknown"
if [ -r /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release . /etc/os-release
DISTRO="$ID" DISTRO="${ID:-unknown}"
DISTRO_LIKE="${ID_LIKE:-}"
VERSION_ID="${VERSION_ID:-unknown}" VERSION_ID="${VERSION_ID:-unknown}"
PRETTY_NAME="${PRETTY_NAME:-$DISTRO $VERSION_ID}"
else else
echo "[ERROR] Cannot detect system version (no /etc/os-release)" echo "[ERROR] Cannot detect the distribution (no /etc/os-release)." >&2
exit 1 exit 1
fi fi
echo "[INFO] Detected distribution: $DISTRO $VERSION_ID" echo "[INFO] Distribution: $PRETTY_NAME (id=$DISTRO)"
# --- Version helpers --- # --- Detect package manager ---
version_ge() { PKG_MGR=""
local current="${1:-unknown}" for candidate in apt-get dnf yum pacman zypper apk; do
local minimum="$2" if command -v "$candidate" >/dev/null 2>&1; then
PKG_MGR="$candidate"
break
fi
done
if [[ "$current" == "unknown" ]]; then if [ -z "$PKG_MGR" ]; then
return 1 echo "[ERROR] No supported package manager found." >&2
echo "[ERROR] Supported: apt-get, dnf, yum, pacman, zypper, apk." >&2
exit 1
fi fi
if command -v dpkg >/dev/null 2>&1; then echo "[INFO] Package manager: $PKG_MGR"
dpkg --compare-versions "$current" ge "$minimum"
# --- Privilege escalation ---
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else else
[[ "$(printf '%s\n%s\n' "$minimum" "$current" | sort -V | head -n1)" == "$minimum" ]] echo "[ERROR] Not running as root and 'sudo' is not available." >&2
exit 1
fi fi
fi
is_x86_64() {
[ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]
} }
IS_UBUNTU_24_OR_NEWER=false is_arm64() {
IS_UBUNTU_26_OR_NEWER=false [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]
IS_DEBIAN_13_OR_NEWER=false }
if [[ "$DISTRO" == "ubuntu" ]] && version_ge "$VERSION_ID" "24.04"; then # ---------------------------------------------------------------------------
IS_UBUNTU_24_OR_NEWER=true # Package lists
#
# REQUIRED_PACKAGES installation fails if one of them cannot be installed
# OPTIONAL_PACKAGES nice to have; silently skipped when unavailable
# ---------------------------------------------------------------------------
REQUIRED_PACKAGES=""
OPTIONAL_PACKAGES=""
REFRESH_CMD=""
INSTALL_CMD=""
case "$PKG_MGR" in
apt-get)
REFRESH_CMD="$SUDO apt-get update"
INSTALL_CMD="$SUDO apt-get install -y --no-install-recommends"
REQUIRED_PACKAGES="build-essential clang flex bison g++ gawk gcc gettext git \
file wget unzip rsync swig time python3 python3-dev python3-setuptools \
libncurses-dev libssl-dev zlib1g-dev libelf-dev make patch diffutils"
# Extras from the OpenWrt 25.xx / Debian Trixie list. Names come and go
# between releases, so everything here is filtered before installing.
OPTIONAL_PACKAGES="bc binutils-gold ccache ecj fastjar help2man libbsd-dev \
liblzma-dev meson mold mtd-utils ninja-build pbzip2 pigz pkg-config \
subversion texinfo u-boot-tools xsltproc xxd zstd bzip2 quilt asciidoc \
device-tree-compiler java-propose-classpath python3-distutils \
python3-distutils-extra libncurses5-dev"
if is_x86_64; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES gcc-multilib g++-multilib glibc-source"
elif is_arm64; then
# arm64 has no native multilib; the cross variants are optional and the
# available set differs a lot between releases (e.g. the s390x variants
# were dropped in Ubuntu 26.04).
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES \
gcc-multilib-i686-linux-gnu g++-multilib-i686-linux-gnu \
gcc-multilib-x86-64-linux-gnu g++-multilib-x86-64-linux-gnu \
gcc-multilib-x86-64-linux-gnux32 g++-multilib-x86-64-linux-gnux32 \
gcc-multilib-s390x-linux-gnu g++-multilib-s390x-linux-gnu \
libc6-dev-i386-cross libc6-dev-i386-amd64-cross libc6-dev-i386-x32-cross \
glibc-source"
fi fi
if [[ "$DISTRO" == "ubuntu" ]] && version_ge "$VERSION_ID" "26.04"; then if [ "$WITH_GO" = true ]; then
IS_UBUNTU_26_OR_NEWER=true OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang golang-go"
fi fi
;;
if [[ "$DISTRO" == "debian" ]] && version_ge "$VERSION_ID" "13"; then dnf|yum)
IS_DEBIAN_13_OR_NEWER=true REFRESH_CMD="$SUDO $PKG_MGR makecache"
INSTALL_CMD="$SUDO $PKG_MGR install -y --setopt=install_weak_deps=False"
REQUIRED_PACKAGES="gcc gcc-c++ make git-core patch diffutils file wget \
unzip rsync tar which bzip2 ncurses-devel openssl-devel zlib-devel \
python3 python3-devel python3-setuptools"
OPTIONAL_PACKAGES="bash-completion elfutils-libelf-devel zlib-static \
glibc-static swig quilt ccache perl-base perl-Data-Dumper \
perl-File-Compare perl-File-Copy perl-FindBin perl-IPC-Cmd perl-JSON-PP \
perl-lib perl-Thread-Queue perl-Time-Piece libxslt bc texinfo \
dtc zstd pkgconf-pkg-config"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang"
fi fi
;;
# --- Base packages (common to all) --- pacman)
COMMON_PACKAGES="build-essential clang flex bison g++ gawk \ REFRESH_CMD="$SUDO pacman -Sy"
gettext git libssl-dev rsync swig unzip zlib1g-dev file wget bzip2" INSTALL_CMD="$SUDO pacman -S --needed --noconfirm"
# --- Determine specific package set --- REQUIRED_PACKAGES="base-devel bash bzip2 git libelf libxslt ncurses openssl \
if [[ "$ARCH" == "x86_64" || "$ARCH" == "amd64" ]]; then rsync swig time unzip util-linux wget which zlib python python-setuptools"
echo "[INFO] Preparing environment for x86_64."
if [[ "$IS_UBUNTU_26_OR_NEWER" == "true" ]]; then OPTIONAL_PACKAGES="ccache quilt pkgconf diffutils patch tar make gawk \
echo "[INFO] Detected Ubuntu 26.04+ (no python3-distutils; OpenWrt docs use libncurses-dev)." subversion asciidoc dtc zstd"
EXTRA_PACKAGES="gcc-multilib g++-multilib glibc-source libncurses-dev python3-setuptools"
elif [[ "$IS_UBUNTU_24_OR_NEWER" == "true" ]]; then if [ "$WITH_GO" = true ]; then
echo "[INFO] Detected Ubuntu 24.04+ (Python 3.12+, no python3-distutils)." OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
EXTRA_PACKAGES="gcc-multilib g++-multilib libncurses5-dev python3-setuptools" fi
elif [[ "$IS_DEBIAN_13_OR_NEWER" == "true" ]]; then ;;
echo "[INFO] Detected Debian 13+ (no python3-distutils)."
EXTRA_PACKAGES="gcc-multilib g++-multilib libncurses-dev python3-setuptools" zypper)
REFRESH_CMD="$SUDO zypper --non-interactive refresh"
INSTALL_CMD="$SUDO zypper --non-interactive install --no-recommends"
REQUIRED_PACKAGES="bash bc binutils bzip2 flex gawk gcc gcc-c++ \
gettext-tools git-core make ncurses-devel patch rsync unzip util-linux \
wget zlib-devel libopenssl-devel python3-devel python3-setuptools"
OPTIONAL_PACKAGES="asciidoc fastjar intltool libxslt-tools mercurial \
perl-ExtUtils-MakeMaker sdcc quilt ccache libelf-devel swig zstd dtc"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
fi
;;
apk)
REFRESH_CMD="$SUDO apk update"
INSTALL_CMD="$SUDO apk add"
REQUIRED_PACKAGES="argp-standalone bash bc binutils bzip2 coreutils \
diffutils elfutils-dev findutils flex g++ gawk gcc gettext git grep gzip \
linux-headers make ncurses-dev openssl-dev patch perl python3-dev rsync \
tar unzip util-linux wget zlib-dev"
OPTIONAL_PACKAGES="asciidoc cdrkit intltool libxslt musl-fts-dev \
musl-libintl musl-obstack-dev swig quilt ccache zstd"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
fi
;;
esac
# ---------------------------------------------------------------------------
# Availability filtering
# ---------------------------------------------------------------------------
pkg_available() {
local pkg="$1"
case "$PKG_MGR" in
apt-get)
# apt-cache policy prints no "Candidate:" line for purely virtual names
# and "(none)" when no installable version exists in the repositories.
local policy=""
policy="$(apt-cache policy "$pkg" 2>/dev/null || true)"
case "$policy" in
*"Candidate: (none)"*) return 1 ;;
*"Candidate:"*) return 0 ;;
*) return 1 ;;
esac
;;
dnf) dnf -q info "$pkg" >/dev/null 2>&1 ;;
yum) yum -q info "$pkg" >/dev/null 2>&1 ;;
pacman) pacman -Si "$pkg" >/dev/null 2>&1 || pacman -Sg "$pkg" >/dev/null 2>&1 ;;
zypper) zypper --non-interactive --quiet search --match-exact "$pkg" >/dev/null 2>&1 ;;
apk) [ -n "$(apk search -x "$pkg" 2>/dev/null)" ] ;;
*) return 0 ;;
esac
}
filter_available() {
local pkg
local kept=""
local dropped=""
for pkg in $1; do
if pkg_available "$pkg"; then
kept="$kept $pkg"
else else
echo "[INFO] Detected older Ubuntu/Debian (with python3-distutils)." dropped="$dropped $pkg"
EXTRA_PACKAGES="gcc-multilib g++-multilib libncurses-dev python3-distutils python3-setuptools"
fi fi
done
INSTALL_PACKAGES="$COMMON_PACKAGES $EXTRA_PACKAGES" FILTER_KEPT="${kept# }"
FILTER_DROPPED="${dropped# }"
}
elif [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then echo
echo "[INFO] Preparing environment for ARM64." echo "[INFO] Refreshing package metadata..."
if [ "$DRY_RUN" = true ]; then
# Note: arm64 has no native multilib; install cross multilib packages echo "[DRY-RUN] $REFRESH_CMD"
MULTILIB_PACKAGES="
gcc-multilib-i686-linux-gnu gcc-multilib-s390x-linux-gnu \
gcc-multilib-x86-64-linux-gnu gcc-multilib-x86-64-linux-gnux32 \
g++-multilib-i686-linux-gnu g++-multilib-s390x-linux-gnu \
g++-multilib-x86-64-linux-gnu g++-multilib-x86-64-linux-gnux32 \
libc6-dev-i386-amd64-cross libc6-dev-i386-cross libc6-dev-i386-x32-cross"
if [[ "$IS_UBUNTU_26_OR_NEWER" == "true" ]]; then
EXTRA_PACKAGES="glibc-source libncurses-dev python3-setuptools"
elif [[ "$IS_UBUNTU_24_OR_NEWER" == "true" ]]; then
EXTRA_PACKAGES="libncurses5-dev python3-setuptools"
elif [[ "$IS_DEBIAN_13_OR_NEWER" == "true" ]]; then
EXTRA_PACKAGES="libncurses-dev python3-setuptools"
else else
EXTRA_PACKAGES="libncurses-dev python3-distutils python3-setuptools" $REFRESH_CMD
fi fi
INSTALL_PACKAGES="$COMMON_PACKAGES $EXTRA_PACKAGES $MULTILIB_PACKAGES golang" echo "[INFO] Checking package availability for this release..."
else filter_available "$REQUIRED_PACKAGES"
echo "[WARN] Unknown architecture: $ARCH" REQUIRED_AVAILABLE="$FILTER_KEPT"
echo "[WARN] Attempting to use x86_64 package list." REQUIRED_MISSING="$FILTER_DROPPED"
if [[ "$IS_UBUNTU_26_OR_NEWER" == "true" ]]; then
INSTALL_PACKAGES="$COMMON_PACKAGES gcc-multilib g++-multilib glibc-source libncurses-dev python3-setuptools" filter_available "$OPTIONAL_PACKAGES"
else OPTIONAL_AVAILABLE="$FILTER_KEPT"
INSTALL_PACKAGES="$COMMON_PACKAGES gcc-multilib g++-multilib libncurses-dev python3-setuptools" OPTIONAL_MISSING="$FILTER_DROPPED"
fi
INSTALL_PACKAGES="$REQUIRED_AVAILABLE $OPTIONAL_AVAILABLE"
INSTALL_PACKAGES="$(echo "$INSTALL_PACKAGES" | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')"
if [ -z "$INSTALL_PACKAGES" ]; then
echo "[ERROR] No installable package found. Are the repositories configured?" >&2
exit 1
fi fi
# --- Display package list ---
echo echo
echo "========================================" echo "========================================"
echo "📦 Packages to install:" echo "📦 Packages to install:"
echo "$INSTALL_PACKAGES" echo "$INSTALL_PACKAGES" | tr ' ' '\n' | sed 's/^/ /'
echo "========================================" echo "========================================"
if [ -n "$REQUIRED_MISSING" ]; then
echo echo
echo "[WARN] Required packages not offered by this release (build may fail):"
echo " $REQUIRED_MISSING"
fi
echo "[INFO] Updating APT repositories..." if [ -n "$OPTIONAL_MISSING" ]; then
sudo apt update -y
echo "[INFO] Installing packages..."
sudo apt install -y $INSTALL_PACKAGES
# --- Post installation notice ---
echo
echo "========================================"
echo "✅ OpenWRT build environment setup complete!"
echo "========================================"
echo echo
echo "[INFO] Optional packages unavailable here, skipped:"
echo " $OPTIONAL_MISSING"
fi
if [[ "$ARCH" == "x86_64" || "$ARCH" == "amd64" ]]; then if [ "$DRY_RUN" = true ]; then
echo "👉 [NOTE] Go language is optional on x86_64." echo
echo " If you plan to compile packages requiring Go (e.g. firewall4, netifd), install it manually:" echo "[DRY-RUN] $INSTALL_CMD $INSTALL_PACKAGES"
echo " sudo apt install golang -y" exit 0
else fi
echo "👉 [INFO] Go language installed for ARM64 cross compilation."
if [ "$ASSUME_YES" != true ]; then
echo
read -r -p "Proceed with installation? [Y/n]: " reply
case "$reply" in
[nN]|[nN][oO]) echo "[INFO] Aborted by user."; exit 0 ;;
esac
fi fi
echo echo
echo "⚙️ After running 'make menuconfig', set Go toolchain path if required:" echo "[INFO] Installing packages..."
echo " Example: /usr/lib/go-1.18"
FAILED_PACKAGES=""
if ! $INSTALL_CMD $INSTALL_PACKAGES; then
echo echo
echo "[WARN] Bulk installation failed. Retrying package by package..."
for pkg in $INSTALL_PACKAGES; do
if ! $INSTALL_CMD "$pkg" >/dev/null 2>&1; then
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
echo " [FAIL] $pkg"
fi
done
FAILED_PACKAGES="${FAILED_PACKAGES# }"
fi
echo
echo "========================================"
if [ -n "$FAILED_PACKAGES" ]; then
echo "⚠️ Setup finished with errors."
echo " Failed packages: $FAILED_PACKAGES"
else
echo "✅ OpenWrt build environment setup complete!"
fi
echo "========================================"
echo
if [ "$WITH_GO" != true ]; then
echo "👉 [NOTE] The Go toolchain is only needed for Go based packages"
echo " (e.g. xray-core, v2ray). Install it with:"
echo " ./prepare-openwrt-env.sh --with-go"
echo
fi
if is_arm64; then
echo "👉 [NOTE] arm64 has no native multilib. The cross-multilib packages are"
echo " optional and only needed for x86 targets; missing ones were skipped."
echo
fi
echo "👉 Next step:" echo "👉 Next step:"
echo " ./prepare-openwrt.sh # to clone and set up OpenWRT source" echo " ./prepare-openwrt.sh # clone and set up the OpenWrt source"
echo echo

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,172 @@
#!/bin/bash #!/bin/bash
# Automatically detect Go installation and update .config for OpenWRT # Point the OpenWrt build at an installed Go toolchain.
#
# Go based packages (tailscale, adguardhome, easytier, cloudflared, ...) need a
# host Go. By default OpenWrt builds the whole bootstrap chain from source; an
# already installed Go can replace part or all of that:
#
# --external GOLANG_BUILD_BOOTSTRAP=n, host Go is built with the
# installed toolchain directly (skips the entire chain,
# requires a Go new enough for the target version)
# default keep GOLANG_BUILD_BOOTSTRAP=y and only set the root, which
# skips the initial bootstrap step
#
# Usage: ./update-go-path.sh [options]
#
# --external skip the bootstrap chain entirely (recommended)
# --go-root <dir> use this Go instead of auto-detecting
# --skip-defconfig do not run make defconfig afterwards
# -n, --dry-run show what would change, write nothing
# -h, --help show this help
#
# Run it from the OpenWrt root: ./helper/update-go-path.sh
#
# Author: Zhe Yuan # Author: Zhe Yuan
set -e set -euo pipefail
umask 022 umask 022
CONFIG_FILE=".config" CONFIG_FILE=".config"
EXTERNAL=false
GO_ROOT=""
RUN_DEFCONFIG=true
DRY_RUN=false
echo "========================================" info() { echo "[INFO] $*"; }
echo " Go Path Auto Updater for OpenWRT" warn() { echo "[WARN] $*"; }
echo "========================================" err() { echo "[ERROR] $*" >&2; }
# --- Check for .config file --- usage() { sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//;s/^#$//'; }
if [ ! -f "$CONFIG_FILE" ]; then
echo "[ERROR] No .config file found in current directory!"
echo "Please run this script inside your OpenWRT build root."
exit 1
fi
# --- Detect available Go installations --- while [ $# -gt 0 ]; do
GO_DIRS=(/usr/lib/go-*/) case "$1" in
if [ ${#GO_DIRS[@]} -eq 0 ]; then --external) EXTERNAL=true ;;
echo "[ERROR] No Go installation found under /usr/lib/go-*" --go-root)
echo "Please install Go using: sudo apt install golang -y" if [ $# -lt 2 ] || [ -z "${2:-}" ]; then err "--go-root requires a directory."; exit 1; fi
exit 1 GO_ROOT="$2"; shift ;;
fi --skip-defconfig) RUN_DEFCONFIG=false ;;
-n|--dry-run) DRY_RUN=true ;;
# --- Extract version numbers --- -h|--help) usage; exit 0 ;;
GO_VERSIONS=() *) err "Unknown option: $1"; echo "Run '$0 --help' for usage." >&2; exit 1 ;;
for dir in "${GO_DIRS[@]}"; do esac
ver=$(basename "$dir" | sed -E 's/^go-([0-9]+\.[0-9]+).*$/\1/') shift
GO_VERSIONS+=("$ver")
done done
# --- Sort versions and pick the latest --- echo "========================================"
IFS=$'\n' sorted=($(sort -V <<<"${GO_VERSIONS[*]}")) echo " Go Toolchain Setup for OpenWrt"
LATEST_VER="${sorted[-1]}" echo "========================================"
LATEST_PATH="/usr/lib/go-${LATEST_VER}/"
echo "[INFO] Found Go versions: ${GO_VERSIONS[*]}" if [ ! -f "$CONFIG_FILE" ]; then
echo "[INFO] Using latest Go version: $LATEST_VER" err "No .config in the current directory."
echo "[INFO] Path: $LATEST_PATH" err "From the OpenWrt root, run it as: ./helper/$(basename "$0")"
exit 1
# --- Check if multiple versions exist ---
if [ ${#GO_VERSIONS[@]} -gt 1 ]; then
echo "[WARN] Multiple Go versions detected!"
echo "If you prefer to use an older version, modify manually:"
echo " CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=\"/usr/lib/go-X.XX/\""
echo
fi fi
# --- Update .config --- # --- Locate a Go installation --------------------------------------------
if grep -q '^CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=' "$CONFIG_FILE"; then if [ -z "$GO_ROOT" ]; then
echo "[INFO] Updating existing CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT..." # nullglob only drops words that actually contain a wildcard; a literal path
sed -i "s|^CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=.*|CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=\"${LATEST_PATH}\"|" "$CONFIG_FILE" # such as /usr/local/go/ survives even when it does not exist. So collect
# candidates and keep only the directories that are really there.
shopt -s nullglob
CANDIDATES=(/usr/lib/go-*/ /usr/local/go/ /usr/lib/golang/)
shopt -u nullglob
GO_DIRS=()
for dir in "${CANDIDATES[@]}"; do
[ -d "$dir" ] && GO_DIRS+=("$dir")
done
if [ ${#GO_DIRS[@]} -eq 0 ]; then
err "No Go installation found under /usr/lib/go-*, /usr/local/go or /usr/lib/golang."
err "Install one with: sudo apt install golang-1.24-go"
err "or keep the default, where OpenWrt builds its own bootstrap chain."
exit 1
fi
BEST_VER=""
for dir in "${GO_DIRS[@]}"; do
[ -x "${dir}bin/go" ] || continue
ver="$("${dir}bin/go" version 2>/dev/null | awk '{print $3}' | sed 's/^go//')"
[ -n "$ver" ] || continue
if [ -z "$BEST_VER" ] ||
[ "$(printf '%s\n%s\n' "$BEST_VER" "$ver" | sort -V | tail -n 1)" = "$ver" ]; then
BEST_VER="$ver"
GO_ROOT="$dir"
fi
done
if [ -z "$GO_ROOT" ]; then
err "Found Go directories but none contained a working bin/go."
exit 1
fi
if [ ${#GO_DIRS[@]} -gt 1 ]; then
info "Go installations found: ${GO_DIRS[*]}"
info "Using the newest; override with --go-root <dir>."
fi
fi
GO_ROOT="${GO_ROOT%/}/"
if [ ! -x "${GO_ROOT}bin/go" ]; then
err "No usable Go compiler at ${GO_ROOT}bin/go"
exit 1
fi
GO_VER="$("${GO_ROOT}bin/go" version | awk '{print $3}' | sed 's/^go//')"
info "Go toolchain: $GO_ROOT (version $GO_VER)"
# --- Warn if it is older than what the tree wants -------------------------
BOOTSTRAP_MK="feeds/packages/lang/golang/golang-bootstrap/Makefile"
if [ -f "$BOOTSTRAP_MK" ]; then
NEED="$(awk -F':=' '/^GO_VERSION_MAJOR_MINOR/ {print $2; exit}' "$BOOTSTRAP_MK" | tr -d ' ')"
if [ -n "$NEED" ] &&
[ "$(printf '%s\n%s\n' "$NEED" "$GO_VER" | sort -V | head -n 1)" != "$NEED" ]; then
warn "Installed Go $GO_VER is older than the bootstrap version $NEED this tree expects."
warn "The build may fail; consider installing golang-$NEED-go."
fi
fi
# --- Apply ----------------------------------------------------------------
set_config() {
local key="$1" val="$2"
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] $key=$val"
return
fi
# Drop every existing form of the symbol, then append once.
sed -i "/^${key}=/d;/^# ${key} is not set$/d" "$CONFIG_FILE"
if [ "$val" = "n" ]; then
echo "# $key is not set" >> "$CONFIG_FILE"
else else
echo "[INFO] Adding CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT..." echo "$key=$val" >> "$CONFIG_FILE"
echo "CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=\"${LATEST_PATH}\"" >> "$CONFIG_FILE" fi
}
set_config CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT "\"$GO_ROOT\""
if [ "$EXTERNAL" = true ]; then
set_config CONFIG_GOLANG_BUILD_BOOTSTRAP n
info "Bootstrap chain disabled; host Go is built with the installed toolchain."
else
set_config CONFIG_GOLANG_BUILD_BOOTSTRAP y
info "Bootstrap chain kept (only the initial step is skipped)."
info "Pass --external to skip it entirely."
fi fi
# --- Verify result --- if [ "$DRY_RUN" = true ]; then
echo echo
echo "[SUCCESS] Go path updated in .config:" info "Dry run complete; .config was not modified."
grep 'CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT' "$CONFIG_FILE" exit 0
fi
if [ "$RUN_DEFCONFIG" = true ]; then
info "Running make defconfig..."
make defconfig >/dev/null
fi
echo echo
echo "========================================" echo "========================================"
echo " Go path update complete!" echo "OK Go configuration updated."
echo "========================================" echo "========================================"
echo grep -E '^(CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT|CONFIG_GOLANG_BUILD_BOOTSTRAP)|^# CONFIG_GOLANG_BUILD_BOOTSTRAP' "$CONFIG_FILE" | sed 's/^/ /'
echo "👉 If you wish to override manually, edit .config and change:"
echo " CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=\"/usr/lib/go-X.XX/\""
echo echo