From fbaa67eb8d5a35d08fef763f0a7fbd7082cbe2af Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sat, 1 Aug 2026 04:27:20 +0800 Subject: [PATCH] feat(app): add nxv --- apps/nxv/0.7.0/.env.sample | 3 ++ apps/nxv/0.7.0/data.yml | 35 +++++++++++++ apps/nxv/0.7.0/docker-compose.yml | 36 ++++++++++++++ apps/nxv/0.7.0/scripts/init.sh | 72 +++++++++++++++++++++++++++ apps/nxv/0.7.0/scripts/uninstall.sh | 5 ++ apps/nxv/0.7.0/scripts/upgrade.sh | 5 ++ apps/nxv/README.md | 32 ++++++++++++ apps/nxv/data.yml | 30 +++++++++++ apps/nxv/latest/.env.sample | 3 ++ apps/nxv/latest/data.yml | 35 +++++++++++++ apps/nxv/latest/docker-compose.yml | 36 ++++++++++++++ apps/nxv/latest/scripts/init.sh | 72 +++++++++++++++++++++++++++ apps/nxv/latest/scripts/uninstall.sh | 5 ++ apps/nxv/latest/scripts/upgrade.sh | 5 ++ apps/nxv/logo.png | Bin 0 -> 570 bytes 15 files changed, 374 insertions(+) create mode 100644 apps/nxv/0.7.0/.env.sample create mode 100644 apps/nxv/0.7.0/data.yml create mode 100644 apps/nxv/0.7.0/docker-compose.yml create mode 100755 apps/nxv/0.7.0/scripts/init.sh create mode 100755 apps/nxv/0.7.0/scripts/uninstall.sh create mode 100755 apps/nxv/0.7.0/scripts/upgrade.sh create mode 100644 apps/nxv/README.md create mode 100644 apps/nxv/data.yml create mode 100644 apps/nxv/latest/.env.sample create mode 100644 apps/nxv/latest/data.yml create mode 100644 apps/nxv/latest/docker-compose.yml create mode 100755 apps/nxv/latest/scripts/init.sh create mode 100755 apps/nxv/latest/scripts/uninstall.sh create mode 100755 apps/nxv/latest/scripts/upgrade.sh create mode 100644 apps/nxv/logo.png diff --git a/apps/nxv/0.7.0/.env.sample b/apps/nxv/0.7.0/.env.sample new file mode 100644 index 000000000..4bfa3a86d --- /dev/null +++ b/apps/nxv/0.7.0/.env.sample @@ -0,0 +1,3 @@ +PANEL_APP_PORT_HTTP=8080 +APP_DATA_DIR=./data +CONTAINER_NAME=1Panel-localnxv diff --git a/apps/nxv/0.7.0/data.yml b/apps/nxv/0.7.0/data.yml new file mode 100644 index 000000000..ec6baebcd --- /dev/null +++ b/apps/nxv/0.7.0/data.yml @@ -0,0 +1,35 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: Порт HTTP + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori data + pt-br: Diretorio de dados + required: true + type: text diff --git a/apps/nxv/0.7.0/docker-compose.yml b/apps/nxv/0.7.0/docker-compose.yml new file mode 100644 index 000000000..34fb9dc7e --- /dev/null +++ b/apps/nxv/0.7.0/docker-compose.yml @@ -0,0 +1,36 @@ +services: + nxv: + image: "ghcr.io/utensils/nxv:0.7.0@sha256:5c39000455d3bb91f91f311b98df42127fbf926381eaa67abdb4c2d234b0d799" + container_name: ${CONTAINER_NAME} + command: + - --db-path + - /data/index.db + - serve + - --host + - 0.0.0.0 + - --port + - "8080" + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${APP_DATA_DIR}:/data" + restart: unless-stopped + healthcheck: + test: + - CMD + - nxv + - --db-path + - /data/index.db + - stats + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + labels: + createdBy: "Apps" + networks: + - 1panel-network + +networks: + 1panel-network: + external: true diff --git a/apps/nxv/0.7.0/scripts/init.sh b/apps/nxv/0.7.0/scripts/init.sh new file mode 100755 index 000000000..5fbf1e52e --- /dev/null +++ b/apps/nxv/0.7.0/scripts/init.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" +IMAGE="ghcr.io/utensils/nxv:0.7.0@sha256:5c39000455d3bb91f91f311b98df42127fbf926381eaa67abdb4c2d234b0d799" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E '^APP_DATA_DIR=' "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + value="${value%$'\r'}" + strip_matching_quotes "$value" +} + +if [[ ${APP_DATA_DIR+x} ]]; then + DATA_DIR_RAW="$APP_DATA_DIR" +else + DATA_DIR_RAW="$(read_env_value)" +fi +DATA_DIR_RAW="$(strip_matching_quotes "${DATA_DIR_RAW:-./data}")" + +[[ -n "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +case "$DATA_DIR_RAW" in + *$'\n'* | *$'\r'* | *:*) fail "APP_DATA_DIR contains unsupported characters" ;; +esac + +if [[ "$DATA_DIR_RAW" = /* ]]; then + [[ ! -L "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be a symbolic link" + DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_RAW")" + [[ "$DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +else + DATA_DIR_PATH="${ROOT_DIR}/${DATA_DIR_RAW#./}" + [[ ! -L "$DATA_DIR_PATH" ]] || fail "APP_DATA_DIR must not be a symbolic link" + ROOT_DIR_ABS="$(realpath -m -- "$ROOT_DIR")" + DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_PATH")" + case "$DATA_DIR_ABS" in + "${ROOT_DIR_ABS}"/*) ;; + *) fail "Relative APP_DATA_DIR must remain inside the app version directory" ;; + esac +fi + +if [[ -e "$DATA_DIR_ABS" && ! -d "$DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi +install -d -m 0750 -- "$DATA_DIR_ABS" + +docker run --rm --volume "${DATA_DIR_ABS}:/data" "$IMAGE" \ + --db-path /data/index.db sync + +[[ -s "$DATA_DIR_ABS/index.db" ]] || fail "nxv sync did not create index.db" +[[ -s "$DATA_DIR_ABS/index.bloom" ]] || fail "nxv sync did not create index.bloom" diff --git a/apps/nxv/0.7.0/scripts/uninstall.sh b/apps/nxv/0.7.0/scripts/uninstall.sh new file mode 100755 index 000000000..a253ebf23 --- /dev/null +++ b/apps/nxv/0.7.0/scripts/uninstall.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# The package index is intentionally retained for recovery or reinstallation. +exit 0 diff --git a/apps/nxv/0.7.0/scripts/upgrade.sh b/apps/nxv/0.7.0/scripts/upgrade.sh new file mode 100755 index 000000000..5ec28d784 --- /dev/null +++ b/apps/nxv/0.7.0/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec "$SCRIPT_DIR/init.sh" diff --git a/apps/nxv/README.md b/apps/nxv/README.md new file mode 100644 index 000000000..22bb46979 --- /dev/null +++ b/apps/nxv/README.md @@ -0,0 +1,32 @@ +# nxv + +## 产品介绍 + +nxv 是 Nix 软件包历史版本索引服务。它下载并验证上游预构建索引,在浏览器界面和 HTTP API 中快速查询软件包版本、出现时间及对应的 nixpkgs 提交。 + +## 主要功能 + +- 按软件包名、版本和描述检索 nixpkgs 历史 +- 查看软件包版本时间线和对应提交 +- 提供 Web 界面、OpenAPI 文档和 HTTP API +- 使用 SQLite 与 Bloom Filter 提供本地快速查询 + +## 访问说明 + +- 首次安装需要下载并验证约 220 MB 的压缩索引,完成前主服务不会启动;实际磁盘占用会高于下载大小。 +- 索引保存在数据目录中,重启不会重复下载完整索引;重新安装前请按需要备份或清理该目录。 +- nxv 本身不提供用户认证。不要直接暴露到不受信任的公网,建议通过 1Panel 网站反向代理配置访问控制。 +- 固定版本用于可复现部署,`latest` 会跟随上游镜像更新。 + +## Introduction + +nxv indexes historical nixpkgs releases and exposes fast package-version search through a web interface and HTTP API. The first installation downloads and verifies an index of about 220 MB before the server starts. Keep the data directory persistent to avoid unnecessary downloads. + +## Features + +- Searches package names, versions, and descriptions across nixpkgs history +- Shows version timelines and the corresponding nixpkgs commits +- Provides a web interface, OpenAPI documentation, and an HTTP API +- Uses a local SQLite index and Bloom filter for fast queries + +nxv does not provide built-in authentication. Restrict public access with 1Panel reverse-proxy authentication or another trusted access-control layer. diff --git a/apps/nxv/data.yml b/apps/nxv/data.yml new file mode 100644 index 000000000..34ef6d844 --- /dev/null +++ b/apps/nxv/data.yml @@ -0,0 +1,30 @@ +name: nxv +tags: + - 实用工具 +title: 快速检索 Nix 软件包历史版本的索引服务 +description: 快速检索 Nix 软件包历史版本的索引服务 +additionalProperties: + key: nxv + name: nxv + tags: + - Tool + shortDescZh: 快速检索 Nix 软件包历史版本的索引服务 + shortDescEn: Index service for quickly searching historical Nix package versions + description: + en: Index service for quickly searching historical Nix package versions + zh: 快速检索 Nix 软件包历史版本的索引服务 + zh-Hant: 快速搜尋 Nix 軟體套件歷史版本的索引服務 + ja: Nix パッケージの過去のバージョンを素早く検索するインデックスサービス + ko: Nix 패키지의 이전 버전을 빠르게 검색하는 인덱스 서비스 + ru: Сервис индекса для быстрого поиска исторических версий пакетов Nix + ms: Perkhidmatan indeks untuk mencari versi sejarah pakej Nix dengan pantas + pt-br: Servico de indice para pesquisar rapidamente versoes historicas de pacotes Nix + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://utensils.io/nxv/ + github: https://github.com/utensils/nxv + document: https://utensils.io/nxv/ + architectures: + - amd64 diff --git a/apps/nxv/latest/.env.sample b/apps/nxv/latest/.env.sample new file mode 100644 index 000000000..4bfa3a86d --- /dev/null +++ b/apps/nxv/latest/.env.sample @@ -0,0 +1,3 @@ +PANEL_APP_PORT_HTTP=8080 +APP_DATA_DIR=./data +CONTAINER_NAME=1Panel-localnxv diff --git a/apps/nxv/latest/data.yml b/apps/nxv/latest/data.yml new file mode 100644 index 000000000..ec6baebcd --- /dev/null +++ b/apps/nxv/latest/data.yml @@ -0,0 +1,35 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: Порт HTTP + ms: Port HTTP + pt-br: Porta HTTP + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori data + pt-br: Diretorio de dados + required: true + type: text diff --git a/apps/nxv/latest/docker-compose.yml b/apps/nxv/latest/docker-compose.yml new file mode 100644 index 000000000..3235bde6d --- /dev/null +++ b/apps/nxv/latest/docker-compose.yml @@ -0,0 +1,36 @@ +services: + nxv: + image: "ghcr.io/utensils/nxv:latest" + container_name: ${CONTAINER_NAME} + command: + - --db-path + - /data/index.db + - serve + - --host + - 0.0.0.0 + - --port + - "8080" + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${APP_DATA_DIR}:/data" + restart: unless-stopped + healthcheck: + test: + - CMD + - nxv + - --db-path + - /data/index.db + - stats + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + labels: + createdBy: "Apps" + networks: + - 1panel-network + +networks: + 1panel-network: + external: true diff --git a/apps/nxv/latest/scripts/init.sh b/apps/nxv/latest/scripts/init.sh new file mode 100755 index 000000000..d63e16bda --- /dev/null +++ b/apps/nxv/latest/scripts/init.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" +IMAGE="ghcr.io/utensils/nxv:latest" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E '^APP_DATA_DIR=' "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + value="${value%$'\r'}" + strip_matching_quotes "$value" +} + +if [[ ${APP_DATA_DIR+x} ]]; then + DATA_DIR_RAW="$APP_DATA_DIR" +else + DATA_DIR_RAW="$(read_env_value)" +fi +DATA_DIR_RAW="$(strip_matching_quotes "${DATA_DIR_RAW:-./data}")" + +[[ -n "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +case "$DATA_DIR_RAW" in + *$'\n'* | *$'\r'* | *:*) fail "APP_DATA_DIR contains unsupported characters" ;; +esac + +if [[ "$DATA_DIR_RAW" = /* ]]; then + [[ ! -L "$DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be a symbolic link" + DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_RAW")" + [[ "$DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +else + DATA_DIR_PATH="${ROOT_DIR}/${DATA_DIR_RAW#./}" + [[ ! -L "$DATA_DIR_PATH" ]] || fail "APP_DATA_DIR must not be a symbolic link" + ROOT_DIR_ABS="$(realpath -m -- "$ROOT_DIR")" + DATA_DIR_ABS="$(realpath -m -- "$DATA_DIR_PATH")" + case "$DATA_DIR_ABS" in + "${ROOT_DIR_ABS}"/*) ;; + *) fail "Relative APP_DATA_DIR must remain inside the app version directory" ;; + esac +fi + +if [[ -e "$DATA_DIR_ABS" && ! -d "$DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi +install -d -m 0750 -- "$DATA_DIR_ABS" + +docker run --rm --volume "${DATA_DIR_ABS}:/data" "$IMAGE" \ + --db-path /data/index.db sync + +[[ -s "$DATA_DIR_ABS/index.db" ]] || fail "nxv sync did not create index.db" +[[ -s "$DATA_DIR_ABS/index.bloom" ]] || fail "nxv sync did not create index.bloom" diff --git a/apps/nxv/latest/scripts/uninstall.sh b/apps/nxv/latest/scripts/uninstall.sh new file mode 100755 index 000000000..a253ebf23 --- /dev/null +++ b/apps/nxv/latest/scripts/uninstall.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# The package index is intentionally retained for recovery or reinstallation. +exit 0 diff --git a/apps/nxv/latest/scripts/upgrade.sh b/apps/nxv/latest/scripts/upgrade.sh new file mode 100755 index 000000000..5ec28d784 --- /dev/null +++ b/apps/nxv/latest/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec "$SCRIPT_DIR/init.sh" diff --git a/apps/nxv/logo.png b/apps/nxv/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e30554f00cbc39e754feada460de9c24e3dd054 GIT binary patch literal 570 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD8Ax&oe*=;X2LgOTT!D1klBW>Vy7Y1D;-}3^ zp0zH0+P3&<{i;`OOP;kaf8MhUtYGmou(D^ZOP;{gKWkt5tQ{;~zvOMpf=58{*2Pc2 z3V@11%G$w3wJwJ00-6kzZCUgTXc17hbJ@eTMNiw8JO;9RR{^zytyuhQ=FBrKOF`OO zfo@y;v>k|+JZT3yV=2hk`c;pB;vHaJ&iVElKtI@*1o;Is9C`4^u|s}xH&3jBNPSgf zLCuTLL21Fe`J@**FTHf=dbwYW!Rm+vlhS6X<=d`uKHGDLb(s(+|~SZj@$G9{;u6(x=~wRWJa#d%GgJozxgh6zy85}Sb4q9e09X(kk^lez literal 0 HcmV?d00001