platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
transportSingBoxLegacyMigrateConfigKey = "singbox_legacy_unit_migrate"
|
||||
transportSingBoxLegacyMigrateDryRunConfigKey = "singbox_legacy_unit_migrate_dry_run"
|
||||
)
|
||||
|
||||
func transportSystemdMaybeMigrateLegacySingBoxUnits(client TransportClient, units []string) ([]string, []string) {
|
||||
out := make([]string, 0, 4)
|
||||
errOut := make([]string, 0, 4)
|
||||
|
||||
if !transportSystemdLegacySingBoxMigrationEnabled(client) {
|
||||
return out, errOut
|
||||
}
|
||||
target := transportSystemdResolveSingBoxTemplateTargetUnit(client, units)
|
||||
if target == "" {
|
||||
return out, errOut
|
||||
}
|
||||
|
||||
dryRun := transportSystemdLegacySingBoxMigrationDryRun(client)
|
||||
legacyUnits := transportSystemdLegacySingBoxUnitCandidates(client, target)
|
||||
if len(legacyUnits) == 0 {
|
||||
return out, errOut
|
||||
}
|
||||
|
||||
migrated := make([]string, 0, len(legacyUnits))
|
||||
for _, legacyUnit := range legacyUnits {
|
||||
if strings.EqualFold(strings.TrimSpace(legacyUnit), target) {
|
||||
continue
|
||||
}
|
||||
path := transportSystemdUnitPath(legacyUnit)
|
||||
owned, err := transportSystemdUnitOwnedByClient(path, client.ID)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
errOut = append(errOut, fmt.Sprintf("legacy-migrate ownership check failed (%s): %v", legacyUnit, err))
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate warning: client=%s unit=%s ownership-check err=%v", client.ID, legacyUnit, err),
|
||||
30*time.Second,
|
||||
)
|
||||
continue
|
||||
}
|
||||
if !owned {
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate skip: client=%s unit=%s reason=ownership-mismatch", client.ID, legacyUnit),
|
||||
30*time.Second,
|
||||
)
|
||||
continue
|
||||
}
|
||||
if dryRun {
|
||||
msg := fmt.Sprintf("legacy-migrate dry-run: %s -> %s", legacyUnit, target)
|
||||
out = append(out, msg)
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate dry-run: client=%s from=%s to=%s", client.ID, legacyUnit, target),
|
||||
20*time.Second,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
transportSystemdRunLegacyMigrateCommand("stop", legacyUnit, &out, &errOut)
|
||||
transportSystemdRunLegacyMigrateCommand("disable", legacyUnit, &out, &errOut)
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
errOut = append(errOut, fmt.Sprintf("legacy-migrate remove failed (%s): %v", legacyUnit, err))
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate warning: client=%s unit=%s remove err=%v", client.ID, legacyUnit, err),
|
||||
30*time.Second,
|
||||
)
|
||||
continue
|
||||
}
|
||||
out = append(out, fmt.Sprintf("legacy-migrate: %s -> %s", legacyUnit, target))
|
||||
migrated = append(migrated, legacyUnit)
|
||||
appendTraceLine(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrated: client=%s from=%s to=%s", client.ID, legacyUnit, target),
|
||||
)
|
||||
}
|
||||
|
||||
if dryRun || len(migrated) == 0 {
|
||||
return out, errOut
|
||||
}
|
||||
|
||||
stdout, stderr, code, err := transportRunCommand(transportBackendActionTimeout, "systemctl", "daemon-reload")
|
||||
if s := strings.TrimSpace(stdout); s != "" {
|
||||
out = append(out, "legacy-migrate daemon-reload: "+s)
|
||||
}
|
||||
if s := strings.TrimSpace(stderr); s != "" {
|
||||
errOut = append(errOut, "legacy-migrate daemon-reload: "+s)
|
||||
}
|
||||
if err != nil || code != 0 {
|
||||
errOut = append(errOut, fmt.Sprintf("legacy-migrate daemon-reload failed (exit=%d)", code))
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate warning: client=%s daemon-reload code=%d err=%v", client.ID, code, err),
|
||||
20*time.Second,
|
||||
)
|
||||
}
|
||||
|
||||
for _, legacyUnit := range migrated {
|
||||
stdout, stderr, code, err = transportRunCommand(transportBackendActionTimeout, "systemctl", "reset-failed", legacyUnit)
|
||||
if s := strings.TrimSpace(stdout); s != "" {
|
||||
out = append(out, fmt.Sprintf("legacy-migrate reset-failed %s: %s", legacyUnit, s))
|
||||
}
|
||||
if s := strings.TrimSpace(stderr); s != "" {
|
||||
errOut = append(errOut, fmt.Sprintf("legacy-migrate reset-failed %s: %s", legacyUnit, s))
|
||||
}
|
||||
if err != nil || code != 0 {
|
||||
errOut = append(errOut, fmt.Sprintf("legacy-migrate reset-failed %s failed (exit=%d)", legacyUnit, code))
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate warning: client=%s reset-failed unit=%s code=%d err=%v", client.ID, legacyUnit, code, err),
|
||||
20*time.Second,
|
||||
)
|
||||
}
|
||||
}
|
||||
return out, errOut
|
||||
}
|
||||
|
||||
func transportSystemdRunLegacyMigrateCommand(action, unit string, out, errOut *[]string) {
|
||||
stdout, stderr, code, err := transportRunCommand(transportBackendActionTimeout, "systemctl", action, unit)
|
||||
if s := strings.TrimSpace(stdout); s != "" {
|
||||
*out = append(*out, fmt.Sprintf("legacy-migrate %s %s: %s", action, unit, s))
|
||||
}
|
||||
if s := strings.TrimSpace(stderr); s != "" {
|
||||
*errOut = append(*errOut, fmt.Sprintf("legacy-migrate %s %s: %s", action, unit, s))
|
||||
}
|
||||
if err == nil && code == 0 {
|
||||
return
|
||||
}
|
||||
if transportSystemdUnitMissingError(stdout, stderr, code, err) {
|
||||
return
|
||||
}
|
||||
*errOut = append(*errOut, fmt.Sprintf("legacy-migrate %s %s failed (exit=%d)", action, unit, code))
|
||||
appendTraceLineRateLimited(
|
||||
"transport",
|
||||
fmt.Sprintf("legacy unit migrate warning: action=%s unit=%s code=%d err=%v", action, unit, code, err),
|
||||
20*time.Second,
|
||||
)
|
||||
}
|
||||
|
||||
func transportSystemdLegacySingBoxMigrationEnabled(client TransportClient) bool {
|
||||
if client.Kind != TransportClientSingBox {
|
||||
return false
|
||||
}
|
||||
if transportConfigHasKey(client.Config, transportSingBoxLegacyMigrateConfigKey) {
|
||||
return transportConfigBool(client.Config, transportSingBoxLegacyMigrateConfigKey)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func transportSystemdLegacySingBoxMigrationDryRun(client TransportClient) bool {
|
||||
return transportConfigBool(client.Config, transportSingBoxLegacyMigrateDryRunConfigKey)
|
||||
}
|
||||
|
||||
func transportSystemdResolveSingBoxTemplateTargetUnit(client TransportClient, units []string) string {
|
||||
for _, unit := range units {
|
||||
u := strings.TrimSpace(unit)
|
||||
if transportSystemdSingBoxUsesTemplateInstance(client, u) {
|
||||
return u
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func transportSystemdLegacySingBoxUnitCandidates(client TransportClient, targetUnit string) []string {
|
||||
candidates := make([]string, 0, 4)
|
||||
addCandidate := func(unit string) {
|
||||
u := strings.TrimSpace(unit)
|
||||
if !validSystemdUnitName(u) {
|
||||
return
|
||||
}
|
||||
for _, existing := range candidates {
|
||||
if strings.EqualFold(existing, u) {
|
||||
return
|
||||
}
|
||||
}
|
||||
candidates = append(candidates, u)
|
||||
}
|
||||
|
||||
instanceRaw := transportSystemdInstanceIDFromUnit(targetUnit)
|
||||
if instanceRaw != "" {
|
||||
addCandidate("singbox-" + strings.ToLower(instanceRaw) + ".service")
|
||||
if normalized := sanitizeID(instanceRaw); normalized != "" {
|
||||
addCandidate("singbox-" + normalized + ".service")
|
||||
}
|
||||
}
|
||||
if id := sanitizeID(client.ID); id != "" {
|
||||
addCandidate("singbox-" + id + ".service")
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func transportSystemdInstanceIDFromUnit(unit string) string {
|
||||
u := strings.TrimSpace(unit)
|
||||
if u == "" || !strings.HasSuffix(strings.ToLower(u), ".service") {
|
||||
return ""
|
||||
}
|
||||
at := strings.IndexByte(u, '@')
|
||||
if at <= 0 || at+1 >= len(u) {
|
||||
return ""
|
||||
}
|
||||
instance := strings.TrimSpace(strings.TrimSuffix(u[at+1:], ".service"))
|
||||
if instance == "" {
|
||||
return ""
|
||||
}
|
||||
return instance
|
||||
}
|
||||
Reference in New Issue
Block a user