feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+92
View File
@@ -0,0 +1,92 @@
package config
import (
"bytes"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"strings"
)
const defaultTencentSMSConfigFile = "/run/secrets/voice-runtime/tencent-sms.json"
type tencentSMSFile struct {
TemplateParams map[string][]string `json:"template_params"`
Enabled *bool `json:"enabled"`
SecretID string `json:"secret_id"`
SecretKey string `json:"secret_key"`
SDKAppID string `json:"sdk_app_id"`
SignName string `json:"sign_name"`
Templates map[string]string `json:"templates"`
}
// Only API and worker consume SMS configuration. In particular, a mounted
// configuration must not enable onboarding in callbacks or other services.
func (c *OnboardingConfig) loadTencentSMSFile(serviceName string) error {
if serviceName != "api" && serviceName != "worker" {
return nil
}
path := strings.TrimSpace(os.Getenv("TENCENT_SMS_CONFIG_FILE"))
explicit := path != ""
if !explicit {
path = defaultTencentSMSConfigFile
// The conventional mount exists only on Unix hosts/containers. Windows
// development can still opt in with an explicit absolute file path.
if !filepath.IsAbs(path) {
return nil
}
}
return c.readTencentSMSFile(path, explicit)
}
func (c *OnboardingConfig) readTencentSMSFile(path string, required bool) error {
if !filepath.IsAbs(path) {
return errors.New("TENCENT_SMS_CONFIG_FILE must be an absolute path")
}
file, err := os.Open(filepath.Clean(path))
if errors.Is(err, os.ErrNotExist) && !required {
return nil
}
if err != nil {
return errors.New("Tencent SMS configuration file is unavailable")
}
defer file.Close()
info, err := file.Stat()
if err != nil || !info.Mode().IsRegular() || info.Size() > 64*1024 {
return errors.New("Tencent SMS configuration must be a regular file of at most 64 KiB")
}
raw, err := io.ReadAll(io.LimitReader(file, 64*1024+1))
if err != nil || len(raw) > 64*1024 {
return errors.New("Tencent SMS configuration file could not be read")
}
// Accept UTF-8 BOM from Windows editors. Never include raw JSON or parser
// errors in startup errors: unknown fields may accidentally contain secrets.
raw = bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf})
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
var in tencentSMSFile
if err := decoder.Decode(&in); err != nil || in.Enabled == nil {
return errors.New("Tencent SMS configuration must match the JSON template and include enabled")
}
if err := decoder.Decode(new(any)); err != io.EOF {
return errors.New("Tencent SMS configuration must contain exactly one JSON object")
}
for key, value := range in.Templates {
if key != "login" && key != "register" && key != "change_password" && key != "change_phone" {
return errors.New("Tencent SMS template names must be login, register, change_password or change_phone")
}
in.Templates[key] = strings.TrimSpace(value)
}
c.SMSProvider = "tencent"
c.SMSLoginEnabled = *in.Enabled
c.TencentSMSSecretID = strings.TrimSpace(in.SecretID)
c.TencentSMSSecretKey = strings.TrimSpace(in.SecretKey)
c.TencentSMSAppID = strings.TrimSpace(in.SDKAppID)
c.TencentSMSSignName = strings.TrimSpace(in.SignName)
c.TencentSMSTemplateID = ""
c.TencentSMSTemplates = in.Templates
c.TencentSMSTemplateParams = in.TemplateParams
return nil
}