From 602c0416bad1fed343fc56ad8e753db4ecc89b36 Mon Sep 17 00:00:00 2001 From: Ivan Vasilev Date: Sat, 29 Aug 2026 02:20:01 +0300 Subject: [PATCH] Move panel name and version into .env, bump to Beta 0.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The product name, the channel word "Alpha" and the version were scattered across install.sh, the CLI banner, two spots in App.tsx and three package.json files, so a rename or a bump meant editing strings in code. They now live in one committed .env at the project root (BRAND, CHANNEL, VERSION), shipped inside the release archive and read at runtime: the CLI loads it after cli.env — so real env and machine settings still win — and injects the three values into awg-ui, which serves them from GET /ui/brand for the wordmark. awg-ui also parses the same file directly, so `npm run server` in dev shows what production shows. install.sh keeps its own VERSION and CHANNEL and says why: it prints the banner and builds the release URL before the archive holding .env has been downloaded. It writes BRAND into cli.env only when the operator passed one explicitly, so the file stays the single source otherwise. Version bumped to Beta 0.2.0 across .env, install.sh and the three package.json files. --- .env | 14 +++++++++++++ .github/workflows/release.yml | 2 +- README.md | 14 ++++++++++++- awg-ctrl/package.json | 2 +- awg-ui/package.json | 2 +- awg-ui/server.ts | 21 +++++++++++--------- awg-ui/src/App.tsx | 5 +++-- awg-ui/src/lib/shared.ts | 8 ++++---- cli/package.json | 2 +- cli/src/index.ts | 37 ++++++++++++++++++++--------------- install.sh | 19 ++++++++++++++---- 11 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..535210e --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +# Имя панели и версия. Единственное место, где они заданы: отсюда их читают CLI +# (баннер меню) и awg-ui (вордмарк в панели, GET /ui/brand). Файл едет в архив +# релиза и лежит в /opt/awg-control/.env на сервере. +# +# Переменные окружения перекрывают эти значения (BRAND=… в cli.env пишет +# install.sh, если оператор задал своё имя при установке). +# +# ⚠️ VERSION и CHANNEL продублированы в install.sh: установщик печатает баннер и +# собирает URL релиза ДО того, как архив с этим файлом скачан. При бампе версии +# правь оба места (и три package.json). + +BRAND=Forgetting +CHANNEL=Beta +VERSION=0.2.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41aea26..b772b94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: | VERSION="${GITHUB_REF_NAME#v}" mkdir "awgcontrol-${VERSION}" - cp -r awg-ctrl awg-ui cli "awgcontrol-${VERSION}/" + cp -r awg-ctrl awg-ui cli .env "awgcontrol-${VERSION}/" tar --exclude='node_modules' -czf "awgcontrol-${VERSION}.tar.gz" "awgcontrol-${VERSION}/" - name: Publish release diff --git a/README.md b/README.md index a2c9c0a..575c962 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ AWG_GEN=auto bash <(curl -Ls https://git.ma7neko.ru/maeneko/forgetting/raw/branc | `REPO_BASE` | `https://git.ma7neko.ru/maeneko/forgetting` | основной источник архива релиза | | `REPO_FALLBACK` | `https://github.com/maeneko/forgetting` | запасной, если в основном релиза ещё нет | | `ARCHIVE_URL` | — | полный URL архива, перекрывает оба варианта | -| `BRAND` | `Forgetting` | имя продукта в баннерах, systemd-юните и панели | +| `BRAND` | из `.env` | имя продукта в баннерах, systemd-юните и панели | Путь релиза у Gitea и GitHub одинаковый (`/releases/download/v<версия>/awgcontrol-<версия>.tar.gz`), так что подходит любой из них. Если архив не найден в основном источнике, @@ -63,6 +63,18 @@ REPO_BASE=https://git.example.org/me/vpn BRAND="My VPN" \ bash <(curl -Ls https://git.example.org/me/vpn/raw/branch/main/install.sh) ``` +Имя панели, канал и версия лежат в `.env` в корне проекта (едет в архиве +релиза, на сервере — `/opt/awg-control/.env`): + +```env +BRAND=Forgetting +CHANNEL=Beta +VERSION=0.2.0 +``` + +Оттуда их читают CLI (баннер меню) и панель (вордмарк, `GET /ui/brand`). +`BRAND=` при установке перекрывает значение из файла. + Сама панель и её API к домену не привязаны: работают по адресу того сервера, куда установлены, внешних сервисов не требуют. ## Управление diff --git a/awg-ctrl/package.json b/awg-ctrl/package.json index dede990..258a280 100644 --- a/awg-ctrl/package.json +++ b/awg-ctrl/package.json @@ -1,6 +1,6 @@ { "name": "awg-control", - "version": "0.1.4+2", + "version": "0.2.0", "description": "", "main": "dist/index.js", "scripts": { diff --git a/awg-ui/package.json b/awg-ui/package.json index 4c5b81a..7ee32b9 100644 --- a/awg-ui/package.json +++ b/awg-ui/package.json @@ -1,6 +1,6 @@ { "name": "awg-ui", - "version": "0.1.4+2", + "version": "0.2.0", "private": true, "scripts": { "dev": "vite", diff --git a/awg-ui/server.ts b/awg-ui/server.ts index d7a9078..191b6e0 100644 --- a/awg-ui/server.ts +++ b/awg-ui/server.ts @@ -16,16 +16,19 @@ const UI_PASS = process.env.UI_PASS ?? ""; const JWT_SECRET = process.env.JWT_SECRET ?? crypto.randomBytes(32).toString("hex"); const CTRL = `http://127.0.0.1:${process.env.AWGCTRL_PORT ?? "3005"}`; -// Название продукта и версия для панели. BRAND приходит из cli.env (пишет -// install.sh), версия — из package.json: в semver она в форме 0.1.4+2, в -// баннерах и релизах — через точку. -const BRAND = process.env.BRAND || "Forgetting"; -const VERSION = ((): string => { +// Имя панели, канал и версия. В проде их прокидывает CLI (он читает .env в +// корне проекта и cli.env); при самостоятельном запуске `npm run server` +// читаем тот же .env сами, чтобы дев-режим показывал то же, что прод. +function envFromFile(file: string, key: string): string { try { - const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")); - return String(pkg.version ?? "").replace("+", "."); + const m = fs.readFileSync(file, "utf8").match(new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`, "m")); + return m ? m[1].trim() : ""; } catch { return ""; } -})(); +} +const ROOT_ENV = path.join(__dirname, "..", ".env"); +const BRAND = process.env.BRAND || envFromFile(ROOT_ENV, "BRAND") || "Forgetting"; +const CHANNEL = process.env.CHANNEL || envFromFile(ROOT_ENV, "CHANNEL") || "Beta"; +const VERSION = process.env.VERSION || envFromFile(ROOT_ENV, "VERSION") || ""; const INTERNAL_AUTH_KEY_FILE = process.env.INTERNAL_AUTH_KEY_FILE ?? path.join(__dirname, "internal_auth_private.key"); @@ -123,7 +126,7 @@ app.post("/logout", requireAuth, (req: Request, res: Response) => { // Единственный роут без авторизации: вордмарк нужен уже на экране логина, // до выдачи JWT. Наружу уходит только имя продукта и версия. app.get("/ui/brand", (_req: Request, res: Response) => { - res.json({ brand: BRAND, version: VERSION }); + res.json({ brand: BRAND, channel: CHANNEL, version: VERSION }); }); async function proxy(req: Request, res: Response) { diff --git a/awg-ui/src/App.tsx b/awg-ui/src/App.tsx index 46092cf..3a47af5 100644 --- a/awg-ui/src/App.tsx +++ b/awg-ui/src/App.tsx @@ -22,7 +22,7 @@ export default function App() { const [drawerOpen, setDrawerOpen] = useState(false); const [activeTab, setActiveTab] = useState(TABS[0].id); const [restarting, setRestarting] = useState(false); - const [brand, setBrand] = useState({ brand: BRAND_FALLBACK, version: '' }); + const [brand, setBrand] = useState({ brand: BRAND_FALLBACK, channel: '', version: '' }); const [theme, setTheme] = useState<'light' | 'dark'>(() => localStorage.getItem(THEME_KEY) === 'dark' ? 'dark' : 'light'); const touchStartX = useRef(0); @@ -117,6 +117,7 @@ export default function App() { axios.get('/ui/brand') .then(({ data }) => setBrand({ brand: data.brand || BRAND_FALLBACK, + channel: data.channel || '', version: data.version || '', })) .catch(() => { /* остаётся фолбэк */ }); @@ -230,7 +231,7 @@ export default function App() {
{brand.brand} - {brand.version ? `Alpha ${brand.version}` : 'Alpha'} + {[brand.channel, brand.version].filter(Boolean).join(' ')}

diff --git a/awg-ui/src/lib/shared.ts b/awg-ui/src/lib/shared.ts index 1808a76..d7b0252 100644 --- a/awg-ui/src/lib/shared.ts +++ b/awg-ui/src/lib/shared.ts @@ -31,11 +31,11 @@ export interface PageProps { token: string; showMsg: (text: string) => void; } -// Название продукта и версия приходят с сервера (GET /ui/brand, значения из -// BRAND в cli.env и package.json) — форк переименовывается в одном месте, в -// установщике. Эти константы — фолбэк на время загрузки и при недоступности. +// Имя панели, канал и версия приходят с сервера (GET /ui/brand; значения — из +// .env в корне проекта) — переименование и бамп версии живут в одном файле. +// Эта константа — фолбэк на время загрузки и при недоступности роута. export const BRAND_FALLBACK = 'Forgetting'; -export interface Brand { brand: string; version: string } +export interface Brand { brand: string; channel: string; version: string } export const TOKEN_KEY = 'awg_token'; export const THEME_KEY = 'awg_theme'; diff --git a/cli/package.json b/cli/package.json index 6257a0a..8083269 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "awg-cli", - "version": "0.1.4+2", + "version": "0.2.0", "private": true, "dependencies": { "tsx": "^4.7.0", diff --git a/cli/src/index.ts b/cli/src/index.ts index 0cd5162..659ff08 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -15,27 +15,30 @@ const LOGS = path.join(ROOT, "logs"); if (!existsSync(LOGS)) mkdirSync(LOGS, { recursive: true }); -const envFile = path.join(__dirname, "..", "cli.env"); -if (existsSync(envFile)) { - for (const line of readFileSync(envFile, "utf8").split("\n")) { +// Уже заданное окружение не перетираем (??=), поэтому порядок = приоритет: +// реальное окружение → cli.env (машинные настройки, пишет install.sh) → +// .env в корне проекта (имя панели и версия, едет в архиве релиза). +function loadEnvFile(file: string) { + if (!existsSync(file)) return; + for (const line of readFileSync(file, "utf8").split("\n")) { const t = line.trim(); if (!t || t.startsWith("#")) continue; const idx = t.indexOf("="); if (idx === -1) continue; - const k = t.slice(0, idx).trim(); - const v = t.slice(idx + 1).trim(); - process.env[k] ??= v; + process.env[t.slice(0, idx).trim()] ??= t.slice(idx + 1).trim(); } } -// Название продукта — из окружения (BRAND пишет install.sh в cli.env), версия — -// из package.json: при переименовании форка или бампе версии строки в коде -// править не нужно. В package.json версия в semver-форме (0.1.4+2), в релизах -// и баннерах — через точку. -const BRAND = process.env.BRAND || "Forgetting"; -const VERSION = (JSON.parse( - readFileSync(path.join(__dirname, "..", "package.json"), "utf8"), -).version as string).replace("+", "."); +const envFile = path.join(__dirname, "..", "cli.env"); // машинные настройки +loadEnvFile(envFile); +loadEnvFile(path.join(ROOT, ".env")); // имя панели и версия + +// Имя панели, канал и версия — из .env (или из cli.env, если оператор задал +// своё имя при установке). В коде только запасные значения на случай, когда +// .env не доехал. +const BRAND = process.env.BRAND || "Forgetting"; +const CHANNEL = process.env.CHANNEL || "Beta"; +const VERSION = process.env.VERSION || ""; const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); @@ -80,8 +83,10 @@ const SERVICES = { // Приватный ключ внутренней авторизации (awg-ui подписывает им токены к awg-ctrl). INTERNAL_AUTH_KEY_FILE: process.env.INTERNAL_AUTH_KEY_FILE ?? "/etc/amnezia/amneziawg/internal_auth_private.key", - // Бренд нужен панели для вордмарка (GET /ui/brand). + // Панель показывает их в вордмарке (GET /ui/brand). BRAND: BRAND, + CHANNEL: CHANNEL, + VERSION: VERSION, UI_USER: process.env.UI_USER ?? "admin", UI_PASS: process.env.UI_PASS ?? "", JWT_SECRET: process.env.JWT_SECRET ?? "", @@ -330,7 +335,7 @@ async function interactiveMenu() { const printMenu = () => { console.clear(); - console.log(`\n${bold(`${C.blue}── ${BRAND} Alpha ${VERSION} ──${C.reset}`)}\n`); + console.log(`\n${bold(`${C.blue}── ${[BRAND, CHANNEL, VERSION].filter(Boolean).join(" ")} ──${C.reset}`)}\n`); for (const name of ALL) { const running = isRunning(name); diff --git a/install.sh b/install.sh index 41d2b88..486d0ba 100644 --- a/install.sh +++ b/install.sh @@ -19,7 +19,11 @@ trap 'rc=$?; echo -e "\n ${RED}✗ НЕОЖИДАННАЯ ОШИБКА${NC} с [[ $EUID -ne 0 ]] && fail "Запусти от root: sudo bash install.sh" [[ -z "${BASH_VERSION:-}" ]] && fail "Нужен bash: bash install.sh" -VERSION="0.1.4.2" +# ⚠️ Дублирует .env из репозитория: установщик собирает URL релиза и печатает +# баннер ДО того, как архив с .env скачан, поэтому взять их оттуда не может. +# При бампе версии правь оба места. +VERSION="0.2.0" +CHANNEL="${CHANNEL:-Beta}" # Откуда брать архив и как называется продукт — задаётся окружением, в коде # ничего не зашито. У Gitea и GitHub путь релиза одинаковый @@ -27,7 +31,10 @@ VERSION="0.1.4.2" # REPO_BASE — основной источник (своё зеркало); # REPO_FALLBACK — запасной, если в основном релиза ещё нет; # ARCHIVE_URL — полный URL архива, перекрывает оба варианта; -# BRAND — имя продукта в баннерах, systemd-юните и панели. +# BRAND — имя продукта в баннерах, systemd-юните и панели; по +# умолчанию берётся из .env в архиве, здесь только запасное +# значение для баннера самого установщика. +BRAND_OVERRIDE="${BRAND:-}" BRAND="${BRAND:-Forgetting}" REPO_BASE="${REPO_BASE:-https://git.ma7neko.ru/maeneko/forgetting}" REPO_FALLBACK="${REPO_FALLBACK:-https://github.com/maeneko/forgetting}" @@ -54,7 +61,7 @@ MTU="1376" TSX="$PROJECT/cli/node_modules/.bin/tsx" CLI="$PROJECT/cli/src/index.ts" -echo -e "${BLD}${BRAND} Alpha ${VERSION}${NC}" +echo -e "${BLD}${BRAND} ${CHANNEL} ${VERSION}${NC}" echo # Запускается ДО вопросов и любого деструктива (rm -rf): на несовместимой @@ -885,7 +892,6 @@ INTERNAL_AUTH_KEY_FILE=${INTERNAL_AUTH_PRIV} INTERNAL_AUTH_PUB_FILE=${INTERNAL_AUTH_PUB} # ── ui (Ring 4) ─────────────────────────────────────────────────────────── -BRAND=${BRAND} UI_PORT=${UI_PORT} UI_USER=${UI_USER} UI_PASS=${UI_PASS} @@ -897,6 +903,11 @@ MA7_JWT_SECRET=${MA7_JWT_SECRET} MA7_LOGIN=${MA7_LOGIN} ENV +# Имя продукта попадает в cli.env, только если оператор задал его явно +# (BRAND=… перед запуском). Иначе оно берётся из .env, приехавшего в архиве, — +# чтобы источник правды оставался один. +[[ -n "$BRAND_OVERRIDE" ]] && echo "BRAND=${BRAND_OVERRIDE}" >> "$PROJECT/cli/cli.env" + chmod 600 "$PROJECT/cli/cli.env" ok "$PROJECT/cli/cli.env"