5 Commits
v0.4.0 ... main

Author SHA1 Message Date
857cb55159 feat: "支持更新"
All checks were successful
release / release (push) Successful in 17s
2026-05-27 16:21:08 +08:00
0c66722989 feat: "支持arm架构"
All checks were successful
release / release (push) Successful in 17s
2026-05-27 16:19:36 +08:00
d21dcaa303 fix
All checks were successful
release / release (push) Successful in 31s
2026-05-24 15:07:00 +08:00
02c7fa68aa fix
All checks were successful
release / release (push) Successful in 18s
2026-05-24 14:52:12 +08:00
c0bdb4fb5e fix
All checks were successful
release / release (push) Successful in 31s
2026-05-24 14:37:11 +08:00
5 changed files with 116 additions and 16 deletions

View File

@@ -48,11 +48,36 @@ jobs:
fi
- name: Publish Gitea Release
uses: https://gitea.com/actions/release-action@main
with:
api_key: ${{ secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN }}
tag: ${{ steps.tag.outputs.name }}
title: ${{ steps.tag.outputs.name }}
files: |-
release/app.mjs
release/install.sh
env:
TOKEN: ${{ secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.name }}
run: |
set -eu
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases"
parse_id='let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(d).id||""))}catch(e){}})'
rid=$(curl -sS -X POST "$API" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":false,\"prerelease\":false}" \
| node -e "$parse_id")
if [ -z "$rid" ]; then
echo "release may already exist, fetching by tag $TAG"
rid=$(curl -sS "$API/tags/$TAG" -H "Authorization: token $TOKEN" | node -e "$parse_id")
fi
if [ -z "$rid" ]; then
echo "failed to create or find release for $TAG" >&2
exit 1
fi
echo "release id=$rid"
for f in release/app.mjs release/install.sh; do
name=$(basename "$f")
echo "uploading $name"
curl -sS -X POST "$API/$rid/assets?name=$name" \
-H "Authorization: token $TOKEN" \
-F "attachment=@$f" \
-o /dev/null -w " -> HTTP %{http_code}\n"
done

View File

@@ -3,9 +3,19 @@
const fs = require("node:fs");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const { pathToFileURL } = require("node:url");
const bundle = path.join(__dirname, "..", "dist", "app.mjs");
const packageRoot = path.join(__dirname, "..");
const bundle = path.join(packageRoot, "dist", "app.mjs");
// `server-config update` self-updates the checkout: pull, reinstall deps, rebuild.
// It runs outside the TUI because it must not depend on (or fight with) the bundle
// it is about to replace.
if (process.argv[2] === "update") {
runUpdate();
return;
}
if (!fs.existsSync(bundle)) {
process.stderr.write(
@@ -18,3 +28,34 @@ import(pathToFileURL(bundle).href).catch((error) => {
process.stderr.write(`server-config: ${error?.stack || error}\n`);
process.exit(1);
});
function runUpdate() {
if (!fs.existsSync(path.join(packageRoot, ".git"))) {
process.stderr.write(
"server-config: cannot update — package root is not a git checkout.\n",
);
process.exit(1);
}
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
const steps = [
["git", ["pull", "--ff-only"]],
[npm, ["install"]],
[npm, ["run", "build"]],
];
for (const [cmd, args] of steps) {
process.stdout.write(`\n$ ${cmd} ${args.join(" ")}\n`);
const result = spawnSync(cmd, args, { cwd: packageRoot, stdio: "inherit" });
if (result.error) {
process.stderr.write(`server-config: failed to run ${cmd}: ${result.error.message}\n`);
process.exit(1);
}
if (result.status !== 0) {
process.stderr.write(`server-config: \`${cmd} ${args.join(" ")}\` exited with code ${result.status}\n`);
process.exit(result.status || 1);
}
}
process.stdout.write("\nserver-config: update complete.\n");
}

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# server-config-cli installer
#
# Usage:
# curl -fsSL <REPO>/releases/latest/download/install.sh | bash
# Usage (always latest, no version needed):
# curl -fsSL https://gitea1.deeppolicy.cn:3002/hanruo/server-config-cli/raw/branch/main/scripts/install.sh | bash
#
# Environment overrides:
# VERSION release tag to install (default: latest)
@@ -47,7 +47,22 @@ NODE_URL="${NODE_MIRROR}/${NODE_VERSION}/${NODE_PKG}"
NODE_DIR="${INSTALL_DIR}/node-${NODE_VERSION}-${NODE_OS}-${NODE_ARCH}"
if [ "$VERSION" = "latest" ]; then
BUNDLE_URL="${REPO_BASE}/releases/latest/download/app.mjs"
# Resolve the newest release tag via the API (works on all Gitea versions,
# unlike the GitHub-style /releases/latest/download shortcut).
proto="${REPO_BASE%%://*}"
rest="${REPO_BASE#*://}"
host="${rest%%/*}"
repo_path="${rest#*/}"
API_BASE="${proto}://${host}/api/v1/repos/${repo_path}"
log "Resolving latest release tag"
TAG=$(curl -fsSL "${API_BASE}/releases/latest" \
| grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' \
| head -1 \
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/')
[ -n "$TAG" ] || die "could not resolve latest release tag from ${API_BASE}/releases/latest"
log "Latest release is ${TAG}"
BUNDLE_URL="${REPO_BASE}/releases/download/${TAG}/app.mjs"
else
BUNDLE_URL="${REPO_BASE}/releases/download/${VERSION}/app.mjs"
fi

View File

@@ -1,10 +1,26 @@
"use strict";
const os = require("node:os");
const path = require("node:path");
const { randomBytes } = require("node:crypto");
const DEFAULT_FRP_VERSION = "0.58.1";
const DEFAULT_FRP_ARCH = "amd64";
// Map Node's os.arch() to the arch token frp uses in its release asset names
// (e.g. frp_0.58.1_linux_arm64.tar.gz). Falls back to amd64 for the common case.
function detectFrpArch() {
switch (os.arch()) {
case "x64": return "amd64";
case "arm64": return "arm64";
case "arm": return "arm";
case "ia32": return "386";
case "mips": return "mips";
case "mipsel": return "mipsle";
default: return "amd64";
}
}
const DEFAULT_FRP_ARCH = detectFrpArch();
const DEFAULT_FRP_SERVER_ADDR = "81.70.134.9";
const DEFAULT_FRP_SERVER_PORT = 15443;
const DEFAULT_SERVICE_NAME = "frpc";
@@ -212,6 +228,7 @@ function buildGlobals({ serverAddr, serverPort, token, tlsEnable, tcpMux, logFil
module.exports = {
DEFAULT_FRP_VERSION,
DEFAULT_FRP_ARCH,
detectFrpArch,
DEFAULT_FRP_SERVER_ADDR,
DEFAULT_FRP_SERVER_PORT,
DEFAULT_SERVICE_NAME,

View File

@@ -48,7 +48,6 @@ function zshInstallPlan() {
const steps = [
run("apt update", "sudo", aptArgs("update")),
run("apt upgrade", "sudo", aptArgs("upgrade", "-y")),
run("install zsh & friends", "sudo", aptArgs("install", "zsh", "git", "curl", "wget", "-y")),
run("git credential helper = store", "git", ["config", "--global", "credential.helper", "store"]),
run("change shell to zsh", "sudo", ["chsh", "-s", "/bin/zsh", os.userInfo().username]),
@@ -80,8 +79,11 @@ function gitPluginStep(name, repo, destination) {
function condaInstallPlan(options = {}) {
const installDir = options.installDir || path.join(os.homedir(), "miniconda3");
const installer = "/tmp/Miniconda3-latest-Linux-x86_64.sh";
const url = "https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh";
// Miniconda asset naming uses uname-style arch tokens (x86_64 / aarch64).
const condaArch = os.arch() === "arm64" ? "aarch64" : "x86_64";
const installerName = `Miniconda3-latest-Linux-${condaArch}.sh`;
const installer = `/tmp/${installerName}`;
const url = `https://repo.anaconda.com/miniconda/${installerName}`;
const condaBin = path.join(installDir, "bin", "conda");
return {