Files
Christopher Britton 8baf956211 Restore monitor service settings from Windows.old after a Windows upgrade (#1051)
* Restore monitor service settings from Windows.old after a Windows upgrade

* Clean up comments

* Review: SystemDrive root, reject empty backups

* Update release notes
2026-08-06 14:51:26 -04:00

181 lines
8.3 KiB
C#

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.IO;
using System.Threading;
using Newtonsoft.Json;
using NLog;
using ZitiDesktopEdge.DataStructures;
namespace ZitiUpdateService.Utils {
internal class Settings {
const int DefaultAlivenessChecks = 12;//12 checks == ~60s
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private FileSystemWatcher watcher;
[JsonIgnore]
private string Location { get; set; }
public bool AutomaticUpdatesDisabled { get; set; }
public string AutomaticUpdateURL { get; set; }
public int? AlivenessChecksBeforeAction { get; set; } // the number of times the aliveness check can fail before terminating the tunneler
public bool DeferInstallToRestart { get; set; } // stage the update and apply it on the next service/system restart
public int? MaintenanceWindowStart { get; set; } // hour (0-23) when installs may begin; null = no window
public int? MaintenanceWindowEnd { get; set; } // hour (0-23) when installs must stop; if equal to Start = no window; if < Start = window crosses midnight
public MaintenanceWindowFrequency MaintenanceWindowFrequency { get; set; } = MaintenanceWindowFrequency.Daily;
public int? MaintenanceWindowDayOfWeek { get; set; } // 0=Sunday .. 6=Saturday; honored when Frequency=Weekly OR (Monthly + MonthlyMode=ByWeekday)
public int? MaintenanceWindowDayOfMonth { get; set; } // 1-28, or 32 (LastDay) for "last day"; honored when Frequency=Monthly + MonthlyMode=ByDate
public MaintenanceWindowMonthlyMode MaintenanceWindowMonthlyMode { get; set; } = MaintenanceWindowMonthlyMode.ByDate;
public MaintenanceWindowMonthlyOrdinal? MaintenanceWindowMonthlyOrdinal { get; set; } // First..Last; honored when Frequency=Monthly + MonthlyMode=ByWeekday
public event System.EventHandler<ControllerEvent> OnConfigurationChange;
internal Settings(bool doInit) {
if (doInit) {
init();
}
AlivenessChecksBeforeAction = DefaultAlivenessChecks;
MaintenanceWindowStart = null; // null = any time (no window restriction)
MaintenanceWindowEnd = null;
MaintenanceWindowFrequency = MaintenanceWindowFrequency.Daily;
MaintenanceWindowDayOfWeek = null;
MaintenanceWindowDayOfMonth = null;
MaintenanceWindowMonthlyMode = MaintenanceWindowMonthlyMode.ByDate;
MaintenanceWindowMonthlyOrdinal = null;
}
public Settings() {
}
private void init() {
string folder = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), "NetFoundry", "ZitiUpdateService");
string file = "settings.json";
Location = Path.Combine(folder, file);
Directory.CreateDirectory(folder);
WindowsUpgradeBackup.RestoreMissingFiles(folder, WindowsUpgradeBackup.BackupFolders(folder));
watcher = new FileSystemWatcher(folder);
watcher.Filter = file;
watcher.NotifyFilter = NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.Size;
watcher.Changed += OnChanged;
watcher.Deleted += OnDeleted;
watcher.Renamed += OnRenamed;
watcher.Error += OnError;
watcher.EnableRaisingEvents = true;
}
private static JsonSerializer serializer = new JsonSerializer() { Formatting = Formatting.Indented };
internal void Load() {
try {
string json = File.ReadAllText(Location);
var jsonReaderEvt = new JsonTextReader(new StringReader(json));
Settings s = serializer.Deserialize<Settings>(jsonReaderEvt);
if (s != null) {
Update(s);
} else {
Logger.Debug("settings file was null? file doesn't exist or file was garbage?");
}
} catch (Exception ex) {
// probably means the file doesn't exist or hasn't finished being written yet
throw ex;
}
}
internal void Write() {
lock (this) {
this.watcher.Changed -= OnChanged;
try {
using (StreamWriter file = File.CreateText(Location)) {
serializer.Serialize(file, this);
file.Flush();
file.Close();
}
this.OnConfigurationChange?.Invoke(null, null);
} catch {
// do nothing
}
this.watcher.Changed += OnChanged;
}
}
private static void OnError(object sender, ErrorEventArgs e) {
}
private void OnRenamed(object sender, RenamedEventArgs e) {
Logger.Info("Settings file renamed. Resetting to defaults...");
this.Update(new Settings());
}
private void OnDeleted(object sender, FileSystemEventArgs e) {
Logger.Info("Settings file deleted. Resetting to defaults...");
this.Update(new Settings());
}
private void OnCreated(object sender, FileSystemEventArgs e) {
}
private void OnChanged(object sender, FileSystemEventArgs e) {
try {
this.Load();
this.OnConfigurationChange?.Invoke(null, null);
} catch (IOException ioe) {
int isLocked = (int)(System.Runtime.InteropServices.Marshal.GetHRForException(ioe) & 0xFFFF);
if (isLocked == 32) {
/*
see https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
ERROR_SHARING_VIOLATION
32 (0x20)
The process cannot access the file because it is being used by another process.
*/
Thread.Sleep(500); //wait 500ms for the file to finish being written
try {
Load();
this.OnConfigurationChange?.Invoke(null, null);
} catch (Exception ex2) {
Logger.Debug("unexpected error loading settings twice. not trying again. {0}", ex2);
}
} else {
Logger.Debug("unexpected error loading settings. file was null? file doesn't exist or file was garbage? {0}", ioe);
}
} catch (Exception ex) {
Logger.Debug("unexpected error loading settings. file was null? file doesn't exist or file was garbage? {0}", ex);
}
}
private void Update(Settings source) {
this.AutomaticUpdatesDisabled = source.AutomaticUpdatesDisabled;
this.AutomaticUpdateURL = source.AutomaticUpdateURL;
this.DeferInstallToRestart = source.DeferInstallToRestart;
this.MaintenanceWindowStart = source.MaintenanceWindowStart;
this.MaintenanceWindowEnd = source.MaintenanceWindowEnd;
this.MaintenanceWindowFrequency = source.MaintenanceWindowFrequency;
this.MaintenanceWindowDayOfWeek = source.MaintenanceWindowDayOfWeek;
this.MaintenanceWindowDayOfMonth = source.MaintenanceWindowDayOfMonth;
this.MaintenanceWindowMonthlyMode = source.MaintenanceWindowMonthlyMode;
this.MaintenanceWindowMonthlyOrdinal = source.MaintenanceWindowMonthlyOrdinal;
if (source.AlivenessChecksBeforeAction != null) {
AlivenessChecksBeforeAction = source.AlivenessChecksBeforeAction;
} else {
AlivenessChecksBeforeAction = DefaultAlivenessChecks;
}
}
}
}