package mock import ( "context" "errors" "fmt" "time" "tera-clone/services/tera-api/internal/alarmdelivery" "tera-clone/services/tera-api/internal/directstream" "tera-clone/services/tera-api/internal/domain" "tera-clone/services/tera-api/internal/logservice" "tera-clone/services/tera-api/internal/networkconfig" ) // FactoryReset is shared by the authenticated web API and the physical SW2 // monitor. Never expose narrower UI-only deletion with a different scope. func (s *Store) FactoryReset(ctx context.Context) (domain.Operation, error) { s.resetGate.Lock() defer s.resetGate.Unlock() if err := ctx.Err(); err != nil { return domain.Operation{}, err } if s.logJournal != nil { if _, ok := s.logJournal.(logservice.FactoryResetter); !ok { return domain.Operation{}, errors.New("log service does not support factory reset") } } if s.supportBundleService != nil { if _, ok := s.supportBundleService.(interface{ Reset() error }); !ok { return domain.Operation{}, errors.New("support bundle service does not support factory reset") } } if _, ok := s.networkController.(networkconfig.FactoryResetter); !ok { return domain.Operation{}, errors.New("network service does not support factory reset") } if _, ok := s.alarmDeliveryController.(alarmdelivery.FactoryResetter); !ok { return domain.Operation{}, errors.New("alarm service does not support factory reset") } users, err := factoryAuthUsers(s.passwordKDF) if err != nil { return domain.Operation{}, err } // Stop diagnostic producers before removing their artifacts. Reboot starts // them again; a failed reset remains visible and may be retried. s.Close() s.mu.Lock() defer s.mu.Unlock() if s.factoryResetCleanup != nil { if err := s.factoryResetCleanup(); err != nil { return domain.Operation{}, err } } if err := s.alarmDeliveryController.(alarmdelivery.FactoryResetter).FactoryReset(ctx); err != nil { return domain.Operation{}, err } if s.supportBundleService != nil { if err := s.supportBundleService.(interface{ Reset() error }).Reset(); err != nil { return domain.Operation{}, err } } if s.logJournal != nil { if err := s.logJournal.(logservice.FactoryResetter).FactoryReset(); err != nil { return domain.Operation{}, err } } if err := s.secrets.Reset(); err != nil { return domain.Operation{}, err } metadata, err := s.tlsCertificates.Reset() if err != nil { return domain.Operation{}, err } s.resetConfigLocked() if err := s.applyJournalRetentionLocked(s.runningRetention); err != nil { return domain.Operation{}, err } if err := s.networkController.(networkconfig.FactoryResetter).FactoryReset(ctx, networkConfigForController(s.runningNetwork)); err != nil { return domain.Operation{}, err } snmpConfig, err := s.snmpAgentRuntimeConfigLocked("running", s.runningSNMPAgent) if err != nil { return domain.Operation{}, err } if err := s.snmpAgentController.Apply(ctx, snmpConfig); err != nil { return domain.Operation{}, err } s.runningTLS = tlsCertificateConfigFromMetadata(metadata) s.persistentTLS = s.runningTLS s.authUsers = users s.authGroupOverrides = make(map[string]authGroupRecord) s.authTokens = make(map[string]authTokenRecord) s.sessions = make(map[string]sessionRecord) s.loginFailures = make(map[string]loginFailureRecord) s.addressFilters = domain.AddressFilterSettings{Mode: "blacklist"} s.authTokenSeq, s.sessionSeq = 0, 0 s.directSessions = make(map[string]directSessionRecord) s.directTickets = directstream.NewTicketStore(directstream.TicketStoreOptions{Clock: func() time.Time { return s.clock() }}) s.operations = make(map[string]domain.Operation) s.auditEvents = nil s.systemLogEvents = nil s.portLogEvents = nil s.linkLogEvents = nil s.allowSampleLogs = false s.auditSeq, s.operationSeq = 0, 0 s.journalLast = make(map[string]string) s.journalSequence = make(map[string]uint64) s.journalLocalSequences = make(map[string]map[uint64]struct{}) s.networkRollback = nil s.networkTransactionID = "" for _, persist := range []func() error{s.persistConfigStoreLocked, s.persistAuthStoreLocked, s.persistSessionStoreLocked, s.persistLoginFailureStoreLocked, s.persistOperationStoreLocked, s.persistAuditLogLocked, s.persistSystemLogStoreLocked, s.persistPortLogStoreLocked, s.persistLinkLogStoreLocked} { if err := persist(); err != nil { return domain.Operation{}, fmt.Errorf("persist factory reset: %w", err) } } s.resetComplete = true if err := s.systemController.Reboot(ctx); err != nil { return domain.Operation{}, fmt.Errorf("data reset completed but reboot failed: %w", err) } return s.newOperationLocked(ctx, "system.factory_reset", "system", "all device data erased; only terauser restored; reboot scheduled") } // BeginMutation drains already accepted HTTP writes before a physical or web // reset and rejects subsequent writes until reboot, preventing resurrection. func (s *Store) BeginMutation() (func(), bool) { s.resetGate.RLock() if s.resetComplete { s.resetGate.RUnlock() return func() {}, false } return s.resetGate.RUnlock, true }