mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 21:13:00 +08:00
152 lines
5.3 KiB
Go
152 lines
5.3 KiB
Go
package config
|
|
|
|
import (
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
"github.com/matthewlu070111/smart-srun/core/internal/protocol/srun"
|
|
)
|
|
|
|
// Built-in login shape values used when neither the account nor
|
|
// login_defaults supplies one. They are not configuration: no UI has ever
|
|
// exposed them globally, and they are the SRun defaults the baseline sent.
|
|
const (
|
|
DefaultInfoPrefix = "SRBX1"
|
|
DefaultLoginOS = "Windows 10"
|
|
DefaultLoginName = "Windows"
|
|
DefaultDoubleStack = false
|
|
|
|
DefaultWiredIface = "wan"
|
|
DefaultSchool = "default"
|
|
)
|
|
|
|
// Defaults returns a fresh configuration.
|
|
//
|
|
// This is the single source of default values for the whole program. The CLI,
|
|
// the RPC schema and the LuCI form all read it; nothing keeps a second copy.
|
|
// The 1.x code kept display-time fallbacks separate from the real defaults,
|
|
// which made `config show` report settings the scheduler was not using
|
|
// (decision D07).
|
|
func Defaults() domain.Config {
|
|
return domain.Config{
|
|
SchemaVersion: domain.ConfigSchemaVersion,
|
|
Revision: 0,
|
|
Enabled: false,
|
|
MultiWANEnabled: false,
|
|
School: DefaultSchool,
|
|
STAIface: "",
|
|
LoginDefaults: domain.LoginDefaults{
|
|
N: "200",
|
|
Type: "1",
|
|
Enc: "srun_bx1",
|
|
},
|
|
Selection: domain.Selection{},
|
|
Quiet: domain.QuietConfig{
|
|
Enabled: true,
|
|
Start: mustClock(0, 0),
|
|
End: mustClock(6, 0),
|
|
ForceLogout: true,
|
|
},
|
|
Retry: domain.RetryConfig{
|
|
Enabled: true,
|
|
MaxRetries: 4,
|
|
InitialSeconds: 10,
|
|
MaxSeconds: 60,
|
|
},
|
|
Checks: domain.ChecksConfig{
|
|
IntervalSeconds: 60,
|
|
Mode: domain.CheckInternet,
|
|
SwitchTimeoutSeconds: 30,
|
|
TerminalAttempts: 5,
|
|
TerminalIntervalSeconds: 2,
|
|
},
|
|
Failover: domain.FailoverConfig{
|
|
Enabled: true,
|
|
HotspotFailbackEnabled: true,
|
|
},
|
|
Log: domain.LogConfig{Level: domain.LogInfo, FileOmitInfo: true},
|
|
PresetUpdates: domain.PresetUpdateConfig{Enabled: true, Time: mustClock(9, 0)},
|
|
|
|
CampusAccounts: []domain.CampusAccount{},
|
|
HotspotProfiles: []domain.HotspotProfile{},
|
|
SchoolExtra: map[string]any{},
|
|
}
|
|
}
|
|
|
|
// mustClock is only ever called with the constants above.
|
|
func mustClock(hour, minute int) domain.ClockTime {
|
|
value, err := domain.NewClockTime(hour, minute)
|
|
if err != nil {
|
|
panic("config: invalid built-in default clock time: " + err.Error())
|
|
}
|
|
return value
|
|
}
|
|
|
|
// EffectiveLogin resolves one account's protocol parameters.
|
|
//
|
|
// Order is account, then the global login_defaults, then the built-in value.
|
|
// Resolution reads only this account: it never inherits another account's
|
|
// resolved parameters, which is what made multi-WAN accounts cross-contaminate
|
|
// in the 1.x runtime.
|
|
func EffectiveLogin(cfg domain.Config, account domain.CampusAccount) EffectiveLoginShape {
|
|
shape := EffectiveLoginShape{
|
|
N: firstNonEmpty(account.Login.N, cfg.LoginDefaults.N, "200"),
|
|
Type: firstNonEmpty(account.Login.Type, cfg.LoginDefaults.Type, "1"),
|
|
Enc: firstNonEmpty(account.Login.Enc, cfg.LoginDefaults.Enc, "srun_bx1"),
|
|
InfoPrefix: firstNonEmpty(account.Login.InfoPrefix, DefaultInfoPrefix),
|
|
OS: firstNonEmpty(account.Login.OS, DefaultLoginOS),
|
|
Name: firstNonEmpty(account.Login.Name, DefaultLoginName),
|
|
// Resolved to the concrete table rather than left empty, so the value
|
|
// the protocol layer will use is the value diagnostics print. There is
|
|
// no global login_defaults.alphabet: the table belongs to a gateway,
|
|
// and an account already carries which gateway it talks to.
|
|
Alphabet: firstNonEmpty(account.Login.Alphabet, srun.DefaultAlphabetTable),
|
|
DoubleStack: DefaultDoubleStack,
|
|
}
|
|
if account.Login.DoubleStack != nil {
|
|
shape.DoubleStack = *account.Login.DoubleStack
|
|
}
|
|
return shape
|
|
}
|
|
|
|
// EffectiveLoginShape is the resolved, complete protocol parameter set. Unlike
|
|
// LoginShape it has no optional fields: every value is decided.
|
|
type EffectiveLoginShape struct {
|
|
N string
|
|
Type string
|
|
Enc string
|
|
InfoPrefix string
|
|
DoubleStack bool
|
|
OS string
|
|
Name string
|
|
// Alphabet is always the concrete 64-byte table, never empty. It is not a
|
|
// secret -- it is a public property of the gateway -- so it appears in
|
|
// diagnostic output alongside the other resolved parameters.
|
|
Alphabet string
|
|
}
|
|
|
|
// EffectiveUsername is the username actually sent to the gateway.
|
|
//
|
|
// The suffix field is authoritative. An empty suffix means the plain account,
|
|
// which is a real configuration and not "unset": guessing a carrier suffix here
|
|
// would authenticate as a different identity.
|
|
//
|
|
// The unverified sentinel is treated as no suffix. Validation already refuses
|
|
// to save it, so reaching here with one means an account bypassed validation --
|
|
// and "user@??" is a wrong identity, which is worse than a plain login that
|
|
// fails cleanly. Normalization deliberately does *not* do this rewrite: the
|
|
// stored value keeps the sentinel so the user is still told to fix it.
|
|
func EffectiveUsername(account domain.CampusAccount) string {
|
|
if account.OperatorSuffix == "" || account.OperatorSuffix == UnverifiedOperatorSuffix {
|
|
return account.UserID
|
|
}
|
|
return account.UserID + "@" + account.OperatorSuffix
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|