From 39d6f4a2051e972f62bd8d2278ff1053ee5791e7 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 12:12:25 -0500 Subject: [PATCH 01/10] ziti service cleanup commented out code and minor bug fixes --- service/cziti/ctun.go | 23 +---------------------- service/cziti/dns-proxy.go | 5 ++++- service/cziti/dns.go | 14 ++++++-------- service/ziti-tunnel/dto/dtos.go | 1 + service/ziti-tunnel/main.go | 9 --------- service/ziti-tunnel/service/ipc.go | 25 +++++++++++++++---------- 6 files changed, 27 insertions(+), 50 deletions(-) diff --git a/service/cziti/ctun.go b/service/cziti/ctun.go index ef0f7972..860fe8d7 100644 --- a/service/cziti/ctun.go +++ b/service/cziti/ctun.go @@ -97,7 +97,7 @@ func HookupTun(dev tun.Device/*, dns []net.IP*/) error { writeQ: make(chan []byte, 64), readQ: make(chan []byte, 64), } - //devMap[name] = t + theTun = t opts := (*C.tunneler_sdk_options)(C.calloc(1, C.sizeof_tunneler_sdk_options)) @@ -143,11 +143,6 @@ func netifClose(h C.netif_handle) C.int { //export netifSetup func netifSetup(h C.netif_handle, l *C.uv_loop_t, packetCb C.packet_cb, ctx unsafe.Pointer) C.int { - /*t, found := devMap[C.GoString(h.id)] - if !found { - log.Error("should not be here") - return -1 - }*/ theTun.read = (*C.uv_async_t)(C.calloc(1, C.sizeof_uv_async_t)) C.uv_async_init(l, theTun.read, C.uv_async_cb(C.readAsync)) @@ -201,13 +196,6 @@ func (t *tunnel) runReadLoop() { //export readIdle func readIdle(idler *C.uv_prepare_t) { - /*dev := (*C.netif_handle_t)(idler.data) - - id := C.GoString(dev.id) - t, found := devMap[id] - if !found { - log.Panicf("An unexpected and unrecoverable error has occurred while looking for tun in devMap") - }*/ np := len(theTun.readQ) for i := np; i > 0; i-- { @@ -266,17 +254,8 @@ func RemoveIntercept(rwg *RemoveWG) { //export remove_intercepts func remove_intercepts(async *C.uv_async_t) { - //C.ZLOG(C.INFO, C.CString(fmt.Sprint("aaaa on uv thread - inside remove_intercepts: %d", &async.loop))) - //c := (*C.char)(async.data) rwg := (*RemoveWG)(async.data) - - /*for _, t := range devMap { - //C.ZLOG(C.INFO, C.CString("bbb on uv thread - inside remove_intercepts")) - C.ziti_tunneler_stop_intercepting(t.tunCtx, C.CString(rwg.SvcId)) - }*/ C.ziti_tunneler_stop_intercepting(theTun.tunCtx, C.CString(rwg.SvcId)) - //C.ZLOG(C.INFO, C.CString("ccc on uv thread - inside remove_intercepts")) C.uv_close((*C.uv_handle_t)(unsafe.Pointer(async)), C.uv_close_cb(C.free_async)) - C.ZLOG(C.INFO, C.CString("on uv thread - completed remove_intercepts")) rwg.Wg.Done() } diff --git a/service/cziti/dns-proxy.go b/service/cziti/dns-proxy.go index 2d7e2455..9352accb 100644 --- a/service/cziti/dns-proxy.go +++ b/service/cziti/dns-proxy.go @@ -159,8 +159,11 @@ func runListener(ip *net.IP, port int, reqch chan dnsreq) { server, err = net.ListenUDP(network, laddr) if err == nil { break - } else if attempts > maxAttempts{ + } else if attempts > maxAttempts { log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "udp listening on network", err) + } else if attempts < (3 * maxAttempts / 4) { + // only log at debug until we hit 3/4 the max attempts to remove unnecessary warns from the log + log.Debugf("System not ready to listen on %v yet. Retrying after 500ms. This has happened %d times.", laddr, attempts) } else { log.Warnf("System not ready to listen on %v yet. Retrying after 500ms. This has happened %d times.", laddr, attempts) } diff --git a/service/cziti/dns.go b/service/cziti/dns.go index b6a4f688..0e11ba74 100644 --- a/service/cziti/dns.go +++ b/service/cziti/dns.go @@ -54,7 +54,12 @@ type intercept struct { isIp bool } func (i intercept) String() string { - return fmt.Sprintf("%s:%d", i.host, i.port) + if i.isIp { + //an ip does not need the host normalized + return fmt.Sprintf("%s:%d", i.host, i.port) + } else { + return fmt.Sprintf("%s:%d", normalizeDnsName(i.host), i.port) + } } type ctxIp struct { @@ -90,12 +95,6 @@ func (dns *dnsImpl) RegisterService(svcId string, dnsNameToReg string, port uint ip := net.ParseIP(dnsNameToReg) icept := intercept{isIp: false, port: port} - if ip == nil { - icept.host = normalizeDnsName(dnsNameToReg) - } else { - icept.host = ip.String() - icept.isIp = true - } key := icept.String() log.Infof("adding DNS for %s. service name %s@%s. is ip: %t", dnsNameToReg, svcName, key, icept.isIp) @@ -190,7 +189,6 @@ func (dns *dnsImpl) UnregisterService(host string, port uint16) { log.Warnf("Unexpected error removing route for %s", icept.host) } } else { - //delete(dns.hostnameMap, icept.host) dns.hostnameMap[icept.host].dnsEnabled = false } delete(dns.serviceMap, key) diff --git a/service/ziti-tunnel/dto/dtos.go b/service/ziti-tunnel/dto/dtos.go index 4c0f3a9e..385db8ac 100644 --- a/service/ziti-tunnel/dto/dtos.go +++ b/service/ziti-tunnel/dto/dtos.go @@ -88,6 +88,7 @@ type TunnelStatus struct { ServiceVersion ServiceVersion TunIpv4 string TunIpv4Mask int + Status string } type ServiceVersion struct { diff --git a/service/ziti-tunnel/main.go b/service/ziti-tunnel/main.go index 9a4b0fb9..d7b7c8f4 100644 --- a/service/ziti-tunnel/main.go +++ b/service/ziti-tunnel/main.go @@ -41,15 +41,6 @@ func main() { } logging.InitLogger("info") - //if len(os.Args) < 2 { - // // if no args supplied - expect to run as service - // os.Args = append(os.Args, "service") - //} - //cmd.Execute() - //if true { - // os.Exit(0) - //} - // passing no arguments is an indicator that this is expecting to be run 'as a service'. // using arg count instead of svc.IsAnInteractiveSession() as svc.IsAnInteractiveSession() // seems to return false even when run in an interactive shell as via `psexec -i -s cmd.exe` diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go index afc912b0..588c25fd 100644 --- a/service/ziti-tunnel/service/ipc.go +++ b/service/ziti-tunnel/service/ipc.go @@ -110,6 +110,19 @@ func SubMain(ops chan string, changes chan<- svc.Status) error { requestShutdown("service shutdown") + // signal to any connected consumers that the service is shutting down normally + events.broadcast <- dto.StatusEvent { + Op: "shutdown", + } + + // wait 1 second for the shutdown to send to clients + shutdownDelay := make(chan bool) + go func() { + time.Sleep(1*time.Second) + shutdownDelay <- true + }() + <-shutdownDelay + log.Infof("shutting down connections...") pipes.shutdownConnections() @@ -327,15 +340,6 @@ func serveIpc(conn net.Conn) { defer log.Info("A connected IPC client has disconnected") defer closeConn(conn) //close the connection after this function invoked as go routine exits - if len(events.broadcast) == cap(events.broadcast) { - log.Warn("TunnelStatusEvent will be blocked. If this warning is continuously displayed please report") - } - events.broadcast <- dto.TunnelStatusEvent{ - StatusEvent: dto.StatusEvent{Op: "status"}, - Status: rts.ToStatus(), - ApiVersion: API_VERSION, - } - done := make(chan struct{}, 8) defer close(done) // ensure that goroutine exits @@ -366,8 +370,9 @@ func serveIpc(conn net.Conn) { enc := json.NewEncoder(writer) for { - log.Debug("beginning read") + log.Debug("ipc read begins") msg, err := reader.ReadString('\n') + log.Debug("ipc read ends") if err != nil { if err != winio.ErrFileClosed { if err == io.EOF { From 0de8a9dc2931804761ae0f6101a83c87847605d8 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 12:21:22 -0500 Subject: [PATCH 02/10] minor tweaks to the UI code. extract the service client to a library to use in both projects. start impl --- DesktopEdge/MainWindow.xaml.cs | 7 +- DesktopEdge/WinAPI.cs | 1 - DesktopEdge/ZitiDesktopEdge.csproj | 738 +++++++++--------- .../Properties/AssemblyInfo.cs | 36 + .../ServiceClient/ClientImpl.cs | 86 +- .../ServiceClient/DataStructures.cs | 28 +- .../ZitiDesktopEdge.Client.csproj | 56 ++ ZitiDesktopEdge.Client/packages.config | 4 + ZitiDesktopEdge.sln | 176 +++-- ZitiUpdateService/App.config | 44 +- ZitiUpdateService/NLog.config | 12 + ZitiUpdateService/Program.cs | 42 +- ZitiUpdateService/Properties/AssemblyInfo.cs | 72 +- ZitiUpdateService/ServiceMonitor.cs | 27 + ZitiUpdateService/ServiceUpdater.cs | 8 + ZitiUpdateService/UpdateChecker.cs | 8 + ZitiUpdateService/UpdateService.cs | 136 +++- ZitiUpdateService/ZitiUpdateService.csproj | 293 +++---- ZitiUpdateService/packages.config | 7 +- ZitiUpdateService/ziti-green.ico | Bin 0 -> 33296 bytes ZitiUpdateService/ziti.ico | Bin 0 -> 43851 bytes 21 files changed, 1028 insertions(+), 753 deletions(-) create mode 100644 ZitiDesktopEdge.Client/Properties/AssemblyInfo.cs rename {DesktopEdge => ZitiDesktopEdge.Client}/ServiceClient/ClientImpl.cs (90%) rename {DesktopEdge => ZitiDesktopEdge.Client}/ServiceClient/DataStructures.cs (89%) create mode 100644 ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj create mode 100644 ZitiDesktopEdge.Client/packages.config create mode 100644 ZitiUpdateService/NLog.config create mode 100644 ZitiUpdateService/ServiceMonitor.cs create mode 100644 ZitiUpdateService/ServiceUpdater.cs create mode 100644 ZitiUpdateService/UpdateChecker.cs create mode 100644 ZitiUpdateService/ziti-green.ico create mode 100644 ZitiUpdateService/ziti.ico diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 74d20b33..09050b29 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -3,14 +3,14 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Input; using System.IO; -using ZitiDesktopEdge.Models; -using ZitiDesktopEdge.ServiceClient; using System.ServiceProcess; using System.Linq; using System.Diagnostics; using System.Windows.Controls; using System.Drawing; +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.ServiceClient; namespace ZitiDesktopEdge { @@ -28,10 +28,9 @@ namespace ZitiDesktopEdge { private bool _isServiceInError = false; private int _right = 75; private int _left = 75; - private int _bottom = 0; private int _top = 30; private double _maxHeight = 800d; - private string[] suffixes = { "bps", "kbps", "mbps", "gbps", "tbps", "pbps" }; + private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; private List identities { get { diff --git a/DesktopEdge/WinAPI.cs b/DesktopEdge/WinAPI.cs index 234bcf4e..942d4084 100644 --- a/DesktopEdge/WinAPI.cs +++ b/DesktopEdge/WinAPI.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using System; using System.Runtime.InteropServices; using System.Drawing; using System.ComponentModel; diff --git a/DesktopEdge/ZitiDesktopEdge.csproj b/DesktopEdge/ZitiDesktopEdge.csproj index 24f18ae0..88fec2a3 100644 --- a/DesktopEdge/ZitiDesktopEdge.csproj +++ b/DesktopEdge/ZitiDesktopEdge.csproj @@ -1,368 +1,372 @@ - - - - - Debug - AnyCPU - {FA4291EA-A97E-4854-959F-91EE7B330B37} - WinExe - Ziti.Desktop.Edge - ZitiDesktopEdge - v4.8 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - true - - false - 1.0.0.0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - https://support.netfoundry.io - en-US - Ziti Desktop Edge - NetFoundry - Ziti - true - 0 - 1.2.8.%2a - false - true - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x64 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - app.manifest - - - 189AAD422A106DC81E8E79C7E0D2118CEB2E6321 - - - - - - - true - - - true - - - LocalIntranet - - - bin\Releases\ - - - ziti.ico - - - - ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - ..\packages\System.Management.Automation.dll.10.0.10586.0\lib\net40\System.Management.Automation.dll - - - - - ..\packages\System.Net.NetworkInformation.4.3.0\lib\net46\System.Net.NetworkInformation.dll - True - True - - - - - - - - - - - - 4.0 - - - - - - - - MSBuild:Compile - Designer - - - ControllerScreen.xaml - - - IdentityDetails.xaml - - - MainMenu.xaml - - - - - ServiceInfo.xaml - - - MenuEditItem.xaml - - - - - RegistrationScreen.xaml - - - SubOptionItem.xaml - - - SubMenuItem.xaml - - - MenuItem.xaml - - - - - Toggler.xaml - - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - IdentityItem.xaml - - - MainWindow.xaml - Code - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - Always - - - Always - - - .editorconfig - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - False - Microsoft .NET Framework 4.8 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {FA4291EA-A97E-4854-959F-91EE7B330B37} + WinExe + Ziti.Desktop.Edge + ZitiDesktopEdge + v4.8 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + false + 1.0.0.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + https://support.netfoundry.io + en-US + Ziti Desktop Edge + NetFoundry + Ziti + true + 0 + 1.2.8.%2a + false + true + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + 189AAD422A106DC81E8E79C7E0D2118CEB2E6321 + + + + + + + true + + + true + + + LocalIntranet + + + bin\Releases\ + + + ziti.ico + + + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + ..\packages\System.Management.Automation.dll.10.0.10586.0\lib\net40\System.Management.Automation.dll + + + + + ..\packages\System.Net.NetworkInformation.4.3.0\lib\net46\System.Net.NetworkInformation.dll + True + True + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + ControllerScreen.xaml + + + IdentityDetails.xaml + + + MainMenu.xaml + + + + + ServiceInfo.xaml + + + MenuEditItem.xaml + + + RegistrationScreen.xaml + + + SubOptionItem.xaml + + + SubMenuItem.xaml + + + MenuItem.xaml + + + + + Toggler.xaml + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + IdentityItem.xaml + + + MainWindow.xaml + Code + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + Always + + + Always + + + .editorconfig + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + False + Microsoft .NET Framework 4.8 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + + + + + + + + + + + + + + + {26b30979-99b5-4102-bd0b-129bedff0057} + ZitiDesktopEdge.Client + + + \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/Properties/AssemblyInfo.cs b/ZitiDesktopEdge.Client/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..d538897f --- /dev/null +++ b/ZitiDesktopEdge.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ZitiDesktopEdge.Client")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ZitiDesktopEdge.Client")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("26b30979-99b5-4102-bd0b-129bedff0057")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DesktopEdge/ServiceClient/ClientImpl.cs b/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs similarity index 90% rename from DesktopEdge/ServiceClient/ClientImpl.cs rename to ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs index d71b8bde..d2266d11 100644 --- a/DesktopEdge/ServiceClient/ClientImpl.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs @@ -9,9 +9,6 @@ using System.Security.AccessControl; using Newtonsoft.Json; using System.Threading; using System.Threading.Tasks; -using System.Runtime.Remoting.Contexts; -using System.Windows.Interop; -using System.Windows.Documents; /// /// The implementation will abstract away the setup of the communication to @@ -35,7 +32,7 @@ namespace ZitiDesktopEdge.ServiceClient VERBOSE = 6, } - internal class Client + public class Client { public const int EXPECTED_API_VERSION = 1; @@ -46,63 +43,48 @@ namespace ZitiDesktopEdge.ServiceClient public event EventHandler OnServiceEvent; public event EventHandler OnClientConnected; public event EventHandler OnClientDisconnected; + public event EventHandler OnShutdownEvent; + + public bool CleanShutdown { get; set; } + + protected virtual void ShutdownEvent(StatusEvent e) { + CleanShutdown = true; + OnShutdownEvent?.Invoke(this, e); + } protected virtual void TunnelStatusEvent(TunnelStatusEvent e) { - EventHandler handler = OnTunnelStatusEvent; - if (handler != null) - { - handler(this, e); - } + OnTunnelStatusEvent?.Invoke(this, e); } protected virtual void MetricsEvent(List e) { - EventHandler> handler = OnMetricsEvent; - if (handler != null) - { - handler(this, e); - } + OnMetricsEvent?.Invoke(this, e); } protected virtual void IdentityEvent(IdentityEvent e) { - EventHandler handler = OnIdentityEvent; - if (handler != null) - { - handler(this, e); - } + OnIdentityEvent?.Invoke(this, e); } protected virtual void ServiceEvent(ServiceEvent e) { - EventHandler handler = OnServiceEvent; - if (handler != null) - { - handler(this, e); - } + OnServiceEvent?.Invoke(this, e); } protected virtual void ClientConnected(object e) { Connected = true; - this.Reconnecting = false; - EventHandler handler = OnClientConnected; - if (handler != null) - { - handler(this, e); - } + Reconnecting = false; + CleanShutdown = true; + OnClientConnected?.Invoke(this, e); } protected virtual void ClientDisconnected(object e) { Reconnect(); Connected = false; - EventHandler handler = OnClientDisconnected; - if (handler != null) - { - handler(this, e); - } + OnClientDisconnected?.Invoke(this, e); } JsonSerializer serializer = new JsonSerializer(); @@ -121,7 +103,7 @@ namespace ZitiDesktopEdge.ServiceClient NamedPipeClientStream eventClient = null; #if DEBUG - bool _extendedDebug = true; //set this to true if you need to diagnose issues with the service comms + bool _extendedDebug = false; //set ZITI_EXTENDED_DEBUG env var to true if you want to diagnose issues with the service comms #else bool _extendedDebug = false; #endif @@ -156,6 +138,12 @@ namespace ZitiDesktopEdge.ServiceClient setupPipe(); } + public void WaitForConnection() { + while(Reconnecting || !Connected) { + Task.Delay(100).Wait(); + } + } + public void Reconnect() { if (Reconnecting) @@ -183,7 +171,7 @@ namespace ZitiDesktopEdge.ServiceClient if (Connected) { Debug.WriteLine("Connected to the service - exiting reconect loop"); - this.Connected = true; + Connected = true; Reconnecting = false; return; } @@ -192,10 +180,8 @@ namespace ZitiDesktopEdge.ServiceClient //ClientDisconnected(null); } } - catch - { - //fire the event and just try it all over.... - //ClientDisconnected(null); + catch (Exception ex) { + Debug.WriteLine("Service error: " + ex.Message); } Debug.WriteLine("Reconnect failed. Trying again..."); } @@ -344,11 +330,11 @@ namespace ZitiDesktopEdge.ServiceClient private void checkConnected() { - if (this.Reconnecting) + if (Reconnecting) { throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is reconnecting"); } - if (!this.Connected) + if (!Connected) { throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is not connected"); } @@ -500,9 +486,6 @@ namespace ZitiDesktopEdge.ServiceClient try { string respAsString = readMessage(reader); - debugServiceCommunication("----------------------------------------------------------------------"); - debugServiceCommunication(respAsString); - debugServiceCommunication("----------------------------------------------------------------------"); StatusEvent evt = (StatusEvent)serializer.Deserialize(new StringReader(respAsString), typeof(StatusEvent)); switch (evt.Op) @@ -512,7 +495,7 @@ namespace ZitiDesktopEdge.ServiceClient if (m != null) { - this.MetricsEvent(m.Identities); + MetricsEvent(m.Identities); } break; case "status": @@ -520,7 +503,7 @@ namespace ZitiDesktopEdge.ServiceClient if (se != null) { - this.TunnelStatusEvent(se); + TunnelStatusEvent(se); } break; case "identity": @@ -528,7 +511,7 @@ namespace ZitiDesktopEdge.ServiceClient if (id != null) { - this.IdentityEvent(id); + IdentityEvent(id); } break; case "service": @@ -536,8 +519,11 @@ namespace ZitiDesktopEdge.ServiceClient if (svc != null) { - this.ServiceEvent(svc); + ServiceEvent(svc); } + break; + case "shutdown": + break; default: Debug.WriteLine("unexpected operation! " + evt.Op); diff --git a/DesktopEdge/ServiceClient/DataStructures.cs b/ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs similarity index 89% rename from DesktopEdge/ServiceClient/DataStructures.cs rename to ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs index fe426aaa..bc2b7e1a 100644 --- a/DesktopEdge/ServiceClient/DataStructures.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; /// namespace ZitiDesktopEdge.ServiceClient { - class SvcResponse + public class SvcResponse { public int Code { get; set; } public string Message { get; set; } @@ -19,12 +19,12 @@ namespace ZitiDesktopEdge.ServiceClient } } - class StatusUpdateResponse : SvcResponse + public class StatusUpdateResponse : SvcResponse { public StatusUpdate Payload { get; set; } } - class StatusUpdate + public class StatusUpdate { public string Operation { get; set; } public TunnelStatus Status { get; set; } @@ -38,7 +38,7 @@ namespace ZitiDesktopEdge.ServiceClient public Identity Id { get; set; } } - class IdentityResponse : SvcResponse + public class IdentityResponse : SvcResponse { public Identity Payload { get; set; } } @@ -158,7 +158,7 @@ namespace ZitiDesktopEdge.ServiceClient public string AdditionalCAs { get; set; } } - class IpInfo + public class IpInfo { public string Ip { get; set; } public string Subnet { get; set; } @@ -166,7 +166,7 @@ namespace ZitiDesktopEdge.ServiceClient public string DNS { get; set; } } - class ServiceVersion + public class ServiceVersion { public string Version { get; set; } public string Revision { get; set; } @@ -178,12 +178,12 @@ namespace ZitiDesktopEdge.ServiceClient } } - class ZitiTunnelStatus : SvcResponse + public class ZitiTunnelStatus : SvcResponse { public TunnelStatus Status { get; set; } } - class TunnelStatus + public class TunnelStatus { public bool Active { get; set; } @@ -251,34 +251,34 @@ namespace ZitiDesktopEdge.ServiceClient public string AdditionalInfo { get; } } - class StatusEvent + public class StatusEvent { public string Op { get; set; } } - class ActionEvent : StatusEvent + public class ActionEvent : StatusEvent { public string Action { get; set; } } - class TunnelStatusEvent : StatusEvent + public class TunnelStatusEvent : StatusEvent { public TunnelStatus Status { get; set; } public int ApiVersion { get; set; } } - class MetricsEvent : StatusEvent + public class MetricsEvent : StatusEvent { public List Identities { get; set; } } - class ServiceEvent : ActionEvent + public class ServiceEvent : ActionEvent { public string Fingerprint { get; set; } public Service Service { get; set; } } - class IdentityEvent : ActionEvent + public class IdentityEvent : ActionEvent { public Identity Id { get; set; } } diff --git a/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj b/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj new file mode 100644 index 00000000..e8c9e326 --- /dev/null +++ b/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {26B30979-99B5-4102-BD0B-129BEDFF0057} + Library + Properties + ZitiDesktopEdge.Client + ZitiDesktopEdge.Client + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/packages.config b/ZitiDesktopEdge.Client/packages.config new file mode 100644 index 00000000..32637c2e --- /dev/null +++ b/ZitiDesktopEdge.Client/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ZitiDesktopEdge.sln b/ZitiDesktopEdge.sln index 7ce876dc..3edaa021 100644 --- a/ZitiDesktopEdge.sln +++ b/ZitiDesktopEdge.sln @@ -1,77 +1,99 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29318.209 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D66B1C6A-ADFE-437E-B037-C4C849A6CA97}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - README.md = README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge", "DesktopEdge\ZitiDesktopEdge.csproj", "{FA4291EA-A97E-4854-959F-91EE7B330B37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiUpdateService", "ZitiUpdateService\ZitiUpdateService.csproj", "{9AB81609-F5DD-40A6-87B5-F3279D48514D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|ARM64 = Debug|ARM64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|ARM64 = Release|ARM64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM.ActiveCfg = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM.Build.0 = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM64.Build.0 = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x64.ActiveCfg = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x64.Build.0 = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x86.ActiveCfg = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x86.Build.0 = Debug|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|Any CPU.Build.0 = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM.ActiveCfg = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM.Build.0 = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM64.ActiveCfg = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM64.Build.0 = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x64.ActiveCfg = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x64.Build.0 = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.ActiveCfg = Release|Any CPU - {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3E499EAC-C1BA-41A0-8076-D660F8ACC977} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29318.209 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D66B1C6A-ADFE-437E-B037-C4C849A6CA97}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + README.md = README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge", "DesktopEdge\ZitiDesktopEdge.csproj", "{FA4291EA-A97E-4854-959F-91EE7B330B37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiUpdateService", "ZitiUpdateService\ZitiUpdateService.csproj", "{9AB81609-F5DD-40A6-87B5-F3279D48514D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge.Client", "ZitiDesktopEdge.Client\ZitiDesktopEdge.Client.csproj", "{26B30979-99B5-4102-BD0B-129BEDFF0057}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM.ActiveCfg = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM.Build.0 = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|ARM64.Build.0 = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x64.ActiveCfg = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x64.Build.0 = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|x86.Build.0 = Debug|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|Any CPU.Build.0 = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM.ActiveCfg = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM.Build.0 = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM64.ActiveCfg = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|ARM64.Build.0 = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x64.ActiveCfg = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x64.Build.0 = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.ActiveCfg = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|ARM.ActiveCfg = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|ARM.Build.0 = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|ARM64.Build.0 = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|x64.ActiveCfg = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|x64.Build.0 = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|x86.ActiveCfg = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|x86.Build.0 = Debug|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|Any CPU.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|ARM.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|ARM.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|ARM64.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|ARM64.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x64.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x64.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x86.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3E499EAC-C1BA-41A0-8076-D660F8ACC977} + EndGlobalSection +EndGlobal diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config index 2c394ec6..12942b24 100644 --- a/ZitiUpdateService/App.config +++ b/ZitiUpdateService/App.config @@ -1,8 +1,7 @@ - - - - - + + + + - - - - + --> + + + - - - - - + --> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ZitiUpdateService/NLog.config b/ZitiUpdateService/NLog.config new file mode 100644 index 00000000..4115bea8 --- /dev/null +++ b/ZitiUpdateService/NLog.config @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ZitiUpdateService/Program.cs b/ZitiUpdateService/Program.cs index 449ad561..b4bdb792 100644 --- a/ZitiUpdateService/Program.cs +++ b/ZitiUpdateService/Program.cs @@ -1,18 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.ServiceProcess; -using System.Text; -using System.Threading.Tasks; +using System; +using NLog; +using NLog.Config; +using NLog.Targets; namespace ZitiUpdateService { - static class Program { + static class Program { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + /// /// The main entry point for the application. /// static void Main() { - UpdateService updateSvc = new UpdateService(); + Environment.SetEnvironmentVariable("ZITI_EXTENDED_DEBUG", "true"); + + var config = new LoggingConfiguration(); + var logname = "ziti-montior"; + // Targets where to log to: File and Console + var logfile = new FileTarget("logfile") { + FileName = $"{logname}.log", + ArchiveEvery = FileArchivePeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + MaxArchiveFiles = 7, + ArchiveAboveSize = 10000, + }; + var logconsole = new ConsoleTarget("logconsole"); + + // Rules for mapping loggers to targets + config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole); + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + + // Apply config + LogManager.Configuration = config; + + UpdateService updateSvc = new UpdateService(); #if DEBUG + updateSvc.Debug(); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); #else @@ -23,6 +45,6 @@ namespace ZitiUpdateService { }; ServiceBase.Run(ServicesToRun); #endif - } - } + } + } } diff --git a/ZitiUpdateService/Properties/AssemblyInfo.cs b/ZitiUpdateService/Properties/AssemblyInfo.cs index e3ab62af..9ba3964e 100644 --- a/ZitiUpdateService/Properties/AssemblyInfo.cs +++ b/ZitiUpdateService/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ZitiUpdateService")] -[assembly: AssemblyDescription("Ziti Monitor and Update Service")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("NetFoundry")] -[assembly: AssemblyProduct("ZitiUpdateService")] -[assembly: AssemblyCopyright("Copyright © NetFoundry 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9ab81609-f5dd-40a6-87b5-f3279d48514d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ziti-monitor")] +[assembly: AssemblyDescription("Ziti Monitor and Update Service")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("NetFoundry")] +[assembly: AssemblyProduct("ZitiUpdateService")] +[assembly: AssemblyCopyright("Copyright © NetFoundry 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9ab81609-f5dd-40a6-87b5-f3279d48514d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ZitiUpdateService/ServiceMonitor.cs b/ZitiUpdateService/ServiceMonitor.cs new file mode 100644 index 00000000..8d02bb03 --- /dev/null +++ b/ZitiUpdateService/ServiceMonitor.cs @@ -0,0 +1,27 @@ + +namespace ZitiUpdateService +{ + public class ServiceMonitor + { + public void MonitorSrevice() + { + //a simple method that opens a pipe to the data service and tries to read data + //from the pipe. If the pipe doesn't respond + } + + public void ResetNetwork() + { + + } + + public void ResetRouting() + { + + } + + public void ResetDNS() + { + + } + } +} \ No newline at end of file diff --git a/ZitiUpdateService/ServiceUpdater.cs b/ZitiUpdateService/ServiceUpdater.cs new file mode 100644 index 00000000..4853fccc --- /dev/null +++ b/ZitiUpdateService/ServiceUpdater.cs @@ -0,0 +1,8 @@ + +namespace ZitiUpdateService +{ + public class ServiceUpdater + { + + } +} \ No newline at end of file diff --git a/ZitiUpdateService/UpdateChecker.cs b/ZitiUpdateService/UpdateChecker.cs new file mode 100644 index 00000000..220d0475 --- /dev/null +++ b/ZitiUpdateService/UpdateChecker.cs @@ -0,0 +1,8 @@ + +namespace ZitiUpdateService +{ + public class UpdateChecker + { + + } +} \ No newline at end of file diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 711269fa..43ba01ef 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; @@ -14,10 +12,14 @@ using System.Timers; using System.Management.Automation; using System.Xml; using System.Configuration; -using System.Collections.Specialized; +using System.Threading.Tasks; + +using ZitiDesktopEdge.ServiceClient; +using NLog; namespace ZitiUpdateService { - public partial class UpdateService : ServiceBase { + public partial class UpdateService : ServiceBase { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private int checkTimerDefault = 60000; private int updateTimerDefault = 600000; @@ -38,7 +40,21 @@ namespace ZitiUpdateService { ServiceController controller; public UpdateService() { - InitializeComponent(); + InitializeComponent(); + + Client svc = new Client(); + //svc.OnMetricsEvent += Svc_OnMetricsEvent; + svc.OnClientConnected += Svc_OnClientConnected; + svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; + svc.OnClientDisconnected += Svc_OnClientDisconnected; + svc.OnShutdownEvent += Svc_OnShutdownEvent; + try { + svc.Connect(); + } catch { + svc.Reconnect(); + } + + svc.WaitForConnection(); } public void Debug() { @@ -50,22 +66,22 @@ namespace ZitiUpdateService { if (ConfigurationManager.AppSettings.Get("UpdateUrl") != null) _versionUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); } catch (Exception e) { - Log(e.ToString()); + Logger.Info(e.ToString()); } _rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenZiti"); if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); _logDirectory = Path.Combine(_rootDirectory, "Logs"); if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); - Log("Setup Watchers"); + Logger.Info("Setup Watchers"); SetupServiceWatchers(); } protected override void OnStop() { - Log("Stopping update Service"); + Logger.Info("Stopping update Service"); } private void SetupServiceWatchers() { - Log("Setting Up Watchers"); + Logger.Info("Setting Up Watchers"); var serviceTimerInterval = ConfigurationManager.AppSettings.Get("ServiceTimer"); var serviceInt = TimeSpan.Zero; @@ -79,21 +95,21 @@ namespace ZitiUpdateService { upInt = new TimeSpan(0, 1, 0); } - Log("Ziti Update Setting Up Watchers"); + Logger.Info("Ziti Update Setting Up Watchers"); _checkTimer = new System.Timers.Timer(); _checkTimer.Elapsed += CheckService; _checkTimer.Interval = serviceInt.TotalMilliseconds; _checkTimer.Enabled = true; _checkTimer.Start(); - Log("Service Checker is running"); + Logger.Info("Service Checker is running"); _updateTimer = new System.Timers.Timer(); _updateTimer.Elapsed += CheckUpdate; _updateTimer.Interval = upInt.TotalMilliseconds; _updateTimer.Enabled = true; _updateTimer.Start(); - Log("Version Checker is running"); + Logger.Info("Version Checker is running"); CheckUpdate(null, null); //check immediately } @@ -111,7 +127,7 @@ namespace ZitiUpdateService { XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/" + _versionType); string version = node.InnerText; if (version != _version) { - Log("Version Checked: " + version + " on " + _version + " from " + _versionType); + Logger.Info("Version Checked: " + version + " on " + _version + " from " + _versionType); UpdateServiceFiles(version); } } catch { @@ -130,7 +146,7 @@ namespace ZitiUpdateService { if (controller.Status == ServiceControllerStatus.Stopped || controller.Status == ServiceControllerStatus.StopPending) { if (_isJustStopped) { _isJustStopped = false; - Log("Ziti Service has been stopped, update Network"); + Logger.Info("Ziti Service has been stopped, update Network"); //should not be necessary in v0.0.10+ RunScript(); StartZiti(); } @@ -155,11 +171,11 @@ namespace ZitiUpdateService { xmlDoc.LoadXml(result); XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/"+_versionType); string version = node.InnerText; - Log("Got Version: " + version+" from "+_versionType); + Logger.Info("Got Version: " + version+" from "+_versionType); if (_version.Trim() == currentVersion.Trim()) { _version = currentVersion; - Log("Version local is the same as the version remote: " + version); + Logger.Info("Version local is the same as the version remote: " + version); return; } @@ -170,38 +186,38 @@ namespace ZitiUpdateService { string[] files = Directory.GetFiles(Path.Combine(_rootDirectory, "Service")); foreach (string file in files) { try { - Log("Delete: " + file); + Logger.Info("Delete: " + file); File.Delete(file); } catch (Exception e) { EventLog.WriteEntry("Ziti", e.ToString(), EventLogEntryType.Error); } } WebClient webClient = new WebClient(); - Log("Get From: "+remoteService); + Logger.Info("Get From: "+remoteService); webClient.DownloadFile(remoteService, Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip"); - Log("Zip Downloaded"); + Logger.Info("Zip Downloaded"); ZipFile.ExtractToDirectory(Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip", Path.Combine(_rootDirectory, "Service")); - Log("Zip UnZipped"); + Logger.Info("Zip UnZipped"); File.WriteAllText(Path.Combine(_rootDirectory, @"Service\Version.txt"), version); _version = version; if (_isNew) { - Log("Installing Service " + _version); + Logger.Info("Installing Service " + _version); ProcessStartInfo installService = new ProcessStartInfo(); installService.CreateNoWindow = true; installService.UseShellExecute = true; installService.FileName = Path.Combine(_rootDirectory, "Service") + @"\ziti-tunnel.exe"; installService.WindowStyle = ProcessWindowStyle.Hidden; installService.Arguments = "install"; - Log("Install Using " + installService.FileName + " " + installService.Arguments); + Logger.Info("Install Using " + installService.FileName + " " + installService.Arguments); try { Process exeProcess = Process.Start(installService); exeProcess.WaitForExit(); - Log("Installed " + installService.FileName + " " + installService.Arguments); + Logger.Info("Installed " + installService.FileName + " " + installService.Arguments); } catch (Exception e) { - Log("Cannot Install Service - " + e.ToString()); + Logger.Info("Cannot Install Service - " + e.ToString()); } } else { // nothing to do @@ -211,7 +227,7 @@ namespace ZitiUpdateService { private void RunScript() { string scriptText = "get-netipinterface|ForEach-Object { Set-DnsClientServerAddress-InterfaceIndex $_.ifIndex-ResetServerAddresses}"; - Log("Executing: " + scriptText); + Logger.Info("Executing: " + scriptText); using (PowerShell PowerShellInstance = PowerShell.Create()) { PowerShellInstance.AddScript(scriptText); IAsyncResult result = PowerShellInstance.BeginInvoke(); @@ -225,12 +241,12 @@ namespace ZitiUpdateService { controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { try { - Log("Starting Service"); + Logger.Info("Starting Service"); controller.Start(); controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); SetupServiceWatchers(); } catch (Exception e) { - Log("Cannot Start Service - " + e.ToString()); + Logger.Info("Cannot Start Service - " + e.ToString()); } } } @@ -242,17 +258,65 @@ namespace ZitiUpdateService { controller.Stop(); controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); } catch (Exception e) { - Log("Cannot Stop Service - " + e.ToString()); + Logger.Info("Cannot Stop Service - " + e.ToString()); } } - } - - object logLock = new object(); - private void Log(string message) { - lock (logLock) { - File.AppendAllText(Path.Combine(_logDirectory, @"Log.log"), DateTime.Now.ToString() + " " + message + '\n'); - EventLog.WriteEntry("Ziti Update Service", message, EventLogEntryType.Information); - } + } + + private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { + Logger.Info("the service is shutting down normally..."); + } + + private static void Svc_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { + string dns = e?.Status?.IpInfo?.DNS; + string version = e?.Status?.ServiceVersion.Version; + string op = e?.Op; + Logger.Info($"Operation {op}. running dns: {dns} at version {version}"); + } + + private static void Svc_OnClientConnected(object sender, object e) { + Logger.Info("successfully connected to service"); + } + + private static void Svc_OnClientDisconnected(object sender, object e) { + + Client svc = (Client)sender; + if (svc.CleanShutdown) { + //then this is fine and expected - the service is shutting down + Logger.Info("client disconnected due to clean service shutdown"); + } else { + Logger.Error("SERVICE IS DOWN and did not exit cleanly. initiating DNS cleanup"); + //EnumerateDNS(); + var ps = System.Management.Automation.PowerShell.Create(); + string script = "Get-NetIPInterface | ForEach-Object { Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex -ResetServerAddresses }"; + ps.Commands.AddScript(script); + Logger.Info("No longer connected to the service. Resetting the network by executing reset script."); + Task.Delay(1000).Wait(); + ps.Invoke(); + Logger.Info("Reset script executed."); + //EnumerateDNS(); + } + } + + private static void EnumerateDNS() { + var ps = System.Management.Automation.PowerShell.Create(); + ps.AddScript("Get-DnsClientServerAddress"); + var results = ps.Invoke(); + + using (StringWriter sw = new StringWriter()) { + foreach (var r in results) { + string name = (string)r.Properties["InterfaceAlias"].Value; + string[] dnses = (string[])r.Properties["ServerAddresses"].Value; + sw.WriteLine($"Interface: {name}. DNS: {string.Join(",", dnses)}"); + } + Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); + } + } + + private static void Svc_OnMetricsEvent(object sender, List e) { + foreach (Identity i in e) { + Logger.Info("metrics!: " + i.Name); + } } } } diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index 896f9f87..f05b8b80 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -1,140 +1,155 @@ - - - - - Debug - AnyCPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D} - WinExe - ZitiUpdateService - ZitiUpdateService - v4.8 - 512 - true - true - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 1 - 1.0.0.%2a - false - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x64 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - ziti-white.ico - - - 189AAD422A106DC81E8E79C7E0D2118CEB2E6321 - - - - - - - true - - - LocalIntranet - - - Properties\app.manifest - - - true - - - bin\Releases\ - - - - - - - - - - - ..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll - - - - - - - - - - - - Component - - - ProjectInstaller.cs - - - Component - - - UpdateService.cs - - - - - - - Always - - - - - - - - ProjectInstaller.cs - - - - - - - - False - Microsoft .NET Framework 4.8 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - + + + + + Debug + AnyCPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D} + WinExe + ZitiUpdateService + ZitiUpdateService + v4.8 + 512 + true + true + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 1 + 1.0.0.%2a + false + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + ziti.ico + + + 189AAD422A106DC81E8E79C7E0D2118CEB2E6321 + + + + + + + true + + + LocalIntranet + + + Properties\app.manifest + + + true + + + bin\Releases\ + + + + ..\packages\NLog.4.7.5\lib\net45\NLog.dll + + + + + + + + + + ..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll + + + + + + + + + + + + + + + + + Component + + + ProjectInstaller.cs + + + Component + + + UpdateService.cs + + + + + + Always + + + + + + + + + ProjectInstaller.cs + + + + + + + + + False + Microsoft .NET Framework 4.8 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + {26b30979-99b5-4102-bd0b-129bedff0057} + ZitiDesktopEdge.Client + + + \ No newline at end of file diff --git a/ZitiUpdateService/packages.config b/ZitiUpdateService/packages.config index d5a74547..4182569c 100644 --- a/ZitiUpdateService/packages.config +++ b/ZitiUpdateService/packages.config @@ -1,4 +1,5 @@ - - - + + + + \ No newline at end of file diff --git a/ZitiUpdateService/ziti-green.ico b/ZitiUpdateService/ziti-green.ico new file mode 100644 index 0000000000000000000000000000000000000000..1773e4e95cd221bbaf3cfd73d26294456a6b9091 GIT binary patch literal 33296 zcmdSB2VBqJ7dZTBZfXi*eNky26-EhTM{_D)vzEz4xqp&ItlRfFME8(ILQ<6Jbk?Kg8=Wpyo29ho3B53{{^;z>s0_V0I;ARR%ehNV=%N0 z1MlIxFW;iT6}H9qgID^!C;?+B7uW>~TeEzNt>;wC%0GR&n8&<8K1e-ALgm(QPi%wbaL7yCE**Fh~ z3-*D1Q@`&E$I=^Q#3X(4Kx+LxucKve?tT8`?bG%KPfhCApjjbsfG5LP(fS1KU+IUmpLs8gN&6g#)&p|@YY6CeKtmdig7w_M2l#b>eZN~j0&L{8G3LtjX)tek zOyVau^s#|WKk5r{z`oz?AD-{v8ian-$E~4i9|3;6jaU5sK^}m7`}G@~$GI^{%**w3 zG}s3Wb2LYb{hkxN-Uz;&U+mDKV&wJnVFYJ%U0dannAFTl@j7C5C#&ZWmp0zWWJ4 z91s^CFTBFF8wBvPK3Krdn4G03bjIh4D5zI#-|zZfJ~sA+xFAl58?G1JpN-$Yxrb|E zG|Y-ATTOy^(NKS9Erz4D1}J~FPOv}332{pU_yFJ=oEx9v+MgfWhkm|#8-09`7ISil z8B?-)7wQN!)EOJwZn%dyA#TXS8+h=4+baY7SEh~8>7Os4p)QBx`Ar-_m#rnEjfXjY zHV23k;(i6-hcv)-f%@nKpgH|>FgkMD1V=A;hXJ+~r>~Pgd7{<&iGSGt3vMWj|MMNr zTX&EF18r5oN-Vkx=uEgqQ0Mwjs-cbz&|*wSxH08xDQJ&z`Jc%IUgp2S57)ru!+!MB z z&!5T{Tql**9rP% zotO$t#PrEut^GIp85#kA7XmSWpV}9w51^muG$#FT_#y6Z?8`q^=Ia=6FH8Uo0r+Np z;X3IJZNVHHp!u0TgMDCMh~tMp_#VJd?fVb*6!PG2+4qeDei;Y)9RJxrK_TQ2^9WkR zFoFzGi6Fv3An=&}^auOk`$Jq1C&Uf?FirsS0N^mTg<1&0{8UIWkzU>(3W{PTln*ao(RePG`|q64(Y%LVkQ zg8}^12T%v+8E6|oo1f7H`$8NL7Y^^gEeO61xb6W^3gD;y7L|s&ELWIg*YMZ|Dgf@4qV~9e>pa2 z1N;{PEIfzzVQaBaxBg-uHqrpc_=bir&v1`QAnsoQTyLnaU@V~(T?_%^HKFu7YXEV> zF>w3&x*fVvCv$-nW^SGwTdn}aoPK;>g%XU`y^?_a)l4`=Ux!3z)v90QJpJJWzG z93RTb7=W*1{M$X;d#`aNFz@FWd*IJN+W_xjOaS@=|JL`*H#im?6F+w-tH09^*9Oip zcFqpRd(g*$W8R)WkA}XP!>GjX?gJbXt^qFqs4sqde8_9LOIOjk%hzyx2loTmdUWO5 zeVktkebL|82RJ4i+X?{m7k_Jfh!@To`V=?k?7#ZYkOtq0!{|122?qL*zcD@>6OMiV zPsWDhfcOn=zr81vzxo7lJlxq~ArM`Qe1f)k$NM|`0LO0s)%gF+g#g5ocB7+bEm1dS z&!8GnPci22cA()I3HXN~7R8Rchq?&*jSoHtp`l;;3*2yQXuE#8_He#1?gY=iP~N&Q zRhSlZ5%8}Q(Qu#P`2Lee;dm%`mi=;OLv>)vG0-3W#XjI|=nuz-=N%|p@Ep|wb1<@%H<-&6cZtvk5Lm#>a`x@JXD!`qOpe_P2*5A>|aQvUHKb)K6 zm=uufB1{H218eqE;N%L#@lcQl;abD9ACv_ki>Mdt576+OV+hYZ|FWkW_8afF{BZuT z9b7-C*E%tkV0|@lJPr35&Kv4M5Q7DKQ-pE*Acq39fOsm&@5nxWJjj3lDt~YdpuEE~ z>C5#DwDAc0SGj`YUj}j9I#dpf#p2du!`$#={M!|d{Y(7=*AkvrAy54i7a+mO6<8Zw z`~dQs)&Rw?WbJ!8@cIYpz+dWL7(0gXb4UZE1@1jOf5SKej0ykYydf=k{R?ftFYOQ9 z2dHCUJOl0pJhMO@*Mu&BKG;`1_y=_0?GLnp;sC~e)fesqh~eYTdoaG@H*plzj4nij zc)?em_!W*X-{Dwz`-`{17r?uJ-9xwrkoTc1HlYfE|8pA!Z61u3z;C}fHXP%t|FOXb zf<6)SQ~wnYTmy@Dd_c!P270gto4I%i<}m)1?(cAZ<9}@!AFji1`JZsEVL%7H#lFPl zQhrm{zT} zur2HZ`~K1Sb0ZDk(E;_O6@WAV&~D&;2fPi1@8CDsW(R=Z8W$T#fW;sX0Vo7QZWV#x zm`5OJhY<*}N(6!^5QGjOApY`)?_UXU4FjAIHyi_w1;>PA!#D-Z6`2C~8mHI+9{vyg zz&hZ0niAZ@*qQ}^H~^IZ1^~n#L_JMtIc>WKO^-DBg#v=q*E&#Rw z@&UXD@H;$+=TX=e_JMsN4u}il{D15RaRY!hl^;L|fIa{})g#~=p4MMoAr6QO;)J;2 z82`t95Z4#j46e=q`T_ijUHKLL`0o%WoCh2Oj`g4U3XcO^p&wBQ00{s;-oIX<4h23X z3dGib=ghbf7aXGi037qL=kTR3aLqT4@ymPY1H%|t_EIDg*u-Diq95UdW5Tij zSS~;xIB#MAHUNHljsf3(G%o%f2IQ%5z67YPK>mhJ!}r^Gg=0e+AT7W3gY5voIm3SQ z05;^3tNTh z0{&FQl%{_=I)J;+_6 zz?t`NWEoEnqz$KAHwpuIhvyrpdw*L;ZlrnRJv?`Svk(f#W8&u=ksv;WtViX+S_$mU zIXfggA8%~`2ltRRNaHtq@a35wT>lf_fc`k%2XQSBH%%kas{V3Y>qR!FU|T2HeA#062?+T9`PnCpB0Q zD?@=8-GAXDNGqOa;FTKy^zHweZhT)TyKv3n+E;)*fOJFM59bAC8par*OoJTD-`WI7 zBcv75jGqJa0sl|T^c^}s9Y&?S>?IQ-?&Lg|A)mRvdHGjwZw|oR?g)~F@ zp%0!1;6JZD!~yLV&^N$t)nFrMOpqIG7TgCI%L8#toLvJokjUSNCI5^rNHe5e1OW8K zf7b4|-$VNFb_?eIT|US_+5M(JoDawcAYr@=0;m)CW{!VA2c#L&ZUNx8^AtbB4{3*T z3TlFIb_>++;9{5X{r7#~nuEGcoJ{~VPbkA7=D(3;NHe4zp6C8&j6Zn?X#};nxV!?4 zheO^0v3bHlZHsnEGg*MQ!r!2%nK1$i?h^e3U-0d<-%M~m~>KzF6x2`9mw@>u;piT`5Z7SqH$Pb_<4*ET4 zuoqu>2g(4*H=>}=0BaH;ufctQYhJW+3k7Y&Q7~7C`%n1zOIt|$AIa}O`5I504*+We zz~3Q!r4{PHm|3fJP~-Pib|GEybLVjT0(@#Ds5wJIIS1{KdV>sqn&0?-j*xb!gT8;job=?!iHvMmVmuS9>l<{N2) zwEwaFf^w{F>Qp?S1^zLAAHRp%>nPf{k8uL_W{~5=mS6ix&LqM5A=7y-}vBUHxBqX z8+i-ANB`l9_n+Yz;IGavkRB+nAV!Fj&l+Hl;hw;Chu@$-P_~+ki@^b(`G0)r|3Ev` z5BT#7r2ntakC0YS?}q|4C^&rtWdh0uaTHY5Ay1N%zrnqs5A^RKjkq<(1%ST%^X1)UaQ*A};Xm;OC%{Lqu<-l~0iJQ84ZxoT z0RBH-n+<(_7(e`O5B@rS3;77v3;yFPzry)w(Dp(b48Lul<8SUE-~2Rw``sLLLEFEI zpF@2F>i>{1-VFUr==;FhkpD>gFXQKUUIODl-h#OTm^1jR`~|F61U1A!c57e_-G8-P zzn#DMZVrE)KZ3ks3TiMwO%e*!ApRHLhwBd4@wf9w-^~H;gARZ{&fmc~fLg(IP|NwJ z`A58L{B{2ByE#C9g1Jm6`%r)3@&ED)ef+=B`)~|6)_=;M{$LJJw?f?u^}v6X#~;ld z;)J;27;vor(eZei{^t4x-~aFG2fm-{mw7B{IDAUK9*Vu?%}x_A6vr5IPhm}{Fx8-fqnlIpJ1^F1P)*U5xgG; z1x4^$35tv0H4xMffh(vh0+mGw@HbH4Cx<|Q=Ags>eFZg0pb7~A{_YC=z~8Imua)o_ z{0%$)8VH|3pB8NxOK&^ zGgwo&?%~>+hiNETD8VQ+sz;QL$U0EzlQ}XfWttsJ5A3T$({kEWYmD?NPq#R(GC%YVzc} zu5Mm)(^56Jw9b8((Pn|Ryk);_s>j}yiy&Ca^gQ$vku?OJr{t3#9g94N7$57n${sDh zk~y{RKQ1$lYW5&b$gH_Ziy*HgHGi4CL)2#V4UsDmGbe#7!5Mi%Qc)_W5NrT3?cTj8 z69UU1u4Du?;(|POzGXY{g8Z3PU4k7#>}OXJ+lZB(mJlTD(GEI?IIk1fLUI%FIp{=? zm%K=8D5X0YFJ<&d+&&bN;l<(bLvEngsT(_|iI$0cf(9ztOw6L+Y(G{ znR;q(=MUk$qrTMaYnd=S03^Y64Shlt9|b? z0xJ+jo~v#wc8%-~9^(ArVs@G%4#R6BZ`F1x>`WPF;G+>nJib5JkRH?#^e9j$$mQ5| zH*wzKn$#-^2EL4*gr3{GFD8U__9#%b5zrS2pp+t% zBV#8qX2r+|lX9h!a8m2E65S8X5BwNZkddsFI_HvqL}zOby(g0=xhGLAf$g4Kd)Hsw zAs3wIsWjJV4WKI`F4&aqv+nfi*ujf7L1ckTmF~$8FC}|DMG&VA_N5fEB>p_jG< zOjo@VDz+C)y#B580nLzdixY*yKdOx%6V$(k~d60>8TYgjHtYIH&p6|c-# zKr#8KQY#3hz9lH07vYwKQLdc3N6gtXYkj8oC_690guvJ|WJdZHGojPjVyu1Tw!lj> z=Waeo?WXXhS>0ycnq#_7CGThGC^;WmIhN7mANj%4uCk*t?wAkF19aZVB4eO;c7WhK zhqf#>%UxPcoOkb)dSVQ_mGIrdp3227fyI|WS6-d$Y&PEnPS1?9!`a7rDxbPCXo?VI ztgO3emtiW1te=$_CI~cf4P%q@f-Y1(ttWkO714PCYd+kfO|oy%z=VNRQb!?1PEnXm zla>A!J7IFL9H}p5>_WD!$MpxK#|h1g50GB3+&V0xEwibiLxK3gvUI@NdaOy{kW%be z`<2V15;h80ddJ#>#~b+4*HcBqsR^*us0#H9TrWBc%DOIKw-2*c&ZS(aP}q^5<+|o! zJ)|QmQk>;9O<|UBe&Ud<{gAw@&tbXCC!#V15N?NuXfkAnq?2vdSYKDpAB^@77%82| zYdqwmx&1LhVb8wh9`Oec$<<{Ew59h)9bKtt4x_YZQF11nblhRy?sOTdQ%2pf z?$7KZoHtD|qck`VGAf(eQ3MvhNJj)vqdYoa+)4oug>zE_J_PjSSf0s-&*QIeKJfG9qPyR8(dD76 z%#ca_@rPUp~( z%_=vnmJcK%)bEf_^`H)S=I`g_Ba<;gOi9lZ4ku|Zt+`4wGO6~5>7Dy%__3+5`1(SI ztu)>8@shWjt1=2oVmlB)-YQs%rAGHsa@Qw_PRD-T4Bj}QzJdD!S576CCVLwC>bBR& zsmSxj=(b&oy-2_0%0kDYpK`b7QDD^8pyn-s-ihUJD&>+jJKjiKvdkSYD%$6g$V~sh z?_EXJ*+(+5drC^(pSdQS-#yiHnkGz>F{qg#(0e|BX{>1fqXCVC`fl#yW&84b6SwW- z9l6^6Qf- znDlU4IE4eLE5WRR(HqFSVi{+S^jB{hst5cJ^ zcG@dyHy9{|M#$Tc%cDb1k)dc^=e3yB3<|Vto(SwNK&oh*A^8mz4{<~*IoM4H|9SPq3-5v>noG0YHbL5JbtQEe8|1Vv?LTk6C?{dKfWPvW^j&a%FG2 z7=8cBMIR$;;Q{N1_d2oNLm@rDx;~5VY?JfyC1)gZ=u_UiMyNg1f;G|R|5PYL6~B8` z+Vylog=jgqf9b$8dKGGghCAh4i3I8_q=KaWv|$B`#C+1LQ?b7OG`fXGb5m3#NwLkr zDE^Y{LK^Cl9*^@%uTE~C)RUUgtfT<~G`0#L|>iDbHkYrUp3KYe>g6VO75`Hj^ZLaL|`V&VFnsMu=H@BRQ z-kYDV z=vikQf59-Y#(+kPy)FPjw$t58SB~gpE#B&89>Km68gjo+J@#BvR_tjN?)}k{*jy*D6}ByJ zLXwv)Qp|0ITVw9YKMFypY4fK&mGFx{Lw?cY#CmfQ-aRYy79pq^5F5Srluns*nyd1_Fl~_Gbyo{(N=#_+?z+U4%AM^!0RcPpEfjl2Tu zb!_u0o2i-4Y=f(Ey?xC1SoYC73~{xAnH8o+WAd2t7c|GTytk6O>u=)CH-6zTaXo0C zo`;|V-^t?sWQxW^!*T@SqIrSg6!K*R9Hi%LKS#z}ANg?Dno%z9d9(9H#@bmSgnn1Q z4=H=*OeTBOqYC;_bWFc-&zw{OfouGEi|~Ql9nA7I=ikrUS1|;eQ4@T)cN6Uzbd@gm zqApT>rC^MAxu5TN02hZysb2rJ0A7lBWwzP15oL7)W8}8Br#Ki33tr7${1#3aW9<&dUXfS zn+t@S)e%!=*vAv?gIwH(le=mI4xs&OD#<18J)AC2dzXm*i1K&bDTN8!Rm5dm7(08E zIoEw?M^oLT;^WsOgTmeFq)vRjiwxW`Mf8vRd-DsW8jg}_q6y|+^ix%jtKB{x>6iF| zT7h#?jKMs2;L%m;k7v&*RaZaDepaV5pVt(Ag@{li~wG3OIT!S-Y%GD z?&$7OL;PMu{0JxCS^Ic#kq7T9QVqlE2?-yQ6E`9BQfe@O?z1af&o!U2QMt(Ml+IC< zH#I1mGdN@2xk##O@0eSWbW zBtYnHi$5ChiaxPfquURiG;Y!$dow_IlS*Zp!3&vK*~~0`36@0iQ`Rft7Xg>;zkF;@ zZ;p)U5z-vG)xF*Way#yq3VOL?5=NibC6oCukuU2ZsaB}7JXCU#Qm>*KerBo|J%7BOvjLh z_!j15uA0tXB4((4eSb0Q@$5kpZq@`c(LK{??k3b+Kz-$hC!0$S)Y3`|BI1$-F}8eH zTB>tnpVL0ry~#HxG!MCnahAoid*`aM&7ssi1amse^}%!XD*1uHMY~i4_>N?E+6%T~ z3o3c^#iXn4$tkw454Br7Pgq_6pKO1d8izSliE~i0?+4Wtre!8F z-p$^1NL{&j`N6_&2l9lwyGZqjEHp`@;t1{s(xy=PtJA600|B$@YIk^$P>3uy@RrFK zu_?6<2gU-3biz%8oelnWSsW}{h~`6aNJ=6su!*0yz1EjFpA%|oZ)GylN5pEbFkMaq z7SuMuML;k_tA2tIoD?e7ZzNHSg!18w_ z7+!J7n>S#t6qrNjJy6|o3`o))!x)(HD!xd#L- zC@P;cn2zqjOK#cN2>e$6sEIHaih6zE?Fd(1N4cJHm<4LVY`6FtN8&0)sJzit2lV zJl0h$XUKZ^8(14;Vn)~peVtDkhM#q2l_svfWU<@hL(hIpI(2%o`T9k(N?R=h!lIM` z;kyrS-5N|U4#+GmeSSgK^YK$*Z0huhAn{NNuC&~;yb7Ia`%XH`#1DojYZ77~eQFTV(Rhztp%J9~a*M+hNv@P~U> zsc#>@Q?oNPJc}Z&D zXAaj+iw80Jk6jhhswLT8Y}Yx#}bd_c|Y0AvG868E=(%bF&Tbsj$&)B68$;{sE zUm*<`SzYCtUZfga4>s9qRA15*LZe`OPOm%(a@(G<6k6CJva@`V8_0Nj23Kv>)41-kC?MeiyH%=J zeYT4r{3_4PoEz&r6eDUrJ0tt{t#{)SC2{v5n&C_I`R=*WHIA*Ou1e_;diP zfyOFMQi5GU79`2625qxl8eF3Vb)OgXd7H!4SXiVgLgk%`ZVC_fU~_aA8{9)*0Xy47 zt>@IqSCL9$%FN#*n!Jy_E;G8P8W;}Y!v?@vJLo^-(_>O}d*HMN`*a(3f&?)yHJL2+ zEwRO9b2h~Lj89m>sG~Fz{56~PW;zDm-q_abl5iAlBJ9wF-HUWJKR#1Vv$>P5(}C|q z`mNKE*3=2h9hV~9x)*|0#1KJ8Y4*lx~p99nUtH^oxl|}&f4`$3qZ^58QsQb zmTsaQ%o<$WSX9w&s0|dPz=fa)w>=AT$x;*(pRh$otPLj@z{XuJqIYPXz3um4_2}e) z&NxX$3bSIXxz9OjuHYUH$r($Mm#i05h&d^CIe0wU&xUh+rrw>=m|0{1{5@J8=Xao& zy~Oc?gavuT?p-REQbP$&gr;TO3eZA)NO^>fx-eSWo!1{Z+3*hN$HS_6{W4_iH_7aY z9P*J;y}fL2E=*?W&$CDpH(kCHb2MR(q870xv-Lf5j4Xixp{?3mRa}gt@a{)d)^J;= z5)sULzgl%Vr6Zrw39x56my*lg@H|(cQ5djpKV`mRU9%MtzBMe0t9d@xjEB%k5Jci4 zRnui{-)QFwrK?U_a3;B>oi&o$Tbvm8?R!$b5?$k)E-(VYEII{!V*cgh|+^@$3Eq)KCH~FkaxtN#Z2Twa^H5QdMRqvk^CGfZ}wPkX}8LqpmwV+#sTPfF(`i6Ip7VJ-Kd(F#q zaI+%Qa%_0M820g;ev^@R*CyB7nK8|z;E+rwuKhl!oT9u(LMg9%R@k=~nGnX?SSf?{ zMNN5-aXb<%alC~QthXQu?J)V zG#2_T#mRgu%MN{t!h|X}i-g#%Fp|#!zKF0<*;Zzwv^lI+EWL%U>g-N!B44ICy=7gs zb_d&Omn&RH*6%PH#UNuBktyceTP|J|Ij(YGeR9^mJ)mRdV=Oq$**KW#x=FPS|N9LSFjWhydA$Xk-{*w>1e^xV;W3t*Z4b$ z`vj347mN>HJErsOWb5O4f6yucC9mV0U3!9#1dq{rmd%L3%l%_{w6y!l;n<+I7eK)oyq z=n0O#yYqrZ;f(m1-32~Kxm(411CF7dDz%26;7H0ali@geI%e&5xkxRoN13B%o%UsU zsxX8s>BGH&TM-D~-BF2mk@8b14>m3DT(?>QtGM@6gNkCIZY}No-GZkQ_M$IZyylI0 z%DfWS_u0j4u9nJmy9Lo~=y>1F=q+>Z*?F$kTxPdDUjT*0kJA7;;6Y z+wx+_^+2SN^)M&J39qS;hvk(YpdorV>L0a1vsYGAC>p1ZF{9R z!TWOl2-Ae1d|=DI+_dK-6orAAaypieomVQ?dx2fO=CQuEFg3cb`qB0A9ivR|&xBi= zS!$LJge2>6MU!82Q#h-HPUt3`TDk5y|PHEJ9Qi5 zJd~{uq7R9Y`P+Yg)}@>@Z%7VmB{VKSk2d_8;nv)U{;7KG=SL|t@Pad4G9#I4E{ zWpJ*A;jt)jnNg2@doowX8)^~elj-|DcLa#7eCVEQ#B{d?*KRMf?B3qaIed0yvC1Yb zpA%a*`^X_R+|WLJhxOiN2U+{-wNJ5z4D`nxxA;mP;^bB{vLbh0(T!+d>P;fr?iYGL zj5+Fa!|6B=Y*VR-CkQrnaWF&;S}{?-9KGBSz-rJvd-LgqTl9}nU0EjXEfKpXDNptV z5C_qH=E=V!wAp4|Ra~#zSe7%wzsJu1!O3+o)S~=O=*x~vF zQ}Qcy{?|ozW8JLp zBjlS!m?bB-ZS8Tf>JL-lTcF!y(O6iVI;VDr%jR(LXBj~<%VrVMiO3x`DsiI$_FW<} zdtOqM%Dvo^)ytYT)8i0Z#>KvuJEJba;N9YRP1kVQHXlpQ(bP5lkn%2UN9X4>ja=c` z!FQ!cw5+1uu*G{yaVl4Z?b}wZb@|ThuAS@|G&gCxNO_JPDWJPjHOssU5uBH0kbQH| z%~;sc#l~pzc#>Ph8OoP;Or_+N45B*{N!rvlr!3C})Z7qj<#e}63IKD|qT$8yah zp*w4J)`U$}TY&ogbaXP&sY&cOOFqxQ^A+CaoT$N2x-CR6%1;Jh<~I2a9hkOye~_u< z5m|~BqZBvSv+j6%rAro?I+ny$2PDP%yiDGy$BU+3nu)!TvZLXF6kTHFEb{?k#0|IR zy*jK;cXySZp=k|H>*6Ni5-=J04m3Y_P8d+l7dh@AdghK6zjL%&;E5BM1Q*J&H z*D0IyVnqFtl?Yk1o*J5SXIRpVR^hsgoE53gty%R`=PgdSHLlJz zm)9L-{?x7XbFF8?!2wX{9Q%52bP}KL`Oeh7 z2NylHmactvHRI7OMVb3Nxx(Ch@dK&KLGP&8aN|7(cJ*NfbJT5!D)k^~qC7U2nrw+V zUj@dw+!Z({=Ala)x3$+rkXhX7(w-9oR<9B-n+rXTZ6m3c1>(X}pkQwq(*nHuXVwAt zeKlRrbUdP;q9hIxeU+c4u|`yYG06CgSRL%it&Rl~s^sUUASN;{UyM~8veH^=WvV`Q zI4{U!`dNQsTKSQyeXMmCov?3m&c^0*dLQ#mGg=UO`7&?Q!DK6E&o-bHaIu)FMJe7m}J!S+o=uZ#FXsQ>S(b zcwDirem+(8)>!Su9o9-Nb6R)V!pZZ^?um(d=9kK@RM28~p1g4`@LdKFG$mtU2e(0I zkDDJLnCaxUOK;g|1NbwWOS>0{ z&GG1h{>?tS_wcaPcRzhwuOt$g?JTAzsB&bjn58zXKXomRCzjy@3!jR%Vyjc+#drF& z115dPdtxb{9Omh%q#v)Vx~=j;WIS$vnSWK7WSON*-sVqWC&g)fdgww`PsG?zb1q)D zdcYAfu@aIpG!c-4R6|x6dKDH4d{i?`oFHZ95KuQ$s(PBGYFhXvvHsR+E`9P~u8P`n zyMTk0Za%ahjCUS1>FrdS=ZK`+S258qK7V8-ZrSgp&l7;*y|mlbLo^lZi7#sU9|Il6 z(J98uEEagE#AuHs-RhCg1?@5e*cOb8Io4^kMiCrxyomOU=y0m{0!@3n z(|}+N#tG@b3-U#>)mIKRmCR+bYx*v=(zXZq51;c$E#VQWq!MSPvwL!H#8&e*#kh`- z`^0AiNkwB<{XtE42T4@D2MqT+yx3u+q$HU1vAkJUQtZ)4%nYG|YVqu8qK}qT4<)s% z1S?lqC~6fRW4IC#_! zIk%^Xno6EPl0Jiige%IkXX5Qu)GqLUgaFP3d?$3qzfOC}lyQZF7TX;lh7B2*n6HxW zR+}+aBTog}v3&Dr-f@|(CXtC1=QLmv@+kcUMpdQiPCZKZRI{epGa})8@_LY|*3ywr zgD(yzKFpeGU#StRoEMa*?*=g}b&<}P5ur5Ufq-Icb2oM$>N@J)NvR=qDbC7(9V^E_ zjj!qZ6zVM_H}|yqMk+aby9TjJr*Or!ZatelJ;$;@xiG;QSKE6vPwVEOpd+p9k~Byn zdsGaao^8}35$Jgm!XuXrTE4Z%@aNI)WRa)n9s*-(Q7`zntt}6yPRjMJxCJO;VimN! z&!wt`kOrTAqqr8p<|*;6Az|s9RrapcCugT?&)srx^KiRe*D7SSX-#?gEoq3#Ni)-V zJ+H$fm}!Rd9Lx(lZnu!+GOhu~GLqi(_F}EmC`dX`xGr9O?VoI!{F=}QY~3QbC#NbB?Ns^85+?@)TsTnMs!S)hzqa{q-V znf>O}z$}B*(1UT>W&b8rTWiM!-XS+Fsa>L?qvF+(&q46%9rZT)IxpS;ngGv)aSvNj_V1G=g3H>k#q~5M(aKUr_`A-bp40~Xs*c_3tQt-{esv6HHL*5~X;{ryx69BL+vl7u(7 z#6`QOXX~F??-g^{nF8Z9-62~8Rh5L94;p*QqREiuANpl-%$#aPZcfuX=hn1yD!e!b+PEp?w1n<3s5}?R&cAIM!5GlBSUlkjKxcZB<$- zXzxN@&DTmQ8Wbi5q09SuH0)ghLFdGo6Sqy)h4!DL^f$zyM>yjA?*>S|30`vZI=z6Z z0?EXn;{gPibyP#OO$6(8;T%pa{!OXoccVK6Y0oOhk=hdx(w-^T>5cKX!4B5CX?!&SY=G`$vr(c0`{(?7=PyA;9&f?xze=y0uMHa{xn<0XZm@~P^RE2 zWwDz2EsjlDjC;&vUUZ>CDLrVi@*}*PD(FMBb2kaGA5gIxQg)!}a+OF?q*fR)2XPcF z6J6sGpchopym`wnr>~DijiRW4+S51 z?3I`rvVIgX^%;?!Y)|BRu!%W1>pp#2M+gDoNWcz1FHC=+y9oiN7^PNibNP16{v}7I zZH({ds!^XjX02Qu0-Fz$7ZOVlr!^^+zMYyBeU@$sPMIsYi@JqD^p&$h-G;I^N*qlu zJnlkC(uf5{d|KISj94acd{z>h@QFENtkGX2&f)1HZBTU6AFr?Iew36moH@!zttFus zR46fzb{9lsJqK~a<9&v*w>mQeI0ruz^>WluRU+nnON=s^X%$|GToxW1uuf$N;tMJ3 zoV>h=HF!V4^Zi~5FjS{=tuw$g&Jq)iYEX4uBpdUJYMB2>sGUUS$-eVhNm@egr?hQe z>3MOSA<5!*l&hOXiQmkW7v``$nH8ej{>19C$5Qs??PY4mJLs%b_OEznUES}qu=$~O z#)Ip}3f4+fTv-L9M7~r7^v*6HsGq$}UHl;c+j@PhX?AzwI@vgC*53QyVjFqd;AWKWw)KPB=F1| zc>Lx{s2K6glkK}D_S4e+lkWxuuOK;6#qKz2|Hll$U* zWwiS<197P*SxQpRBUyvBLz10Ks1=%e{frFV&xSY=Gj`Nm!H$f2xQNf|l^8u{CKHGR zsk<~miBNtJcP$sC?p1CP6|TzJ?N$3&^amBAL0 zY&}!X6C2HOMxiH{x!U56W$I8kp|%#ilvC#e0u3AIqMMQ1KZWcES^Cm}x7W0`4(>Wz zTaoe>b=!uHv4Q_SR&+UVN_SB-`DHw1Nb-kih-;4B!0~39{l`_ugl8}Z2hw)*!z1p` zjK!rA9mwqlXFu}T%het{=G8awSQ@E*C6w9XP{5*$krpGniayos+5LTVJF?a8CefY* zH7LVNK-E>y$g%;;%J5Eq}&ye$Bu}WLZqOC1$`qOam)>7X>dq4I!hk2>q=u? zfRsz36;;C-lpV2Gg+Z~sX(H32vv(5qmFHH*m;6=-2xgDCD>#wm8Y$;lj4YQ-4V0Kip?1S8~!c_B>`;&4K%VTGWx(bkX z5*Do&xoqPH_%{phAy$Ri2@slPkWL|(E(d`MpO#9VK};`w;=0xyH);;7u+z~fOY zPIA{ARv6D{9ug1X6pFuNZ*^#B*}>E^=}1L|XM(ug7};LyL#DMgRN33u8!EQGt?|~l zwM)?2-eL*U>gm0_-CHatbsq>St`ji@A6Gf`#OZ^6l_arhbEI&?K)0LM7NwW&CPBmU z4#`ene0qI7mM65y`vh82%-v71Fb3|LXG?y;eIubxS$pyHr>GL?TU@oXm${UVgm9|X zgLf)X& z@}ce-j)+vs6D-c5qivxZ9qM*7`~)&6$@Z9qH(q|YyLs5FLXlanTSw*E{M=quhDi)sDs+k5n41U) zyPg=5MR8H7dZJk+;NBjB452#o1n-MY$~lko3Yh)OjU@b1oJI(=tHsST&b+ z6T9qxzTeG%HsV$MC)3P|;eq-=kf-GE@b&e}(%*{;ygrSTV~zxyxrMgqHbNl8dSR!U zbRO`vcC*a2^V^c!b(m_{p^@D-$dPc{)r(Pc861sSZL1yk# z-5z2|n-y8RrlBr-%)MM%#_Ml5%j6^~rWX#_JwDc9wmp+ZdA;n^lLDL+BM$nHm0YNB z0<}UNnE{@@p?q44l)DjbKJ#pqQSK(sKiGx{5kL1)qEl?$Tq1pqE5ooPJBSNJR|F1< zE}q?7)*`pdJ=y&HslGW|^-(oXwxYP#9?56;f`&sl_YN3phcRnj0OQP*8;Y2qx+?cO zdpF7L^3|a(ADm{D0QGE)K{6CY)5&M{1kotOlpoQ`+oP!c+1;cPS=XAMrmtq?=762= zz7p|5UL1Q^yv@;Ed|qTCn2Sy6L;ngjq|UTP2GKcV z#8kxRY>A@JN5q+lzb4BqY69}zO?IliMR!$tZ<>0XH ze9!A>vxS6FuQd=#<+GG8nH!vnu979Xx|h+=VXuAb6*bvGzE-3n6LX-QJi#iqb?ZoT zGKHe{Yjs{5qca{)1HhTB#1qtR0*$swY)1cOcE5Ddz2bsAk<1g=R=^?!RZ15)suH98 z2v%9_+~hl~30Z?1Zyjjb_eK^ZbtoP!gM>;w)y%#+PInU`#d2-ml6hDgvw!_%m11=_ zm06kDUc%%XwPfW42i}CRrtTwEP`KH1vgdGW2q#qs2$$}!VF~v20%gkn4pUX~)%%Hq z6-4w)fl}SM$_v((UF)8CBW>zR==?Uz6+#ozO&YU)ZQHMoznvDIWxxEIM!`HbKSxIz zXu9};F%Z}dE257iG5Q=gcs2B{`_N8@4kYU3dpf~Fz5|~;Bt|GIbJJelIC`cxG|;2J zEH=fp%$iz(G{ob?N^BYkaMl4oX3%|Q)c=~_q3pHLnj`K5PMO&%6w*W)b6ZhE6ZhnA z1M@tsMdNh|aN4Ac=Gil|Jg4Vef_wec+TB3C1wZZEyki5%(AOt2Zc&!@IUYDUR@YC0 zk{&j-pSx_mD+I-je289y60MoUCjEX^I9}5AcAY%*ozA+;WrD4I#JocVQpGYBku1SG41^L%Vy~tT4FxFpDs$V0XAiu2dC7;AO@slYr33F;+OCAqmCnq%I~!+?tRnH$w6`pQU2QChEu&!#4(5&? z1P75=shiJgz{+UPYv($1w6+h2)~&woBq^#KB79uN(nR6u|HfY?kZUJ7DJf7!Ysh@9 zSAO@w0HI{Za^Ds1Uo~^)MIz>ThYM6u>KgKj+V35OU@EK`Ni0b$x&CDJIOWbH z$-bm8C$}RedeIEh;(kP|(+OFIGv(!Hdspw3wTXZ_9sWkG zV|$$Lv%R35F)11#K@rR=-R|2nLt#hGLKB{a7DZZmS$G{zL0@gYF;3B$KNO>JFtWYe zG53k5BLSA)vmjC0+*=$>uJlmrCnly+-N9q4X3KRKJAtz_rrRlw-71=S-M^;8m^s^I zRSi^!J3cLt?O1qXX7n&(zR%=a%P(K#mHU5X1VB$az=gor95 zxJpz(b=DsIaS1QspxX+Jz)-RLQ=NPBu8>1J@H4JBWZ0n2C(pc~OA zr?iF314cj5e=A@I)_F4<@pnX;UT5DwX(0|;g4$w z5(p00vcl64*)QPB}Y-pL82nxy&Bu|zB2n2b*Sh{QAVKJ!J z{DUG<&**Q`A?y6((b)n3$2$i9J#ZI#7h%)X{Hnd#-945e$a^UFLgA&-7H#(pMiupJ z{(CyW;nyfrV_kRTMsE4QZs7M&#S?xDNVdCaQ;xbJDB=jqJUX2+5NxX)DBL&rShOZR zOZWZ_lSggo{R(Z|*wYMCOXBOh2>&baM@(Yrr0K)xh9Ch{WUX@Vz$3N2)x)~Bf8*>2 zejoTtrdaU9R1*Lg5rHD`55O1D%Y=a$%@8Cos&&cnhB(m>BoG{|4HoVmSZ|i4tM~bjj|V(}diEZm4V8StVrmKi z96iDE3UE8{RUp^yW=#o>G6V_0$FF^N@Uf~|^Pjqp#3V5U{2gkUZ&C|*wt}fK33xK< zlY<9=KS9mDehFCG?xuA#M;U^;jU=y>w&WiedNz)Z{_)s?D&F~BpgffWe}w7rh1cf= zz5#p&b*N(K2VqPn4%7ysS+7vY`K9um1M9?)pVxNZlT$+tGd~A>i)j-4FkJ)yjaYDcu=??gP{Y2dF>e*Wro3j7hK@3*c*0AMj{JLczsS?z9C zCm|5NL<*+!bI=@zfqw!19@q^`t6u+TrvDeOgunr=1O5=WoT)zfsc988RImSKRIgu| zu7N+o^cMg)IveN%ehsxpdqKOKrRgYk0$)S*{QH1u+v^|A%n*PPln|J}#i-(pHvoxt zH%rr2@~9d9uK+JFg-t!pFtY?;1SJG1;6~s-padb(?q+Ewl`3jh|Enkg$j_v}A7N$+ z031Io_&mzlwF12!Hr?@ow}Gz%_cDXh{n41Y0x*IS0s|HNx1)?i3()swx*<`=aNPsk zfpYbAY^kovLOYXtji{Y^KLFlA2mZKd0caE@1Ohk}xE>`0mjdZ_ zH*JQK1)fC({zEA1?u?(@YZNU2L8&hvrh!Xpp2_*&@bY?IfJ}`g^`kw=j zqH6CAFvEQMlYth1R#F#(G;lg_6>ueRE-(j(w!4WPLK!%SI#BZo)R}slP`$qn`WB)E zV6v#MBE*4tD5KHE!>`kTZf1@klz~B%C;$23*Gs@&RBcB+i$6(d0caQX?4S!J2Hq@Dod6XFFVDDhG z08B;d^9eR88;GF-+hQ7d?bqTbYm z{L5n_vak79 z?*lNWssfM$fE)ni0RB0Lo=pw_+5&*KFb8engF3-?d&q%R4Sy6{jbQYDzJULD1$(+| zgd=?>mIGZ5^R0vL+-+&|+aZHh>jTB$=7ADmMO_9gsVl&*IGFy=IIKcj_y!qxz`>%5 zH;x5a{Kr-N`_r5l(h<%KZxGIm>7-5!8Rm}kZ<`$Gve)bn<^ns~JODWW$N^Z>Y;(Br z6Nl<8d-xp(gJ1eZ54eEwi%ol2gB)5k2)^Hd9LV5|Q2Rgj3)e#_h`Waqv0Ru^Paqjz zWk3dtkO8<&p9vi4vH;`&{*uG~9rm!V8dx0o2(CiEFr}#5;TK=G^x-c#?CIa+umt_$ zEo336wtEaqefR&3PTo5lkGOaE1^!*;#LI4nlDk})Qh*Cn8o0yw3P29vXAU{w)^F^= z68Z(yNMOrU1rqJLfbHQ=P#ZA%@OuueztG7Id)VgC19j2_vLJx-zc=i^IKzuI7U9kM zjKq^A&J!}2xqCPPxHBgK$N@l`_;(zjK3MH90dD-&V5oHtB-wWYb4s*MV*KKF_FxF( zg#pP<4xJnE0?nZhz?g})hgmp$V9X$edifVC`e$!lp)NuAvPYl?l@D7~xHoIevKLD% z@ML)oAP4Y|IMAd0Vy8V6fSdaZz~l3EVDZN)80nk`o?><2I^|}(_-`Cw?ttMy*uM

BqalcZqfaUQkA%fS2CMhkBY_WFG=R1MpiTTc4(s}W;b24k z4x~A@fXzo$(lD@Ps95(4i~W@waTCqq_xkWVd)VNxdV#P%9_A4H;P}PL{{E$hoI!|3 zoR3Hza6AqRJoZ^&-5%n1*u%?pzd&y41H+&7;X_>C=JLVf+zN=h z)(kB7mB5&Z(TANJ%>E?@9ilD(Ijq4vDhdu#IR1XYJC1nF9e{YkV zE|WEZZF^|n$R|5tF4?sV^GR93Ag`rFn1sb;(7oe z2k?+H_}_70-qcBG4;vhu>0g0B@q92f@Z+cTqo0&V_JUjV=-dGgv`+5iuy5Nhezu3- zaM-)WVHVaGMp`5T2srJ`Xs&;P5;i%Ip0NvH*|+fE++D*T3W733YPIFLrQ1 z(!T_81{Gj=er0|AYpZKuZ4HBB9<>aj4eEf+fp;*EDu8)Z5zHn30|(L__JHBg1#1ie zI1sST_$`B|<8BBz_C_ANn-+e|13(S{asWSZ_>DdI9}V5s2ezm!orE#-Cl0QRiQt{* zXUu}(&+e)*&|g2X{k&!pcpNVRb~GFLB+VZ=lw)*q-5$`jMK!?KL+wT$g|P=%Yr-09 z2M3b2H?Y&^H53Z5HQfO*u?`fF%gD16ib9O==yBsyNa+sLc`0S8KSyx8DCj^==_HMck{!Fto& zkfap>>+e4?h!ePtcrM_`7%SlP1+oB;1ArXB7Ke3v@Y-Pyei*;lX%8IR`rv=$&-UQO z@(cv&6wQn(*9a{tuT9v?oHWTI!S-^fhfA|{?>v40# z9)8cE5!wVZ96zl;LC_8n%Ik%Q7rf>4;-te`oPZ;UJ>dlYDTl{seOTx45TlbD9ANGM zc5?7xi3E`fufgmnM*C()et?iOZ-FaAJa{OU2?oDz`$2o@2=L&^2Dj-7b!HUEMGy5>g5&A;W)dWHNmGaT#dPZhO5Bnw+1UW(kXfb%Nk03ZhdZQ?f^ zTz>BtJm`4g2iEQ37aaWApMZ*>n)UUee>9}E10VJnm`gr`xnvyhKAHe(6Sn;T9V5e( z3&9=wR9I*I4F{-`7<<^s;XSZ`I{62CSc7$zH5>@ok3qaTWriLkX`<#usiM~b zu@y4yj2F4;QQ#u%Hk?`>--V8F(tvA`X#J2Q(->&YRevO!+dhfFa80C>lk~O zhV`a29P8`PkT7z4BW?^J2k=_#D)?6%wsaEO!(VWC%;^h~wV#9esrmJFO%G0k82LC5 zaOClpKA<_=XNv#OJQb8m2fy@yS|Snt7X>I3XOVB#ja z7xf#z*k9=ZYYarTl-}MfNrU{i5{3YB0KejpB5JY2FRXvkhb_O@!6E7w9G-G{fqcgd zum*Jv{phdh10g4$fQK9~uZHm=@F-d*L)PPkFKZZxR!jvGU8wbA(LX+?egi%n3E(a> zth1OC{{shfUX9KtH+z%i#ZW6rbES27a-?+oAP0~osSo}OhZiU9!QXKRq!aU>vAP0~w`7b#LZ`;Gq9Gtgw5^WDLf}Wtav~6qsrT22d z6HcEEo!sESxq}0=hamn4(3zwb+;~+#j1?nUwUU;yeIiPDSXb)SxD5wt`e&Gb; z=CAx>qer_4`?Q9Ivg$qsvYH@YMhm=?z5ssaU;r|={KDi<`tU2ih=*}=lSAwYOdb_; z+!-XF^#fDg80}k{TLc+KaUg^nU0?WMYAkfT;M%OSFdPDp1c4Xo$pFdGJqVw zf8uZo9WSoIn2F(#EMx&v#B4yhV+HNyPoo`!T3FkiF4== zy*#%7rh75AkAB|xybL_!$K+Ab{4OBsm@9ZL;R{9^2f@rB3Z!a9K?dGiei6*!4^ng! z!OZXs`txzkhc&&q=e`Lm$xFF9!c>=!Uz zyp_c0!<$ovAXCN+d~}ZhsE+>i&-l9^=zhn0w`35^d_NBZuMxlIr#q4FLv%-Bret}pPKrlBo1nnB*CvZl>f#r3S`tk zb6_aE&)fH8b$J;K6<2}sD=r{Y+z_DsBLBi|=o8z$?tzleAWgywycD*Cbr!nDvIa?_ zw?J>fc28`5u}~|~3>Ig$b!2sU1?CX#;I)1%tSw;fu&$H-8+p`bk9M}aUnSP4xh{pO@XR_To5M?wSedD zj^5-({s4P0n0)e&9R9LrKm0}YS9Hiu$4CD7s@^pgz?zAFX!XZ2w& ziOwf4t#inhGX}j`rC@D!71X$df>$EOFlNGd0pn(h@HLQs`94^jnq6P=kC7=b(XwqX zt4qtEx9BT)tM?T49_`N@wtM!Q_V5P|Z0mbj4PVvxUwu7) z0(?1t68w?F$3ObTIgCA2z<5!P=5R&ld733+J!5Rx4H!p(>bj^Lr7_>dl z-s0C9pAS+*Z-Do=Bf&cZKd6(|AmOCVMlTB1S+FTc<^X zXTDjT7yOokGFl(Rz`8xGbCAZwi%olwKLa{~pdTz@df4mtF)I%gpMx<%RvlpSC~c4{ z1@mg?7uoB6fvz@m>r_6}k%j!W_I5g8e9{lbD_yH+n^WTOXz&a1D( z`r^h84(Q(Gc8?Zq58FMt&7M8RFMi7bvzIjh>x*OGwAqO}w2tJpYaIoU1K8%k2b$D& z_{HCF_;6MVe7Ru;7KVqm7|e8h16BGa0Iie7r_nXm1_xQKbq=|b+Mw!&8(7}Rk)}KQ zL7u7=cq4|%t6@*>f5^doT_=CDhf@Uog0%AjD*_JJPHi>-S%BYhfI12FK>)yb@smA# z+UXYxQlL9D7_7lOVDmAU{sxpQsDRSbu)aXY%^e)_q|`x^e<&uQhbIh|)_@$@%RlRb zma;xV>@C~ zL5}3b9r^(4icOtd?@j(o4qH9BO~2UK(>vxd4dbR1ti2JvI!tgd?uBz<&&5Na`{H4+ z#X)O_J^WJ+ilU&|#uO}$Zs)8sEiK@qo-U|3a|RPP(Hx*XlqqY0iP{bAgSp)o_WhSN4E7rgL@-h@WI^SXAbo$f}la?G(dfPzh&FY)6<~E z#Q~sovJ%G4iZim{tD_@WoyX+&Gi@E9Lj4l-3)PKY6gpm@brRi+`n5g$TMipN+7=iu znBmyk{~OXffEd=NM!@;@kRHt%WC8vghvQrJ@LBl;Xm`82@5fwHs8>-tcJQx?%S>PcSl7+pE$jdSEF?j-ILqU$sHUpy~#~`*vVn%-o>_Gr0m?&6TbNs?rk1| z0c>+PxPt@JPx|mX4lu6_O=9-+V6C|b?ZFJa--+Ju z{nca4fE)qG-Z4Y+lu-i;06Bnv#(@>)4!^dCrt>_Y8)*WTCo$i7KreW@t_rj{n1Q8@ z7_m4u0vfLBLBEjP(SzCKpaAQvO%6MIv>3nmojv@tmxbZ*8s?HDuuuD|rLVs>ahU`$ zVMvM|$KYrmHzWtY;xKUOclNN80|zFbT-QlnFq-sqE6-V5UIH^;KEk>g(=VHC`U2{; z<-tdoKh(fpRJDTizu>U3ckz=>Zu`XshfcV+c>=xH3v;R8t{?rzpenEP72*Vk3VJC++ zb#~By*BGo!Z-2*SlfhtwA3(faa$+1IB0W#iC5eCK6?GD3qL@&>s8R8a%`i%*z8699S7L6 zN9R%NJ^NqjgXSvCp(0=|Ndouv{?VHMc+iaTZUh{ZaKu0stB?cOAdH(B zd!XOQqtG0fVNQJ*bZQ+2Jy%7+e9NcJTMw4L_k)gWXF=0>UYJLrMagi zdpN5_a%#_E%}rG(tMSKW{j2A*Cinh~x$34hl8gc*_4n#Zbfowx3|HdBBexb_(P?_QD>z~Sw#pF?%=y-vy zH8(k+dk_Db!wl@nc|o5bh5gB2E#*JI_TvgJ;>Q&nJUHvmUBT^y9KcT;E)xS7Kf$=+ zP7eFl^9Pt$uji6mesPf=3|I(*#r8Td7VZQ(HJR6Y59|3Ph6~F2uqWEUX`B% zJm}VD!svrGI&QLW*~35N0QYdjG9Uv1xPC0SkMmbc_#eNwcohqQ<^Tr?99JQSuaLvq zzvqC~2RhJyi2At~{mI|i!y4>GHNoDbd7X*~DU6+ndKHoX@%sMh z1s1MiBbKfqAP4M2kb^7a&;~iIVmR#nNgsY`4^SsDegQe?qVq{~ykG>qf6rkR_GsIo zHn_uB!3-H-!TFzl?Em%S7EG}ai&qiYkOMR1U=2BxLk=^4;32 zC|SGh1x*grgaq2p^g5p+yDQ4KwCgS zyTF3BKn59{fDDXbtavnSygL_VOxO(b>QTsHe&RCe3XB&kV+IuSBL-As-}U!3!znzrB6{e0%+X zZuE=~KrwpWwSM^e3FxN?1m-k&BD^a4A-*{;V4gFqUj|-ibMD5xj=wnEdo6V@Zh9t zop=R-xLPhRBdPAtHXdmo!>nZo#7z$ubS_U6W*4;T(azhIMGoHRxcdHmPB#w0y$7Uc zn4YhkVZ=Sd_?4Qdh1$sI?vePM`OaI-?M{vbT4uGAByS(`Ke{3pr*&(7CBw3-ae3rp z!H|=3uF1W!V+qgupD-c#6wb;d;fzSq;$CzWh>@+|7cA$77qd6>d<;<{sm88E1U9zo zq0cS~^{Q?JysX25yqwjDdi*x7dR*K4BUs(85+qlni(i}Juo9h^E4JV|{!P}a!sCMi zhn4o&i`n>|g+LRDQ@r{yyKIlVmI>Z#Aa=Dtf=-271)E0ZA*J?*5D^6CV8iS6aV!>Qusr}^h>gr=CWrP(jVwBrh3TM{hc zRSUSK5eRb=I3aTk;`7!XJT0RrCi1>HBgk^k#~_$|uJ<$+8^J;R6e&A52^It6M*{vK z8D0xn3+d5RCNc)%!*Xu>-G*f6hz*o?KQ>bHOJ6fJ?(qhkeamCeLf4VQ>SsiVTp1~@eZrS zUf=)GXLoh0V`Y9Q{pY*q_bMaA&UQaZUkGF};In!XW9j>_=n~^_NBF+*1+(Xk>aiC! z@$V}-i>!FFFH1!4cOZ3;sple~xKB_Q(?5qFWlHjL`AOHs;seF9#Rp>Bl$NY@ zW-HB@k*sn@13-D5$E0ZNYl9H?32Dc(-A`izy+0_aeVaZ3EisN!$VwhueN1Byh2sR*fCgy;fp^ z%P3sWJxt8r@3z;?9$O2GZPy7UzfLYxkc-V33h`YG)MT@`-Sn!+-S3EbyK>A;_&ir* z9>a5_`EDG*Cvc&WfD}TpTO{+o0)tb6Htc@LPNE)%2Hs&e(dIkc~ zJw!P+ zPrFvi%n5X7?^(qwJ!k)lyZGo{CHNP$5Bo0+-R&fbIP#gGn~@or@@e0$4{yjmaPcLC zE_|o%!&Y`j>U1|BB2oyOYO4#FHK4;!I2eUMX}`2a66@KqYF)Yon~%%`G^yjD>vVi=%(*Bk6qehAZ9bkcksUT2StQ+4K-fOXF~oo ziUoq#lpUnvO`0F?-%6Jspg@iL2<3S*+)2xjDQ<9!g)v=Dl&C42MXY zW$H*bO$_VH5+!L_rzX;_c&_BUfo0UwkQ+Xh<2AY*hq-;C)B4z_Y}{PRyz}qVER`2j z-h5pnc?wHgX-I>NWi`P~!;2D?@t~UjvBo&lHI92jCzZ-A>4gM+qKUicjq=z5%do|s zUD%?xx=;sGtm~B!`=%L)o%iJ^)V~Qmw03=WT%Vu`Q;)u7azX}AY0*j!L)p@*qg{El zN{4;#ke-`-=0If#4w7q1IdKvD(9-Z7HY?uys)FTXLDY*iZnUmWIClcC2CqojPO(Tv z_1F0bX!w>~dp{Y3M{BO8$k&2X*wze|u$sa!*)K{Xe08D$*V zXD*D}`H`tL%2iSqQZdKJ&CMInz9U>L^Ei6zE1ysT3FOz@lu@?LHOom_bQo6E=tfx5bi?ChM^ zi0f4>UcX{HSa5bhWqQHPLV%hOca_|VW_W+?yOjz%Il)rtB0-#=e?Pf*#J(QfoyG~Fw{(NShJl!t9@VSAJ5yV|&l;B<0a{jeKF z=aqQ;jB`=h@E1ns1@~*3JVYk51NVQBA3gUR(K~dcQ0VK!Ofl|!QgbimTh6A!csSg6 z1r3bOs>S^N+4>7#6&gx1K345!w4GtcX2I)Vw91QNyYeU*(c7oLSB!Srv_ZXA{8@y3 zw5nNqG+nA>*fnBxuFim51?Nwa&Yz^6t%pP0Zyp|=mk_%lu_!V)e3&=k8shyYW}H}Q zbwX#F**W1v%r&mg*X&)kd5bh@fNfp)X#D=wS zRyo{8VsAGLiwjv>O&fJ*##$uAIXQD$u;0VBD#L9&Sde+xyz9x+JsfT4I0Yv}ktIS4 zwoWT8lb=iZWR6fQun%YBT)KkTuRB$)&`^;M17l486EY$RgSW-=IAY%9oVTgRjY32y z3^T*3FH}CUskorwxa`naN?PKYClcbGVRBE|#k(z_WNDcIl4lDS8d=+sz#z zI9FD2RIF#%FNYz_-56r>rPWP@r-u~$awVN5RW$}r;^oYJxtPo`O)~Qamqq=te2Un^ zJQ8P~LaH+>D;_N?^!n4iB41FpZtcXg`R|c;eY+N06dKrGeH?)wF`n!!5k-X{Lu0$l z++m*bO3U6LhB9+%i$*dw;kE~R7>$eVCR2J^s9ppt-A39xc=8T$ZJ*RisT3;Mr1g7? zoR4iudEFOS;w3W!H$c?-L=5zYD9iRdJvVNnwv^#fO8F{C={< zCtb~D75l!}iydpQtv;Gzjm=(QMvW`ob2fIr5cknw^|aEf9TW`5eDuG5;<#|;1#4y_ z{z6dAXyQw2v3wRuL)p{v(@r)c4i6~A0|L!^b@(o-u1P-#CvNOEnRIZ7H#6bLxPi@{ zXGSeaRWFNjQcyL9 zUqmUYU2rlM(u&riJJmXLeqSAls+!qh;!dU0uTL!H$~YepL#0~0ja{lM=8C1s?;X!D zCl5J6ArZVnU5xL_uxZ} zh!i%nu(N$6?!+c0u+U#@ujsso4SD{wzY_MaIAI58atTM*TBX?)ty`f~6cUU6(m02( zWE`{E^~qnDu}%-=%BgrO9N^*GAWthMM>0by4@W1_^$$RDJP`wfJ=BC1th9?E0>wH6mw^ z5F0Yt(4Ti?L^73g3ZNS5c4^beH(kyYp`(>uJlW9whyZU=2-r8A7NiYL-yPHYzGMLr z$v$<$pi05>^9`Rs|A2C+0sWjbxgVo@s%@@m=f#&ky?H^f=-#_zR~9LuOz+8Sk3R$j zFkTHWcERG#%mFTF2eWa^axF_9ktWIJ9$Y@`P^NXgm)Yq*qjzTZ|m+{qKH0tr(4+N zmW*>dvCdu;6A20xYqZbrD#JlV{<%x%_vBmR9da`(B8izU+ZCgZrB9l}yVU-XRJiTJ z69L6#-4w19l!4i1V>6)^$Zx5Zk)rwEsT(WwPQO;NLU}#pU#Y1}>Y2@6pjfJ`Ak{8R ze(PGx7<8)le05us0IqC0MUk=1pCc9~4#a|7<$ zh1l>zZw9m192!CoBX3T(3$C%4Hd;09eoT3eX4L(oC2>UnUf$uxgUp5BJdZ~_Y$EB^ zvHff<37Ue7MI>`3I`Hoc6l#V>5il-&By{76sXqO*!7 zjlJY)1+Sx2>Os9Y_kaoBx%L`P`a0Ys(OF46Pv-NB(ksqMaU8vIdft})>=$uYW1kI@ zdG2NqU4AM{+m}OGK%i|S&5)>xa3P5xo5TTxrUKO3_#)!S*_lOOIY(bTq`~uH>N|zW z)^}-nET0#gn7L+IWYQXY%w6u*YU&h$c0E0s&UdxBWraJG5AzP{kW!>50An2o4`$ax_} zpVBVER{-m4XT2AKWw;gFh3jfkv{24R*50^WpI6+xU!=|;{Mk8#&n|XX5C`AO_gITF zV*UJ*DpFp@#@q8o&{W7~Dq=zQaA8AiFkvs-$ievYs;nJWj?BJ;rbTTc zPl| zioG-sHVbw%x!^9^xVpoI3?9c}kuBX*Ub(jm7e%occhpkkZP<+RO=OY&JJBf>9&wk~ zUK3(N6cX}2C0{&D*2u(_yoJs=?ot|jO{8Z|jy|r=aYJlI%;iHUYoRj2j$y=K z$c0-rx1`P__WL1Z(@Im5!OT#6?h@nE&o7gpQuHj<&>Z1H4dM%2KhT#@G!}1ZaQ~7M zo9^oP7g>{g*zP>#%?f^@jD_F!S&3`l`Q3~^7;A7> zBVtbYbW!;%++2yJUz@u6TF-~UC2E?y=iNElIH#FYABID!NG2wXyWHdtsZ}>c8l|lv zRhaCfYso9&KdWkGRh7+5vY(z9GNof<`VJ= zT#{P(-bJRV+gr)^RlW9-&ZlH=n>F<(o8#hF=h{YWE+j@|DRx?d3|E(K+G>QKu!lxOdb>5=|+bAX3z zwqW_=UKdUlYC?T=ttqYK9pDGvSWYM+VHCDO=q`< z4+%u<1M^BRzDtaF6ho<$EOgd6kC*Pv!zRbz(e#)C@&y}JuGN;wEs0UjIKq-R=LPshZUx#@ z7T|}3@bZ1J^}DB-HDRSiFm{zE?tK-N;o=z$1gei3vY!knXO)^$a()@0MW~CILN&OI zMCR}vS>TjV$i3LDCSjAW5_J6>t<0A;^60l&7v#+XAAXJ(png!STw?Xoh)A4@Sp)cG zQJxC2$lCv?oY!5_Vba4tSQ8Y^O4YK4 z#J?vG3qLzN8eT4OeVNe?Tk(F&oa7Uvt;fR`3_*R7o;}#&Lde|lBY6e!Z3$dRL+Zxj z1R@7=!R~tgH)kcuBxdF!Z&sv{&sszYTyh|)NTp}Mr|;rEdgS?QlcKm!;R)`;I(KRX zpN09>rp}1c2Z}Y;F)&P)k-nWf?8)x*!~ZyMx3jd0hKZ}kxQW4)Pl_|Q8;r#VOBu!G zqL7(`b)i4_chQT}b&>cb1|-}c!Dps+?#;{-Z6~Cp!IF8bhvOVPTq~aM*swz1MTTcn z*2gyImz7kSAJ5oaloy)3$5P8_@|$=`ATldDJ^jqAo(rw=Rf5?$rJFG}j|N#y+wT)g zYrjc!cYSt-6WeM)F~c$My2XX%&(rowQ>HC_?S{tLj`*t5$pLs)=gyQBVLO(6mbgNz z|H`Gs`VyO*dOC@`^QW%2C+C>)>jqGU0=J#b#R$&UyP9)f?@y%a>>N2xrHP!LQOdeb z%goqX5Q*g%=+T;9bBWHNs@M9gv}g8;(6@GznR|{eCngHaZk`fraU4~5s;2i?dj06F zLU15;{DGAjwaHnz%X{u4C_Ri^M^8C6a2~&te(H_R%H@Ihn{yb%_&YMP(7G&2$p6HOP~M>9oj*+YOxb87=g!4TDq(i(G=WimAfMN4bcZ((+=3 zE}G3V;=f~E?D@8Q;DFCzJ;Eo-m63VxsTu5%0y)o~-OFlv)biONI^T>;w6JWY_485H z_-rL}n*#&;%vfH^*Xzi>9ph2TEel%Bi9%cy&k-NFq@gS~mn>y}Pf?5gMg?1RK$>^p zuHKia0g}7Y-o1Wkr0JB`#Y3)&{NbzA*}+fVK4oM%t4T+^>&GNRLXk@~YCiZWzIKah zRJ!eVeAIYTD+x_+oGcUa;*#^TVj*njrC^4gF=zAkVkid4oL0ctrxH8Tcqi{JBZ%Gfc&?V&l6FNO zbYAkY)bS56#KB2(FlDYxMcpcjHY+V4v7LUu)YC%i-0=`*R_#B?v=Yv>w^b*;BY1x)N75WNZ`Y+qHzT|i5v1BZ_J0N}TH;rrz{Ule_n&8eJXLz;)Gyv8rqldaX(d32$C^ zJc(BnnXSi@u8X8N(6@4+#)+yeRFAq@`kbe^c30O$!+f?>k>raw>ELPm@fSswN8gc@ z?MfnYtF66x+bJ@dh?DDtc1~jHsU&AsWXEti(_=a$%KMcVss1V>@?g=~*bdvbfgPz- z^vqW2?WGrH?GNLzzxc%Dsb7&DAspY6(EXx^4Oy$kf3~uy-@-q_$}f9)RlVnF(L{e^ zv*%@})zqLMLONVe{DhV~d%u9aL*aq@czpI@NAAm789Obq;_hpiHf2`@I`pivFb(N{ow(xW9+Fm4RNgFfyQjxH9LnQ%&?x zR^_}(b27!is%Kb9ku)A7>w(R6{kY2WYF7G(P30AP}6%{QkzUk9WSaDEf>xAuDp_0&z@& zx%GAvUY`!=4o z_w`kIDyDFb(l_Kmp)5$Z&aamgul-0dPTl7ajsRAr^VR{TwYv>IFWWOEGpQDwSFgE5 zCLXDt5xjt;d@&}~z0h4a(;=rMl-{&!E^c~R+!poG<_Fr~dGb|6BImVu>b!IlTLx{zcjQ5A^OIMreee>QF zM_ZpzCmz<6k~a^Zv)rR|BO`O*$i!m0Lh#FBld$UK0V-Xa3mhde+4U|pO{x=%i(-e= zu4JkV*%+QvZ+&ZY&q|wawB@VrStGUGWxFWKc?fQvpFMgFMZAYALa4aTWCm%_|7KC0 z)?T|_OtHStmCf4Uw28m~wQT>scrG%AP}j-%YbSFMiwid|W%lir%a%k{TC-A*tF7FgRsv}k#@=QB(3Jh$UbO^%1ei^~FMr)Jd(U=)Pfk@AUjcr-NTWy7LIkDDv{J|3Eo zf6Q6vNMe(~;rHHCUCAf#u2@L?;}mXK7 zni+_9k0mo{?X^xT%=cIMpfg-#Gem9QRp3?S<2_j(NXtS~aOnDV{*RrJ6MNhUpQ_7Q zQr(hY$$f__+Mp-@R_lD(otrKA6W)R6=%@_d#v}~Bk!s&<@O}AKIaVjGx(F&hjbrdg zq$^1|n^%B*98bVWf5Q+J)hkn-9BtaXb?rKLSXmX2x990^P12v!9l!MP#P#FX-x!TP z;Gk>Yr>>$wr?5EDD&>5AYUZ}F{c~&Mna)E4y5-IJ!fk^6=Yt7}Px!kCP3UA9nd#v8 zx$)nPBDp#B{85#XXaCrvK|}KBjLcT5TF#1asrz!su~)Zrg742uuGqvM9g4V67n;!6 zrxE24>s84x8t`o_{3S`Tu-k2}2r>>FC8dzyU|}7qG|Qtea$oqlMcpL5sUCb(3B~m- zY7ch=OA!YTGC3-3iq7zPfITSj12jI;~vqyp1O+rY&+T^^;p=V0Mz!g`^Uvw2;cuTY*|m%17oqGoI45pT2x@ z<@u=gLCd4qyY2+pPB6qJRkX^VCwpcjZ`bA{2^!qmqy1P1gRbpGH+6TLMl}_;T5@$?n@qUvu<$Ic zC2!w-WoL5+1wW70f;;z^18+vX%j8Ll&HQR$U7G2jwd_-)I-(t{lJBr^ps&|6vq9R~ zygjKHThcy0&?w7a!W`$)3FdPA7R>Zj-k;F=828n1ZzMPVUInXpW&7g- zGyxL};rn{o+CO@~F%Qp^G8kRilWcx&ScvyYPU{Dsl3IaYruSJYQGDu(=v5 zAdc+In?9w}xA;LvHMzlts=bWiru92*m#hkJdzp*K39-t$@(&;BY(_(d6%@GyF9yHixSnKY-PR^tE0pn>Vi8NvxiCdPh)Z`a5 zR{F7%bQC>RwA@?qAv)QmgV1;+Wtr*kCG{LnTwS?laVvq~E@78wjBqixcko0NT zdR>Z0Bqt(xdg;si>c_Cgi`!G=XuvS4NBg?JE{VyQPkPl)MYIXuQ=-m|Z|Pm+e(ZB+ zcX4X#ntVUEUpDEw2bZn^CCv6J>LNSaJeIlz73N<*^6)rG@6gILeD-DB83hENkuYAP zr|so^UJF0)!mrx+xcPrJWh*?Dbj)VzYn8m>Ksm)V$I5mc(T5D({>@8^pLpiwq{nT0 z&5?AgtjO3vTsbe+)}r=r#FFDR*9dUSPbIw}(~mS%uvr^(&m74m$rll+%ugY2q)-`n zexm9_;0Y~Dzqm>-6hPJ^6O-L%QVk8%KLiHD{`63_#%_`Bai%Sgyo8+E}9wn8d#Q8L52CP}S2eoI5Dfm>SlgjgSo^e*3&)lT;bBo-5z zN^o1yTIc?%Jf~ zoa0pUvcvd5VYz(K${mR>A;)aX)sW$B_(==N)lsR^GKWSM@cKNntVd|h9iX#yl%<}S zHcb%tZn_pBA`TM|GvO-@;fc{N0BCUJJ zPNcCB_P8GQ@QUXd;h(zHS5+^rvEnP3sOkHrP_7wr4 zCsEterv8FMAML2ec|Y3G-WMsUd_QR8^*!`iAw#9at90zut6X>Fv@ zOaVC6k+qyvJXS-Bsqkw!Sqq2p`3U9sL<_09c)MeL+q7+LQ;p6)uaPcludAt>i$54` zH-O6Ud;q_CP^HpAN=+Me8Ppo?OSN6gCw$UKy5}tayYvQ^uv19u{DK5>`ELcMbB|a= zi6XiEzM^U&VhgNbW-|70KUu`;+Y^Y>IiVNw9n|LWIj)O7HVRZO5e|#Do(-en&bb;j zRPQnx&xCZW<37}L=2l?fCmU9x>i6OAdEK$9Mo=@C%a<5$Tt{Vn2(F6ag4s_horb!m z$A_$sxH0je5wEQs)fhwzzmd=Qr5Fp9v&E4-%6j1Qux3!rF;u#enW!-a_9f5W0TLdG zX~k}j*2wfvkuiiTS>n{G`Lo*N>HCPM`7TF-C|G&KZZR^KXOYa^q`c2u z|JF35$4!1DEaDrfx*lDuogr0T)4cohA8}#TpDiQn8l%OcPSGwqPG#9$M?RxCe)+Pu z@ts;f6(Jp+PL=$HXN5!V0v7}aloO-RRW3>#uQw{c;_Ha5*wBL_6w`iKVYF`qm5MGn z1!Kbqxub>_8e6}=F>2>6%5h@rsXEj9gdv+Ae(|NLF7oz-sFGm8V@JBj>e@}+tL%jb zuTwTmR%uqJ_t)dZ zGtX0NNF%x8Q^}JP6ZuI`zKo}%FJV-hqn=5E_lNzsJL|rj)!BFt$`=P6o!bQ_g=1B8 zDEdd{Z`Id#L-nDcAe#VHdw)#QSMa9X2_5v;bvVlO?qF;B^Dx3RJ$Euv8lCDyOU|l7 zyvcMc0ls4ug%)=?FZHe5&u~1KeB{UpZ-p=)p531JGG1M%S{bnOsnckBVm*PcnK71g zWzDKxxYPLx!`oxl1`BX>M@j(0c)5!3Zrn?DJkKY`-OW2ihFDqb}tiL*p z)PnHnas7q?!{)HL`77nN1T9au~fO%Ii4!NoQ#47ILur)J6vq^cP7_+@hL)T6}!`_dORotNS|T$=Z?R-W$~>4R-*qhfU>;g|kFwKvW= zYw=#KFU%BnWQ*1}Zz*20xT_G5mvbm3$>YEVp-XvTZ>q;|zfO;kcP>R~ z=q>}Lpfjoz3ez*ull; ztLXeSJ-f!^S+cqoZth3{q1m+`KQyD0|pp&oQyiH;zOF`XokzEgV==dtF!tC!S?X}-y=G;wIX9!^}*df_8c`Osf? zCOg6Eb2OcDpL=r4BSU0wJJ;h1@sk`{Z&s}1FB}qmD$I@GBDsI<056Z_UV1I^Z~<}a z70v|%I(FY4fruF$p&pq@Ehq7Lnf=PAY#HaC=z0kudxvb{|CH)6b^d4= z3#axY$Gy)|;~rI4lS;2rFkaw2zr^RV#)(~4&}Tb&;MvWn4B9{$gSF|4I3^XlxgxAl z=e6!=T|Cg62|r4I%N6>k~8l!SCQA|(yO&|M;kbV?&2 zAkBc(NDiQc)X)sgkVAj(_pMoL{+f5+y!&pPyU*VH3^^4#z4J?X&f&s9A_qOnWRN5ec;B6UQ z++6YqEqTm+lO9U^_lXHaOq;cj{bp?EoD!QN_+wLsPQ4LSu$K&rcMZ z)S}$WizT*Sx2CWE@)g)-q^}g~yW)gH*k?3i)OBrP1(=;>l>){F1Eq$(WRC)ZsEI3 zqJEYK!3`~gYja{kx_B-$q9{Sj_?Xz^-8O{kDK&oI)HH3MCVkJC^}cEf;F2SEKp6P> zgxb8R=j_pOH|>3Hsqi(eP9c11nCywDqde*$_o@Giz62AhqU3!E+gnf$fQEo&bvHQ` zCWo(kRjADZ&tC$E{iu5kjy}Eg47$lNVIiMUgCqkz>63o$@z(mjAIzIy*tP^~KN?St zYu!dlKL2uOiYKD!Q#0cI>Ms2K^8L0;+u5n(6tqnG%iqRN=zFOS#(uw6?H*NFbYY>- zl~>&8t@NfBs(|P>K#*RS4;jzDF%d0>=?S;JXFFZ!>Y5M}Dld>snF*j?d?@+ed0|GI zT0g&0b|DQZOD?SX`#j_0D!zfTQXuUVv_>M}ko7TQ{_DjZos0UPNiyWT=x_7{C-u)| z2{meUEVhX0Z6TZc)Fio^lG%XG`=mrtjeZh$;}5RMGe1A0C1f1>{B&Ys@62e8T6Z&C zaFd(F-c+~5hA8e5Qx$klDLCpp!{*?U)aK)(0W+>7aNKGTYixes{^|KIh$d+HFbC-o z$NXE`*OhYmC)Ykgx)(XYNs)t7BU2{ln4utovy58a%ssgvnWg=dm=YbZxhkJ&t)eWL zW)m1v`Xv3`S8pbriP)j+#`#LO<7Q=(4^O}IS)+zdoz16zuPn1glM=&T+0sWeDZC0E z_s%gKX_accN|*Tg>s8}xN8rE9^rzT<(o-Wldto)XYg&tM@QtC=aBYD;xa_lYM4<%0 zAzvZ`-AgN3p{Cv&)YQf?m!2!W)yB&> z9zFLdH9JL>7}wVf{<*|g!khth739nJRnmT{5G!e+K+My`Wi|=bTK^;bj(e$NHUnyk zC`&R^v`H{q%{H@T2MHC8_um*D6S@e92f0(YQNvEJmHX z8K#_pEImz>EGg(2EmmP@Fu}}%{CG9|Tx3FaC_L(mzQK)c6oEU52EkF2sFY{-Q2@*Q=6jIhrLDR`{#-aqx$b63J|Q+E=&!1aX!6GhZ6Ea!KG{ zON>h0;bh%cQ)1rk2acE3dHVrf3E3HMc4_s4AimYHY=3{w)}Nh4MGi-tx$fD%_kR>u z@H7*xyjdky-JN0|hmnO^9u|5A0PcIR$u~1jPx|)ZR=#>4e-Y-^A`@rG7fX3;R%o{#% zJRaXmZ$?l`fQt#;4zs-n+)DfjS+y&00G}gcS@awxM>XyonZbVati6*AG1E77j7MvT zq4Z>oizyXDj&SPaXEn9A=}u+%Dla~M8c+>qi-_WPF%AY(rmd69_;HtnNr?hjwb@m< zi&0a1?aC36wzB_bN(X_qYAh~eew@3jSRc1q=g++CtzH9735E6qHRw6T*0-!y1r1_0 zIU0vqb^N(YRa&3Z>Ppw@6P_nzMZS>d>f=VQjC}}$7p^lkgs0X(C?ZeaKgzdKgKw>d zhI16SCvvBd4JP~C_}V;BCQ%}Gs3{mALZ&{ozZq1V>He$kY#iEr@g`jHCa12^rRFTIUCK^d424r+Mvwf7F!_BJCOU#*jq#F)LUK?Cw2X1*B?|Ci6w{ z{77HyKh})9m!gZ)_RUENb`z=ho*oN%B1r zS;Q5*(qre=B&s%s`a*+BWAk17Z-eUU@BJ2v+x(kNA_??L^!5X-S@0#i^=2(JeOyZ2 zCtvuuPy;=ytP(68ybzeBQIm<*y!Bofu}tgTx*UQ#uG!k2u1sxB{_2@}CmF1qJ`<6E z{z208D@GtP)|w8>!RTL`{U;w`Z^}pXO+NY*XZ}XK=6o)1q)xukYzbGcBL8ep{%9Dd zHN)Vrs&`ihEm-U%lM9|prwyi!qBHTk@!T5SE#G@p=#foKU0C>_V>!zzouAElbvms0Xfz2Fl4Y@O;^HwOb(T|9^~sg z1Si4E_}+<%pb*9?dn$JMGMFJ^*!s*^l=J!lI9txhBgXE{IpUmp@Es$`$$*Wq%o*Ca z;Jf(S11`14j7V#s7XRmd80*gD-WOUjZmMkjrNha-)iDuqzo2*a;UMAr$inIZlDGHo zZ_bYV1@+l2i(T3GmG3K$xt_eRY6?o1JhFsu9NT$MWpS_3HhqrL&V92BEK}_#Wefui z#7;N33ISj<3YHEZFg`jzt{~d_mACtSzN=Cv%8VzPZPZj>vm*2qdm!W0+{QHM(B{2d zt}g1rMEuP@e&#dd025iC(<;Wj;#Y;Q9&brEE?6^1sv-pf!z7kb>m~zd2D}fh{P=;wHG-Z zQ?5;4!qWbdg!1X_quuP`;5fWn8V=T)AUjDVd0U-z-^QjK*T=F31RpH_^oMigFzz*+ zM%~?_s^Ox~CBCfMr>$#2m{c+uv+cGUgOE$}YInURd^w$J*n1(+WWx~j$N4LCL=M{6 zH{2Ibg??O$N~eG1@yGQAE4u&;NqotVYqI?!iO7$0>M4h|+c|f+eghO*-5y2mxN_a! zirXW%qNewQ8YRgwH{{=zR&v(wVT;*JwN=jkU@o~^mqRUmrQcyrGT;0{7nx2d^ADCG zAM5sQOiVQTMW^|fol`aF)E!ekwfIaf_pS&Y6 z2%Ccxf``{S2)8}8jV>&y%ooX$jX$?0>Re=182PteokSaJO%)jMQsVXl-8Y+Wr2ajj z&~cr^yBQhiWr}e9)ikp=4b#`>;drMXEdJOB$^F|x*Z*Wmi>4hw&dbUJc zU@Pk2{l>`3>DPw1yG5M5x%EyA#f<1$mpfiM{{SvZ@H20T-*k6AIndRpoBFGxsp3P} zCvRJM#M^_768y1g^dJ@75#!_pKMaP&a0l!@SU4biJc?Ov3B2N_ZIZhj9-pQZY`1IU z`8_^9@hjkvlcAU8Ppu}^^*M_{$KmHc?oVks$5*@|p+qnNME#dd^LJaP_;W3#1!6+x zy;(;Vs+uLB?d^A8Qtw~$xu{coIRoqd(N#2e!R=cr$(W7i!v9x283QXrE-%6w^l0qv z4h@fj(!OgS4hKiI#M~Niyt$djWelFysZ9vkN*}6j=~jkE*CrAvfuYW`_EaXEz|h6N zCJwV;-Jivcj@m#=^$4Tm>(e{o;fhs4l`>nsv1-`89uGLAkPvLdO-{M-;=34B>o-&&vJW?F-L4J%dHEtGsyR(il4jzt)yU~L#XDoC+uJxXT+N%Ehus!2Tq?;-} zH{(Md-OGg&nGtGh=WlN$uRlcAKmBm-cI0To*+?cO{cJ!F5gZpLACYl~-}c&G8~jT6 z13i|Sr6&~=&vrMvyt%5Zn*YuM{F$go{x!wB+#M;NNAo0k&L$g_u4%xs#2q5yCH`M<`$Qrq!(KCAqjEk3wdX7nLSYR|v;lqw&={g5cijV988$6=^ngg~Zzck1Vl7{~61 zN2x;|4J?3ujR`{H{+`eNIajXtpE&)WGKes`ufgymhdcASFz2$})RFZx=d?C?C}oat z|GJWIN&!t=N5V%QTzRX)d46P%26+Kv-diaY8wl07QC2$>)3`@RW^Lmi4-08eT~Ijn zU+aVj#YJi|KcE6HT=)A#iY+`I$`lL-Etib6?mCAzIwQ8;YhlSWB{K4EBA*B23;AoFNE5*sgI&`DXuG&_Oxi zWc=~I@oXA+*6M6M@gjM$!*OmJXQH7df9ixatwe8#DS$c7eL4i)LlC@h6L0{1eD+~S zv*tUk#;w0&Wy4AMI{|6O%5}%25g@9m1K${{UXZXxTf4is?Vqo3Kp1UFMVN6Et<<(? zjhNZ7IcG8qhyEt>`tw&P-BK}8-ih}0Yad}N5v0TftbR3}yE375wDhAYrDn6Z9mp`s zdEJx1`)wmz=0T z3+WV|7I!vV{_Q!-qFva3_X9E3Z3G{WY2kFo9OS4Z!CKq?F>C%piVk~@Br*;vn%?;> zTXC%%nW_$B%(Id-YCG1G|B@mzxOQ`R5V81B2iMhQw)^10o^j*y2#H?X|k9<{bn-nIVrj3!hp0A+1(jL8gxyhFNa2)qnu-5A=(O_p`acr+e zK1G^bD-WYA3hj($b_HbPKvJZwWFC*Ow~>er@4GA@=v6Svk}hY09(1g``iv&8DyxYW z^Gx?rX4p4*>tZe~TxZec4;w|mxr-WD1xI%OU2nL}=JPpb9*UvXU8{yqS_EzX$r?xn zDpV_T_jX<^ZPU%o)J{IV zv9dylC@qyF^I(L|P@m&OglD_;o$YWaV3fVSD-KNVKyhkcKDa(CaW~&>jUnSl0PXYo z-%#Z|g?0$yjhVyO`wy(xE7_-i-ess08>-(FgQt-_b{BE*F&`zpnSDodK! z`}-%9haaml$E#lXH2v~;Q}yC?sBIl6KYBAMxj;}zsRQ5tKo(9$pQS-~cW|b?>-~xX zl>CDfi6er;RWlgd!yOkqcmAX;=efhPSs*EcN+#1rqhiL)f|Ax-m%9(}qe+8$R7$Mf zwzm`vH!57Ki`(1eq^$#tL&i%=m{c8DI6W!Qs9-Gev;jw9`vH||b9n}3cW0B!yX_rQ zL>%4RnxiQPOf}^94E}`#3ygE1xvOSfo4>r7@QmoklNWK3m@|$5(KM>fYwaYGO6i!i zFGa-U-Ol17L-46b&%dqAllEr-Ml#hkG(c@la??Z7_fzt@=KFaUsl&1~f_LrR7OrsH zg*ouio~St&CBofZMp7Qq-c`$Eat3SN^V=S9OgXPemPPrx$u}%~FNFAm1*9yxezMRl z`;4BIVV$|LSb;sJL=^$ZYs>x|5SivH@cvyl?J{R|@)-RI`vXqDX(3JVB{`jM$#?i@ zC6IAbu3fJXsF%XblB2+zoh)7Kl+dM$vy36-chpVUG|FunDioHSb%Ji(jjzWNjV~%8 zAZDVouJ?+3L~CgCv&Mi=SPB?@h6c%A7#IsbQPU?&ItlO&0K zDk3VU2)}^L8%PUhJQt`fsi{i;0sUYYc~w#33Mxw~;d{?F?fQ<fX(UALGS72~pJh!5CbNQx2+@Xs)=JJTB{|MzU#n!0aF| zYAs79JB_^E%nbhc?UKoBO_wP9(J53~cRtSCRw)dinE z9qfosqXoA&%-$9ON!;{i_VSixZC!_te9_%n6U%5XIt;(t)q_MQ!f+^WVIvoQ0Z~6p zRqZ3vh_~`>X^@w2>Qh?7$ai0->RW{Zg%s4>vK8mq$t*G0xrt_c>#wV9&C*z`PM7K$ z9x4y$FtZHSv>nYywAad~y)AHhQ4}9-U@97IhG7bYY+?c^j_xt)U0TVIX24@Zi1lwv z->Z8~RIig#q5C$g?cKWmbJ-5IS6DAkPw4SG{D++GSi0AbAviYPgwtl8h zf0dm=@X6O5*!CU3OROpPm@2h;oeVEF^mr9a4H|4*OL&DbJAEEPFf#~QxlM7K!zJb( z`~QeD{(Jjxek;1EF%mrkq1$<4?Sc5TPKZ(Htin+=g^@b60mw3lOaaMJn6O9xG)&}Z zg9O$Nvwv^*dA9qUiX#Y5!2SI707&CD7u^x|ct$}}fSV}($r-sO_M>5UN0<9t`eN?B zDq=AH&PxkoOK*+GY%0aVE&I#!+wpSeL@rP3G`rYq=87PJnd<%D&e6DxDCjz)rKj7M zgKqx_`H;ojlb7(OTZ!j5zP5GHs5QGYMP0Q=e?+77e2X9!x3jqNp9pU1%P?Mu*i`lE zVM)G}>&;$%D|aP1a9QT%!(S1t*=|mQ#c#@2nPoU|e9y-DHWxBlk6SINTiOIov~@RD zS82SNEhhbN=x}$LKaiXG2I}lj6YyVFMPVI;t6|(NOpbZGl5!M}-N`y3y)@85k1F#; z=Zgrq26#8|LsPLq3Kct2@;O0-DgR{(pDC|TT`vN1XpVeuU^>^I#FrR*T-(58U|i40 z9hUs^9Xwq8yz4@u^RBj0OLcbCJi~%m_k|S-996kL9{A&+nw6OSc)<+AGGWaT3+txy zO|R%^n3$*$0%&?y18pvm`k>IsWzqLr|BVP`C$q`SR#q_{NHOAW$o+Y76Qfv4Nv%Ay zXKU0yz7EJGw`|Byh28J_A1y&A+gYgJe+HixI|k2`U%Kw%kljsLN5OE$$8K=*R;5eohSOkA>l6vizz-?v+27kG_p6740c(s%zdW+EPa1^zPl`wK6XrnweY{mB2PQily6OqAAIzl_OD3O$Ukc9C?J(MRo@;}QVSyF z*tmyu2dB7MFLc$JyxJVi;fDCGqZ)q(T%etOpJ@)P37};|xA(85IM^toD6)_g{-e&; zF71yTqx|)Loa;-VljR{KiRe)D3aXK|w9rpoIE@2f{6`Nfi3_;=-m3&p>XcmGFnOJt zRME~z&xi`y=JnM)Ab2u&S=3csNxCZYLJoW0glH5y_YI_Dgr z&cn%Z1ip}$UKk20dhJKDqPqK4;;kZn@lr_42ggO4z0Bups+Ai4HSu)X%(!v=FRVb!v_q+`9-S(LBB^1TsPG z_V(LY@(~YepeQ-NmVhCr*io;A|KmI1ilT-}cKXV>RK77@^PP$}_2NC*470i&qjz_Z zv^(3Hhe67?G)R`rAgqUF&2j$EKKK81@%X{S-a+hO-)ETMy-9WY=|_N5%CJZ6?F<9T zQ7FV$6YOn3&Qh&dIn_~#|NH=dR%@7nufp7jR$lEW9W6CW{*DIf%9SAO5z@`l(_8@6 z*iJL0tUJBX;*$lWiu-XOP38cZ66EVJd>nQjzu=`SoEI8|`USLSn_$(W6txRd#J%p@ zlzu};87&@En(IW%&#yy8gfJYUbQ~w6Whc=W36f)~V4t#*>^DAYOfGH;QC3kmH+O-S z3nOJLatYBK8BjinO5Ktw8%l&wywGUY{5tGxp7S{JlX(NTlPK3Uc>u<=*jl%je&&d- z_{Z*U8A|LL((IWsUjTc6J);Xa^9xJ;J6R@Z=?RZd#ka&!y?89TD6QfA{gl7=iGc(; z0X^A|=&rV#IW5?k0zo8ka_6euZVK?Ymi~61B_A#cRsGJ@64IU5$!A4XEK{Ny6CDjM z>8=voe@K|-z5Y>pw=;Uv$&VZU4@>-ay^8if~Fo z^CZXCzcX1;<$}tCHo#~s%7}I14@Z4Te+8Wjn;JO#((KOBb9+t)*h!fJQZDrB!shF5 zzP9dnK0Lp;x3)d`uJC6QHBF>im0U|Su2St%ItT9V3EtYE;_(gXyTf^L!Zu5ET~E6P z@29qgQd*wcYRIAl@m#bbcb1TDN^hrcpYWMh3ql=NwtJ zggBCHtxQNl1n>n;X=j=Peg>>tO+eU+$}JQ&DWRC9?0Jxb;70F$D+Eza6L#Qd3K~YX zaVbdTX$ajp(xWKxs^1(93XRqSefq{_q{7@R1?{PlsakhoarZ0DNp27rqN#Br4BdB} zeWvXt_+QMn;BIjRKW*}p(6}mX1Kd!D#w&thZJlVZ&4&P<&`91Cg3b@xKq_6EWG!uy z5JCY$Jkzlt&NNvk`S3T7I|cZt7K&(o9l5C4_O@i5oDW50AsJ(a$2LFGn8J!Yu&|{ zNmP^1q#EeRo%B}TmxK0phAa%-SFM|QkRM0mI&;HyC8cBURt18r-3$(K#)>$OPcc%N z$WIEoe=}f$&*Q3#NlO=nt4zE6JN}WYZl9q2VSe8@n(2&gV73r`EWN(z^W#A(<96d4(GjlQF#4 zp@n`qbd0m7W1Z_OP6cHc3eZS(H{aC6iTTCdi$aW%=tJx5`lp|tzl zl^n@D!W0mta7WzvFZbBx!-H3h(-`-AL)C=N%8Ey1rGIt?$;z2Q3anSn@2~SgJa}9n zg&D`j5hi&zV$VI)I=0YLnU{z`A$CfkT7qh!QeS7KDVI&Y+)^ZgO?LWoeUFXr<$CkL zBX^_5*(4x$I%U^tVg+JuXA&3Ul(LFx8-ClUgFO>S;H^fE8a>V(Ir6V*VE3kzfLY#O zvQ2wM77PU{!)FR~$(`o{$LN651@YVTX8KFC@iA_+@448rq^f;RAAc`#5yak0{I>b@ zS)1IKo`&j_((}Iq$9bqLJ{&vz~d@JCXU60GA_S>Wm#+mh&U=t|7p~Vhf31}o01t`HF??~ z<-D=y(op{4f!|iXPXV3ND=y{$#b| z2l?}Rf1{Ho-tJ+O^fz)%IU=({01=UzYyt3HYw8Sk+8wg*7r zvjMa@=);rkmnWw?rbAc^YaGLwJ$^WN5#u8-LPrQ$g|M{acIy~v$HA;n7n{#X`Wvk8 z*!r~xT0kn9J4uhYpy!u&Iqifg4j^=@P*Iic`>)SG!>w?mVqf3sxYo3;QY97!HSUD? zkS@3dJt<>vJ*q!O1}|7MierNo-rA&uU)s(^oc9ly<9a7hhjSo zXW!7|A_9N^GG9$YZhSz~(^++EQ1xBfJ{?I*W>4JTaqM)AtcsU`ts8f@KOf*&SgWR} z;#-YP$lq;K0618UiouyyG#y0w+V?zd9Lv@gje|x!Du(yUS6R^xsV{E9J=iyFP%4^7IZtj>FJm zU6kUKee$TG2_Vhm&ZSU1PXspCSL^!3BBzDnDLUYqmsYaL)WfU;7qc7+g(p?};?y%Rw>}cmj8yzYy z`ser8sg%RGonjA%ZOuGNP~X3QCMj^4=j>G%8s}3xzO}6)j;ygo+qDC5}ci|k)-7BxOB^n-D z_U0}V3@P{S+&c42ZD<%+p1t04R9w)4$?+S@zUU^A@lk%b?3`+?C%~Y>USdbi;+9c- zW0$SeooDp7JF)iX{*sqWrE%M;XzZgwg%Vi)t-S_AJtkMZJQJId-gr;7XV5}M@aD7B zlO*n{ui_0STYm`Cqs&YPk+llV;B_7LKFV0e@O~I>w2NcRBXD^WANmd3GRvb}jQgFo zy|wtIr&Xk~7iD7P@$1VuG=VlF2iNt6z>!;6>$C$z(B~q_3)SlT#!$4xmDieAer(DXHqYqk-j3KT34-c4IACoyurb-7@=^`0~@hk zU-9+3FvBj&?qf#qVBC>+)(8(kCbfU;df@PJ0>HqyFy9;gWPQPESHIs^uItwXM5llC zeV6aW@8Is*W(Nnk=udg*6_&~}e1sb{EGGQIU?POpdzADfA|@~NA7@oz+q+a@LNH}% z?~a>Ldit+=H>5`*`QB(cd?2Lx7ms~m4P3_#tW7u6tikkW9%u0K@v~<4Pg8;0hLO`X z?*|A71RVt(SE*rv4Pj8nmd3ppxbkDeSCgXljMLWwmFs!exj+7+TJ1&r3oo#)tBL|t z<5?8wc)Cs-8Pda(cwusk9z>T1b_MG&{B75W&T38OfMo7g@(`x5oo%cwaO3elCnc-* z9*IS+LUoTU=UmW}xg$DNxAVzuo|=#ty_QOewY z$EzN>{fFYBF7&9HkRQL>lFnFg4dP-*65xcm%auI(T;G$^6A&(urpmjgk^5w~JaF`{ zR{gJ7zp`_$xN&$t%qBgnpHqpp96h&^MjaC9&B<8gU`#1kF6b%et19whdP+50%RHR! za74WW`asnYa;C9%^@;_d%$Y_LUmhU4*Enw-%LJXb^6!WDV5nDZEm?T&ATeyqdtkWD zwRE>(dq0Mx523{aR1z9XtW^)}!?7GG1;~w^WAr^uY3^q7z)UI?Sf3tzim{b`z`yER z`mHE@#>+6EeZ%&6dTIZ5nG%_v^~}I{PyWveYWrI2Ezuz7@g~Cv=(q-97R9cCQ4oaU zCR15)Q*_rp*Way3D^EkKn6hZ`C=75y_$rCXRhut?xwrd%ztjKz_ivm9=)K5#$91v$ z8JA=w$yHuq>m)f&m*K<$RMuB^B>3X~Z6qPaN~7>nV&QrZt;#t)zq2C)N(_JGI*26> zFh`Blh-lz|^v>Z7k`jxyI~LOQ<*?rz21V84&$2}W9tvJ-w7C&M>%aH7vS?mcgH%5JoB0@5#Hl~3CTs6{dlPpWsjo; z6!)$EnIF~z+pvDECX<`j@eZ2;6`(OagU@w-e%##?-yF?BT#K+2?*lf6ee@PbqT8-U z?rDoyV0H2kR}0|8!zAh$ik9jXQS8PHtiw=`iC@1filxNpvFy4f$12ONfGACLu>L_h zCZj(UptEECn{}0FJ5llOIQP4|<3TOl}b z=#t6$)?@{NTb4m`SI@gn`?XjL1ET=4X+Z4#cA}g1>|gFTdzrYSB>mQYKMgQ=MkjKT zNnYb38Ld!e+Y$Kh{yZj*Si8UZ=Gk`K5lrhXODt(U^JXLOz!<`2Z9%Iimm+vVkhK8w zF6>26XWI8jtk2l?cOmRAUy*2;>59H0QLRZ!1C+A>^iczth}i%!#L#xb0KBIjc|?Ht zlB{E{d#!F&9F#ek7n}~SJ9X#ni2(UDEzyv`5;A3n1s(WGcc3ni78UHEDeKA34_S#8;BI$&CUSz=#vJNhe6du*QBg+ZZo1)2#=ul@$LoZ848hd+37Q9oVM(6j;;*RdH zgEZE0Fph_272*ri3N0zll5w1W@Eouyf<5*R7PnfWdHZr?2vanu6#UB?w(+%}9WP2G zhOP>uUJrz7tfAGwHw05mC_%d9PYHp0YznULn%RHIn3$(JPFCi zi9MDC-deKsSz*8=gx{_W0WmeUZd*^!pu^OSRatCG(!FTV0iJ@q z3R1GrJ8@AIn>cZpSQX>A+#QVdmYwU`MIZF^O}Xp+D4PbjlN?l((1YC4>Qw?}WrDh$ zUAXzf9sZ!-g;js&Ln2whBAxGmL5Od-OcL*J085$0T&WA|c&RbQdu1IBWbi-%>uHhP z7#qxrAi;^p#;<%nkpr3alhk2XYxPLfQn1E8gNM9C9w;Pk83D})2UO@W4QYc--8E^} zTts8*q+{!hefqf|Cgv_i6W!Ej5{hG4+@R5L;Kj}O6!iCqPm4f!s|v`cG1dCv86aTE zv;#1KMRCdkU^jQGC;6zBLhY(ltkU`8;Bc89HT**3KZFogFlf)7($7QPr>`opuhchQOR#q}=HiXGEC%q0 zfa72uh{^pvHh@5}k7A?9?Qzg*E8U?(xQ(&5A|t>A&Y5|@wRul>Cfievb1=Cw!uH#*76%VP8y_ZpmNBzQ?`uU={ zWR}4_JWL_N$DS(-FZ}NXxIm+8KwHt05AkGGTqU;MRMQN~0YB0GF*f9Y&8l|Mw~t11 zHhiPMulJ*RD#sNsz~_F-*Q%f9T!Qycx1BkmTEZwV3)?MlfWMfBmmLjArA+h>V}6KZIQFl z8RTAP-e!xKHuyLW{cTRMN?w{J9!UA88wMu4Th}6ppSkPAn;j9GFyZjXv!c~5R z%8O(O*HwYARf_JO;#5D~&1xs2ME5=R#k2sGxfL-}>Ej64FL6g|)Vl4LFwBdb?{b0Q zAhor4G+$LWW-V?$)w`{vrRRT|JUP8={@-|aGCyDpZZJ_7`^-UI<0IwiCw9pGLo|V2 zUWCT}q2|u4#{+Lr_X(@s4W0lk*aYCp_2aKOjun)IF0l%*m$;8xUZ_uXz(9FBDy+{ncv9|#4^0H8UMUj=g z3PzA#YPh6_C%)`p*alOG)G%23Bl7C;zyKHY3$Jfo5x3?;CmoIa!OaWjR{}#d zaUo6e)S!Re;~#Hx=_F(ZAy0Wk*?N6q=saq*{r2)AX zk&-fjgg(U~y;d-Ehd*~!);U4_R%Ey?teZ5EL0ILDrU?%T$%$#fUE${f!KPa@me#7LC!Q0j4*RR&1mmFb6agT(BBYrEii;>s#t&Txb0;(3lC9_qmX+S%4sDgIKd!?h z)>+2777gdyjLm30|XMh=K-~R7Q*Z{0u?SYFT6%x}}r}G!K+x*dRR!d5wen%|IW)IAWs_RH4Ss0uLC^mtAa(t^jsQFzZol>!m zyp_O+w?b6*WZQ-kbn#G34!Tg6W63cnYlWa$z{#QSP>UF-`6)#>q zyM5t^vh?FK#xP2tT*x#201}n8autEJ!k|!ETef>jUIs#uVX7uAX1SK*QEuQujR?5~ zF!oF7LN=@XLU%=JO)Ej<`!EMqWR-`6ZGX!*HqOv4M*nz>C9IrK#}@@S7Bq9Ny>?_y z3a<{Aa>jr^orxLHwqx){gpNb^>16ThWN{#cM@`Sii5aIzGR-iifIt7VZ#@gu^;gw0i1e^dp$m?|Bkxd&V*NM!_ux+~g5v%bWZ1-V#s{dMw zv9rC4$tV5yB1YV(P`lCX#iI2r;kpw1T+!y@Hp*ol< zghVoL&XD`DJ!DP=%3s&oU&!KKke_duhhJdaWhSN=IspMmrljY#hTpWcC+`DG_f+CE VEu=RKC~JiAT188__N7(${{WTZr4s-E literal 0 HcmV?d00001 From 477bb1b6457ff3c823273212fa03f9f6bc52b455 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 12:22:39 -0500 Subject: [PATCH 03/10] add the iputil in case it's needed later --- service/ziti-tunnel/util/iputil/iputil.go | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 service/ziti-tunnel/util/iputil/iputil.go diff --git a/service/ziti-tunnel/util/iputil/iputil.go b/service/ziti-tunnel/util/iputil/iputil.go new file mode 100644 index 00000000..838664de --- /dev/null +++ b/service/ziti-tunnel/util/iputil/iputil.go @@ -0,0 +1,50 @@ +/* + * 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. + * + */ + +package iputil + +import ( + "encoding/binary" + "net" +) + +var allOnes = 0xFFFFFFFF + +func Ipv4Inc(ip net.IP, maskBits int) net.IP { + baseIpMask := 32 - maskBits + baseMask := (allOnes >> baseIpMask) << baseIpMask + ipMask := allOnes >> maskBits + + ipAsInt := Ipv4ToUint32(ip) + baseIp := ipAsInt & uint32(baseMask) + //fmt.Printf("actual ip: %b - %v ", ipAsInt, ip) + + newIpAsInt := ipAsInt + 1 + + return Uint32ToIpv4(baseIp + newIpAsInt & uint32(ipMask)) +} +func Ipv4ToUint32(ip net.IP) uint32 { + if len(ip) == 16 { + return binary.BigEndian.Uint32(ip[12:16]) + } + return binary.BigEndian.Uint32(ip) +} +func Uint32ToIpv4(nn uint32) net.IP { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, nn) + return ip +} From 376fd6d6ca266fc6f34b60753dea2b6ec42f7d47 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 12:28:34 -0500 Subject: [PATCH 04/10] tired of fighting VS line endings -> CRLF --- ZitiUpdateService/UpdateService.cs | 514 ++++++++++++++--------------- 1 file changed, 257 insertions(+), 257 deletions(-) diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 43ba01ef..09bc2970 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -1,45 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.ServiceProcess; -using System.Text; -using System.Threading; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Timers; -using System.Management.Automation; -using System.Xml; -using System.Configuration; -using System.Threading.Tasks; - +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Timers; +using System.Management.Automation; +using System.Xml; +using System.Configuration; +using System.Threading.Tasks; + using ZitiDesktopEdge.ServiceClient; -using NLog; - -namespace ZitiUpdateService { +using NLog; + +namespace ZitiUpdateService { public partial class UpdateService : ServiceBase { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private int checkTimerDefault = 60000; - private int updateTimerDefault = 600000; - - private string _version = ""; - private bool _isNew = true; - /// private int _majorVersion = 1; - private string _versionUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/maven-metadata.xml"; - private string _serviceUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/${version}/ziti-tunnel-win-${version}.zip"; - private System.Timers.Timer _checkTimer = new System.Timers.Timer(); - private System.Timers.Timer _updateTimer = new System.Timers.Timer(); - private bool inUpdateCheck = false; - private bool inServiceCheck = false; - private string _rootDirectory = ""; - private string _logDirectory = ""; - private bool _isJustStopped = true; - private string _versionType = "latest"; - - ServiceController controller; - public UpdateService() { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private int checkTimerDefault = 60000; + private int updateTimerDefault = 600000; + + private string _version = ""; + private bool _isNew = true; + /// private int _majorVersion = 1; + private string _versionUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/maven-metadata.xml"; + private string _serviceUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/${version}/ziti-tunnel-win-${version}.zip"; + private System.Timers.Timer _checkTimer = new System.Timers.Timer(); + private System.Timers.Timer _updateTimer = new System.Timers.Timer(); + private bool inUpdateCheck = false; + private bool inServiceCheck = false; + private string _rootDirectory = ""; + private string _logDirectory = ""; + private bool _isJustStopped = true; + private string _versionType = "latest"; + + ServiceController controller; + public UpdateService() { InitializeComponent(); Client svc = new Client(); @@ -47,220 +47,220 @@ namespace ZitiUpdateService { svc.OnClientConnected += Svc_OnClientConnected; svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; svc.OnClientDisconnected += Svc_OnClientDisconnected; - svc.OnShutdownEvent += Svc_OnShutdownEvent; - try { - svc.Connect(); - } catch { - svc.Reconnect(); - } - - svc.WaitForConnection(); - } - - public void Debug() { - OnStart(null); - } - - protected override void OnStart(string[] args) { - try { - if (ConfigurationManager.AppSettings.Get("UpdateUrl") != null) _versionUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); - if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); - } catch (Exception e) { - Logger.Info(e.ToString()); - } - _rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenZiti"); - if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); - _logDirectory = Path.Combine(_rootDirectory, "Logs"); - if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); - Logger.Info("Setup Watchers"); - SetupServiceWatchers(); - } - - protected override void OnStop() { - Logger.Info("Stopping update Service"); - } - - private void SetupServiceWatchers() { - Logger.Info("Setting Up Watchers"); - - var serviceTimerInterval = ConfigurationManager.AppSettings.Get("ServiceTimer"); - var serviceInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(serviceTimerInterval, out serviceInt)) { - serviceInt = new TimeSpan(0, 1, 0); - } - - var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); - var upInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { - upInt = new TimeSpan(0, 1, 0); - } - - Logger.Info("Ziti Update Setting Up Watchers"); - - _checkTimer = new System.Timers.Timer(); - _checkTimer.Elapsed += CheckService; - _checkTimer.Interval = serviceInt.TotalMilliseconds; - _checkTimer.Enabled = true; - _checkTimer.Start(); - Logger.Info("Service Checker is running"); - - _updateTimer = new System.Timers.Timer(); - _updateTimer.Elapsed += CheckUpdate; - _updateTimer.Interval = upInt.TotalMilliseconds; - _updateTimer.Enabled = true; - _updateTimer.Start(); - Logger.Info("Version Checker is running"); - CheckUpdate(null, null); //check immediately - } - - private void CheckUpdate(object sender, ElapsedEventArgs e) { - if (inUpdateCheck) return; - inUpdateCheck = true; //simple semaphone - try { - var request = WebRequest.Create(_versionUrl) as HttpWebRequest; - var response = request.GetResponse(); - Stream receiveStream = response.GetResponseStream(); - StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); - var result = readStream.ReadToEnd(); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(result); - XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/" + _versionType); - string version = node.InnerText; - if (version != _version) { - Logger.Info("Version Checked: " + version + " on " + _version + " from " + _versionType); - UpdateServiceFiles(version); - } - } catch { - - } - inUpdateCheck = false; - } - - private void CheckService(object sender, ElapsedEventArgs e) { - if (inServiceCheck || inUpdateCheck) return; - inServiceCheck = true; //simple semaphone - try { - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller == null) return; //means it's not installed or the user doesn't have privs - - if (controller.Status == ServiceControllerStatus.Stopped || controller.Status == ServiceControllerStatus.StopPending) { - if (_isJustStopped) { - _isJustStopped = false; - Logger.Info("Ziti Service has been stopped, update Network"); - //should not be necessary in v0.0.10+ RunScript(); - StartZiti(); - } - } else { - if (controller.Status == ServiceControllerStatus.Running) { - _isJustStopped = true; - } - } - } catch { - - } - inServiceCheck = false; - } - - private void UpdateServiceFiles(string currentVersion) { - var request = WebRequest.Create(_versionUrl) as HttpWebRequest; - var response = request.GetResponse(); - Stream receiveStream = response.GetResponseStream(); - StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); - var result = readStream.ReadToEnd(); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(result); - XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/"+_versionType); - string version = node.InnerText; - Logger.Info("Got Version: " + version+" from "+_versionType); - - if (_version.Trim() == currentVersion.Trim()) { - _version = currentVersion; - Logger.Info("Version local is the same as the version remote: " + version); - return; - } - - StopZiti(); - string remoteService = _serviceUrl.Replace("${version}", version); - - if (!Directory.Exists(Path.Combine(_rootDirectory, "Service"))) Directory.CreateDirectory(Path.Combine(_rootDirectory, "Service")); - string[] files = Directory.GetFiles(Path.Combine(_rootDirectory, "Service")); - foreach (string file in files) { - try { - Logger.Info("Delete: " + file); - File.Delete(file); - } catch (Exception e) { - EventLog.WriteEntry("Ziti", e.ToString(), EventLogEntryType.Error); - } - } - WebClient webClient = new WebClient(); - Logger.Info("Get From: "+remoteService); - webClient.DownloadFile(remoteService, Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip"); - Logger.Info("Zip Downloaded"); - ZipFile.ExtractToDirectory(Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip", Path.Combine(_rootDirectory, "Service")); - - Logger.Info("Zip UnZipped"); - File.WriteAllText(Path.Combine(_rootDirectory, @"Service\Version.txt"), version); - _version = version; - - if (_isNew) { - Logger.Info("Installing Service " + _version); - ProcessStartInfo installService = new ProcessStartInfo(); - installService.CreateNoWindow = true; - installService.UseShellExecute = true; - installService.FileName = Path.Combine(_rootDirectory, "Service") + @"\ziti-tunnel.exe"; - installService.WindowStyle = ProcessWindowStyle.Hidden; - installService.Arguments = "install"; - Logger.Info("Install Using " + installService.FileName + " " + installService.Arguments); - try { - Process exeProcess = Process.Start(installService); - exeProcess.WaitForExit(); - Logger.Info("Installed " + installService.FileName + " " + installService.Arguments); - - } catch (Exception e) { - Logger.Info("Cannot Install Service - " + e.ToString()); - } - } else { - // nothing to do - } - StartZiti(); - } - - private void RunScript() { - string scriptText = "get-netipinterface|ForEach-Object { Set-DnsClientServerAddress-InterfaceIndex $_.ifIndex-ResetServerAddresses}"; - Logger.Info("Executing: " + scriptText); - using (PowerShell PowerShellInstance = PowerShell.Create()) { - PowerShellInstance.AddScript(scriptText); - IAsyncResult result = PowerShellInstance.BeginInvoke(); - while (result.IsCompleted == false) { - Thread.Sleep(1000); - } - } - } - - private void StartZiti() { - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { - try { - Logger.Info("Starting Service"); - controller.Start(); - controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); - SetupServiceWatchers(); - } catch (Exception e) { - Logger.Info("Cannot Start Service - " + e.ToString()); - } - } - } - - private void StopZiti() { - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { - try { - controller.Stop(); - controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); - } catch (Exception e) { - Logger.Info("Cannot Stop Service - " + e.ToString()); - } - } + svc.OnShutdownEvent += Svc_OnShutdownEvent; + try { + svc.Connect(); + } catch { + svc.Reconnect(); + } + + svc.WaitForConnection(); + } + + public void Debug() { + OnStart(null); + } + + protected override void OnStart(string[] args) { + try { + if (ConfigurationManager.AppSettings.Get("UpdateUrl") != null) _versionUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); + if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); + } catch (Exception e) { + Logger.Info(e.ToString()); + } + _rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenZiti"); + if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); + _logDirectory = Path.Combine(_rootDirectory, "Logs"); + if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); + Logger.Info("Setup Watchers"); + SetupServiceWatchers(); + } + + protected override void OnStop() { + Logger.Info("Stopping update Service"); + } + + private void SetupServiceWatchers() { + Logger.Info("Setting Up Watchers"); + + var serviceTimerInterval = ConfigurationManager.AppSettings.Get("ServiceTimer"); + var serviceInt = TimeSpan.Zero; + if (!TimeSpan.TryParse(serviceTimerInterval, out serviceInt)) { + serviceInt = new TimeSpan(0, 1, 0); + } + + var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); + var upInt = TimeSpan.Zero; + if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { + upInt = new TimeSpan(0, 1, 0); + } + + Logger.Info("Ziti Update Setting Up Watchers"); + + _checkTimer = new System.Timers.Timer(); + _checkTimer.Elapsed += CheckService; + _checkTimer.Interval = serviceInt.TotalMilliseconds; + _checkTimer.Enabled = true; + _checkTimer.Start(); + Logger.Info("Service Checker is running"); + + _updateTimer = new System.Timers.Timer(); + _updateTimer.Elapsed += CheckUpdate; + _updateTimer.Interval = upInt.TotalMilliseconds; + _updateTimer.Enabled = true; + _updateTimer.Start(); + Logger.Info("Version Checker is running"); + CheckUpdate(null, null); //check immediately + } + + private void CheckUpdate(object sender, ElapsedEventArgs e) { + if (inUpdateCheck) return; + inUpdateCheck = true; //simple semaphone + try { + var request = WebRequest.Create(_versionUrl) as HttpWebRequest; + var response = request.GetResponse(); + Stream receiveStream = response.GetResponseStream(); + StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); + var result = readStream.ReadToEnd(); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(result); + XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/" + _versionType); + string version = node.InnerText; + if (version != _version) { + Logger.Info("Version Checked: " + version + " on " + _version + " from " + _versionType); + UpdateServiceFiles(version); + } + } catch { + + } + inUpdateCheck = false; + } + + private void CheckService(object sender, ElapsedEventArgs e) { + if (inServiceCheck || inUpdateCheck) return; + inServiceCheck = true; //simple semaphone + try { + controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); + if (controller == null) return; //means it's not installed or the user doesn't have privs + + if (controller.Status == ServiceControllerStatus.Stopped || controller.Status == ServiceControllerStatus.StopPending) { + if (_isJustStopped) { + _isJustStopped = false; + Logger.Info("Ziti Service has been stopped, update Network"); + //should not be necessary in v0.0.10+ RunScript(); + StartZiti(); + } + } else { + if (controller.Status == ServiceControllerStatus.Running) { + _isJustStopped = true; + } + } + } catch { + + } + inServiceCheck = false; + } + + private void UpdateServiceFiles(string currentVersion) { + var request = WebRequest.Create(_versionUrl) as HttpWebRequest; + var response = request.GetResponse(); + Stream receiveStream = response.GetResponseStream(); + StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); + var result = readStream.ReadToEnd(); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(result); + XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/"+_versionType); + string version = node.InnerText; + Logger.Info("Got Version: " + version+" from "+_versionType); + + if (_version.Trim() == currentVersion.Trim()) { + _version = currentVersion; + Logger.Info("Version local is the same as the version remote: " + version); + return; + } + + StopZiti(); + string remoteService = _serviceUrl.Replace("${version}", version); + + if (!Directory.Exists(Path.Combine(_rootDirectory, "Service"))) Directory.CreateDirectory(Path.Combine(_rootDirectory, "Service")); + string[] files = Directory.GetFiles(Path.Combine(_rootDirectory, "Service")); + foreach (string file in files) { + try { + Logger.Info("Delete: " + file); + File.Delete(file); + } catch (Exception e) { + EventLog.WriteEntry("Ziti", e.ToString(), EventLogEntryType.Error); + } + } + WebClient webClient = new WebClient(); + Logger.Info("Get From: "+remoteService); + webClient.DownloadFile(remoteService, Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip"); + Logger.Info("Zip Downloaded"); + ZipFile.ExtractToDirectory(Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip", Path.Combine(_rootDirectory, "Service")); + + Logger.Info("Zip UnZipped"); + File.WriteAllText(Path.Combine(_rootDirectory, @"Service\Version.txt"), version); + _version = version; + + if (_isNew) { + Logger.Info("Installing Service " + _version); + ProcessStartInfo installService = new ProcessStartInfo(); + installService.CreateNoWindow = true; + installService.UseShellExecute = true; + installService.FileName = Path.Combine(_rootDirectory, "Service") + @"\ziti-tunnel.exe"; + installService.WindowStyle = ProcessWindowStyle.Hidden; + installService.Arguments = "install"; + Logger.Info("Install Using " + installService.FileName + " " + installService.Arguments); + try { + Process exeProcess = Process.Start(installService); + exeProcess.WaitForExit(); + Logger.Info("Installed " + installService.FileName + " " + installService.Arguments); + + } catch (Exception e) { + Logger.Info("Cannot Install Service - " + e.ToString()); + } + } else { + // nothing to do + } + StartZiti(); + } + + private void RunScript() { + string scriptText = "get-netipinterface|ForEach-Object { Set-DnsClientServerAddress-InterfaceIndex $_.ifIndex-ResetServerAddresses}"; + Logger.Info("Executing: " + scriptText); + using (PowerShell PowerShellInstance = PowerShell.Create()) { + PowerShellInstance.AddScript(scriptText); + IAsyncResult result = PowerShellInstance.BeginInvoke(); + while (result.IsCompleted == false) { + Thread.Sleep(1000); + } + } + } + + private void StartZiti() { + controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); + if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { + try { + Logger.Info("Starting Service"); + controller.Start(); + controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); + SetupServiceWatchers(); + } catch (Exception e) { + Logger.Info("Cannot Start Service - " + e.ToString()); + } + } + } + + private void StopZiti() { + controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); + if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { + try { + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + } catch (Exception e) { + Logger.Info("Cannot Stop Service - " + e.ToString()); + } + } } private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { @@ -317,6 +317,6 @@ namespace ZitiUpdateService { foreach (Identity i in e) { Logger.Info("metrics!: " + i.Name); } - } - } -} + } + } +} From 4fb7796b3621ff3959ddc753fe156db1013f9dc9 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 15:10:10 -0500 Subject: [PATCH 05/10] wip commit --- ZitiUpdateService/App.config | 31 ++-- ZitiUpdateService/Program.cs | 56 ++++---- ZitiUpdateService/UpdateService.cs | 157 ++++++++++----------- ZitiUpdateService/ZitiUpdateService.csproj | 3 + ZitiUpdateService/packages.config | 1 + 5 files changed, 115 insertions(+), 133 deletions(-) diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config index 12942b24..429074e0 100644 --- a/ZitiUpdateService/App.config +++ b/ZitiUpdateService/App.config @@ -1,30 +1,19 @@  - - - + - - - diff --git a/ZitiUpdateService/Program.cs b/ZitiUpdateService/Program.cs index b4bdb792..7296b6a1 100644 --- a/ZitiUpdateService/Program.cs +++ b/ZitiUpdateService/Program.cs @@ -1,18 +1,18 @@ using System; -using NLog; -using NLog.Config; -using NLog.Targets; - -namespace ZitiUpdateService { +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace ZitiUpdateService { static class Program { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - ///

- /// The main entry point for the application. - /// - static void Main() { - Environment.SetEnvironmentVariable("ZITI_EXTENDED_DEBUG", "true"); - + /// + /// The main entry point for the application. + /// + static void Main() { + //Environment.SetEnvironmentVariable("ZITI_EXTENDED_DEBUG", "true"); + var config = new LoggingConfiguration(); var logname = "ziti-montior"; // Targets where to log to: File and Console @@ -21,7 +21,7 @@ namespace ZitiUpdateService { ArchiveEvery = FileArchivePeriod.Day, ArchiveNumbering = ArchiveNumberingMode.Rolling, MaxArchiveFiles = 7, - ArchiveAboveSize = 10000, + //ArchiveAboveSize = 10000, }; var logconsole = new ConsoleTarget("logconsole"); @@ -30,21 +30,21 @@ namespace ZitiUpdateService { config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); // Apply config - LogManager.Configuration = config; - + LogManager.Configuration = config; + UpdateService updateSvc = new UpdateService(); -#if DEBUG - - updateSvc.Debug(); - System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); -#else - ServiceBase[] ServicesToRun; - ServicesToRun = new ServiceBase[] - { - updateSvc - }; - ServiceBase.Run(ServicesToRun); -#endif +#if DEBUG + + updateSvc.Debug(); + System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); +#else + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + updateSvc + }; + ServiceBase.Run(ServicesToRun); +#endif } - } -} + } +} diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 09bc2970..48d86951 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -16,26 +16,20 @@ using System.Threading.Tasks; using ZitiDesktopEdge.ServiceClient; using NLog; +using Newtonsoft.Json.Linq; +using System.Reflection; namespace ZitiUpdateService { public partial class UpdateService : ServiceBase { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private int checkTimerDefault = 60000; - private int updateTimerDefault = 600000; - private string _version = ""; private bool _isNew = true; - /// private int _majorVersion = 1; - private string _versionUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/maven-metadata.xml"; - private string _serviceUrl = "https://netfoundry.jfrog.io/netfoundry/ziti-maven-snapshot/ziti-tunnel-win/amd64/windows/ziti-tunnel-win/${version}/ziti-tunnel-win-${version}.zip"; - private System.Timers.Timer _checkTimer = new System.Timers.Timer(); + private System.Timers.Timer _updateTimer = new System.Timers.Timer(); private bool inUpdateCheck = false; - private bool inServiceCheck = false; private string _rootDirectory = ""; private string _logDirectory = ""; - private bool _isJustStopped = true; private string _versionType = "latest"; ServiceController controller; @@ -43,7 +37,6 @@ namespace ZitiUpdateService { InitializeComponent(); Client svc = new Client(); - //svc.OnMetricsEvent += Svc_OnMetricsEvent; svc.OnClientConnected += Svc_OnClientConnected; svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; svc.OnClientDisconnected += Svc_OnClientDisconnected; @@ -63,7 +56,6 @@ namespace ZitiUpdateService { protected override void OnStart(string[] args) { try { - if (ConfigurationManager.AppSettings.Get("UpdateUrl") != null) _versionUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); } catch (Exception e) { Logger.Info(e.ToString()); @@ -83,12 +75,6 @@ namespace ZitiUpdateService { private void SetupServiceWatchers() { Logger.Info("Setting Up Watchers"); - var serviceTimerInterval = ConfigurationManager.AppSettings.Get("ServiceTimer"); - var serviceInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(serviceTimerInterval, out serviceInt)) { - serviceInt = new TimeSpan(0, 1, 0); - } - var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); var upInt = TimeSpan.Zero; if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { @@ -97,13 +83,6 @@ namespace ZitiUpdateService { Logger.Info("Ziti Update Setting Up Watchers"); - _checkTimer = new System.Timers.Timer(); - _checkTimer.Elapsed += CheckService; - _checkTimer.Interval = serviceInt.TotalMilliseconds; - _checkTimer.Enabled = true; - _checkTimer.Start(); - Logger.Info("Service Checker is running"); - _updateTimer = new System.Timers.Timer(); _updateTimer.Elapsed += CheckUpdate; _updateTimer.Interval = upInt.TotalMilliseconds; @@ -116,52 +95,79 @@ namespace ZitiUpdateService { private void CheckUpdate(object sender, ElapsedEventArgs e) { if (inUpdateCheck) return; inUpdateCheck = true; //simple semaphone - try { - var request = WebRequest.Create(_versionUrl) as HttpWebRequest; - var response = request.GetResponse(); - Stream receiveStream = response.GetResponseStream(); - StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); - var result = readStream.ReadToEnd(); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(result); - XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/" + _versionType); - string version = node.InnerText; - if (version != _version) { - Logger.Info("Version Checked: " + version + " on " + _version + " from " + _versionType); - UpdateServiceFiles(version); - } - } catch { + var updateUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); + if (string.IsNullOrEmpty(updateUrl)) { + updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; + } + + try {/* + HttpWebRequest httpWebRequest = WebRequest.CreateHttp(updateUrl); + httpWebRequest.Method = "GET"; + httpWebRequest.ContentType = "application/json"; + httpWebRequest.UserAgent = "OpenZiti UpdateService"; + HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); + StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); + string result = streamReader.ReadToEnd(); + JObject json = JObject.Parse(result); + string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; + + Version installed = new Version(currentVersion); + Version published = new Version(serverVersion); + int compare = installed.CompareTo(published); + if (compare < 0) { + Logger.Info("an upgrade is available. starting update process."); + } else if (compare > 0) { + Logger.Info("the version installed is newer than the released version"); + return; + } + JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); + string downloadUrl = null; + foreach (JObject asset in assets.Children()) { + downloadUrl = asset.Property("browser_download_url").Value.ToString(); + break; + } + + if(downloadUrl == null) { + Logger.Error("DOWNLOAD URL not found at: {0}", updateUrl); + return; + } + */ + var curdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + var updateFolder = $"{curdir}{Path.DirectorySeparatorChar}updates"; + Directory.CreateDirectory(updateFolder); + + var downloadUrl = "https://github.com/openziti/desktop-edge-win/releases/download/1.2.12/Ziti.Desktop.Edge.Client-1.2.13.exe"; + string filename = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1); + + if (string.IsNullOrEmpty(filename)) { + Logger.Warn("filename not found from web request. using generic filename {0}", "Ziti.Desktop.Edge.Client.exe"); + } + + string fileDestination = Path.Combine(updateFolder, filename); + Logger.Info("Checking to see if the file has been downloaded previously"); + if (File.Exists(fileDestination)) { + Logger.Info("file already exists at {0}. not downloading again", fileDestination); + } else { + Logger.Info("update found. downloading update from {0} to {1}", downloadUrl, fileDestination); + WebClient myWebClient = new WebClient(); + myWebClient.DownloadFile(downloadUrl, fileDestination); + Logger.Info("update downloaded"); + } + + StopZiti(); + + // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well + + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error has occurred"); } inUpdateCheck = false; } - private void CheckService(object sender, ElapsedEventArgs e) { - if (inServiceCheck || inUpdateCheck) return; - inServiceCheck = true; //simple semaphone - try { - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller == null) return; //means it's not installed or the user doesn't have privs - - if (controller.Status == ServiceControllerStatus.Stopped || controller.Status == ServiceControllerStatus.StopPending) { - if (_isJustStopped) { - _isJustStopped = false; - Logger.Info("Ziti Service has been stopped, update Network"); - //should not be necessary in v0.0.10+ RunScript(); - StartZiti(); - } - } else { - if (controller.Status == ServiceControllerStatus.Running) { - _isJustStopped = true; - } - } - } catch { - - } - inServiceCheck = false; - } - - private void UpdateServiceFiles(string currentVersion) { + private void UpdateServiceFiles(string currentVersion, string _versionUrl, string _serviceUrl) { var request = WebRequest.Create(_versionUrl) as HttpWebRequest; var response = request.GetResponse(); Stream receiveStream = response.GetResponseStream(); @@ -225,18 +231,6 @@ namespace ZitiUpdateService { StartZiti(); } - private void RunScript() { - string scriptText = "get-netipinterface|ForEach-Object { Set-DnsClientServerAddress-InterfaceIndex $_.ifIndex-ResetServerAddresses}"; - Logger.Info("Executing: " + scriptText); - using (PowerShell PowerShellInstance = PowerShell.Create()) { - PowerShellInstance.AddScript(scriptText); - IAsyncResult result = PowerShellInstance.BeginInvoke(); - while (result.IsCompleted == false) { - Thread.Sleep(1000); - } - } - } - private void StartZiti() { controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { @@ -252,13 +246,14 @@ namespace ZitiUpdateService { } private void StopZiti() { + Logger.Info("Stopping the ziti service..."); controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { try { controller.Stop(); controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); } catch (Exception e) { - Logger.Info("Cannot Stop Service - " + e.ToString()); + Logger.Error(e, "Timout while trying to stop service!"); } } } @@ -312,11 +307,5 @@ namespace ZitiUpdateService { Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); } } - - private static void Svc_OnMetricsEvent(object sender, List e) { - foreach (Identity i in e) { - Logger.Info("metrics!: " + i.Name); - } - } } } diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index f05b8b80..76db24c4 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -74,6 +74,9 @@ bin\Releases\ + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\NLog.4.7.5\lib\net45\NLog.dll diff --git a/ZitiUpdateService/packages.config b/ZitiUpdateService/packages.config index 4182569c..bba3d6da 100644 --- a/ZitiUpdateService/packages.config +++ b/ZitiUpdateService/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file From 5d4731db74b2ef9b57941d0538ce34c521a85678 Mon Sep 17 00:00:00 2001 From: ziti-ci Date: Sun, 1 Nov 2020 15:12:43 -0500 Subject: [PATCH 06/10] set the monitor to startup at install --- Installer/ZitiDesktopEdge.aip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installer/ZitiDesktopEdge.aip b/Installer/ZitiDesktopEdge.aip index e87b60bf..67cb335f 100644 --- a/Installer/ZitiDesktopEdge.aip +++ b/Installer/ZitiDesktopEdge.aip @@ -265,7 +265,7 @@ - + From 297681fb7e6da10d860bd3aa62b18e9cc9e5e4aa Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Sun, 1 Nov 2020 20:06:09 -0500 Subject: [PATCH 07/10] merge and update build script to not commit/push if not a release branch --- Installer/build.ps1 | 11 ++++- ZitiUpdateService/Properties/AssemblyInfo.cs | 46 ++------------------ 2 files changed, 12 insertions(+), 45 deletions(-) diff --git a/Installer/build.ps1 b/Installer/build.ps1 index 907ae717..ee1b20ce 100644 --- a/Installer/build.ps1 +++ b/Installer/build.ps1 @@ -52,8 +52,15 @@ if($gituser -eq "ziti-ci") { } else { echo "detected ziti-ci - but no gh_deploy_key. push may fail" } - git commit -m "[ci skip] committing updated installer file" 2>&1 - git push 2>&1 + + $b="$env:GIT_BRANCH" + if( $b -match '(^master$|^release-[0-9]*\.[0-9]*\.[0-9]*)' ) { + echo "branch $b matches the required regex - committing and pushing" + git commit -m "[ci skip] committing updated installer file" 2>&1 + git push 2>&1 + } else { + echo "branch $b does not match the regex. no commit/no push" + } } else { echo "detected user [${gituser}] which is not ziti-ci - skipping installer commit" } diff --git a/ZitiUpdateService/Properties/AssemblyInfo.cs b/ZitiUpdateService/Properties/AssemblyInfo.cs index 861b47fc..4f2b14db 100644 --- a/ZitiUpdateService/Properties/AssemblyInfo.cs +++ b/ZitiUpdateService/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ -<<<<<<< HEAD -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -33,43 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -======= -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ZitiUpdateService")] -[assembly: AssemblyDescription("Ziti Monitor and Update Service")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("NetFoundry")] -[assembly: AssemblyProduct("ZitiUpdateService")] -[assembly: AssemblyCopyright("Copyright © NetFoundry 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9ab81609-f5dd-40a6-87b5-f3279d48514d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] ->>>>>>> release-1.3.0 +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] From 06eade3c1e4d3e091af3ad8e499b3b0cfee80eb4 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Sun, 1 Nov 2020 20:21:24 -0500 Subject: [PATCH 08/10] tweak the installer and add missing using statement back to service --- Installer/ZitiDesktopEdge.aip | 2 ++ ZitiUpdateService/Program.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/Installer/ZitiDesktopEdge.aip b/Installer/ZitiDesktopEdge.aip index ca6b7e45..0a6bc5a8 100644 --- a/Installer/ZitiDesktopEdge.aip +++ b/Installer/ZitiDesktopEdge.aip @@ -22,10 +22,12 @@ + + diff --git a/ZitiUpdateService/Program.cs b/ZitiUpdateService/Program.cs index 7296b6a1..6c482650 100644 --- a/ZitiUpdateService/Program.cs +++ b/ZitiUpdateService/Program.cs @@ -1,4 +1,5 @@ using System; +using System.ServiceProcess; using NLog; using NLog.Config; using NLog.Targets; From be1639257155d96e99611658bef8d242fdbf147b Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Sun, 1 Nov 2020 23:03:59 -0500 Subject: [PATCH 09/10] wip auto-update --- Installer/ZitiDesktopEdge.aip | 15 +- ZitiUpdateService/IUpdateCheck.cs | 15 ++ ZitiUpdateService/UpdateCheckers.cs | 88 +++++++++++ ZitiUpdateService/UpdateService.cs | 176 ++++++--------------- ZitiUpdateService/ZitiUpdateService.csproj | 2 + 5 files changed, 162 insertions(+), 134 deletions(-) create mode 100644 ZitiUpdateService/IUpdateCheck.cs create mode 100644 ZitiUpdateService/UpdateCheckers.cs diff --git a/Installer/ZitiDesktopEdge.aip b/Installer/ZitiDesktopEdge.aip index 0a6bc5a8..20c3834b 100644 --- a/Installer/ZitiDesktopEdge.aip +++ b/Installer/ZitiDesktopEdge.aip @@ -8,7 +8,6 @@ - @@ -50,10 +49,12 @@ - + + + @@ -68,7 +69,6 @@ - @@ -80,9 +80,10 @@ + + - @@ -213,6 +214,8 @@ + + @@ -267,11 +270,11 @@ - + - + diff --git a/ZitiUpdateService/IUpdateCheck.cs b/ZitiUpdateService/IUpdateCheck.cs new file mode 100644 index 00000000..fb0a472d --- /dev/null +++ b/ZitiUpdateService/IUpdateCheck.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZitiUpdateService { + interface IUpdateCheck { + bool IsUpdateAvailable(Version current); + string FileName(); + void CopyUpdatePackage(string destinationFolder, string destinationName); + + bool AlreadyDownloaded(string destinationFolder, string destinationName); + } +} diff --git a/ZitiUpdateService/UpdateCheckers.cs b/ZitiUpdateService/UpdateCheckers.cs new file mode 100644 index 00000000..c70c5e46 --- /dev/null +++ b/ZitiUpdateService/UpdateCheckers.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Net; + +using NLog; +using Newtonsoft.Json.Linq; + +namespace ZitiUpdateService { + internal class GithubCheck : IUpdateCheck { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + string url; + string downloadUrl = null; + string downloadFileName = null; + + public GithubCheck(string url) { + this.url = url; + } + + public bool AlreadyDownloaded(string destinationFolder, string destinationName) { + return File.Exists(Path.Combine(destinationFolder, destinationName)); + } + + public void CopyUpdatePackage(string destinationFolder, string destinationName) { + WebClient webClient = new WebClient(); + string dest = Path.Combine(destinationFolder, destinationName); + Logger.Info("download started: {0}", downloadUrl); + webClient.DownloadFile(downloadUrl, dest); + Logger.Info("download complete. file at {0}", dest); + } + + public string FileName() { + return downloadFileName; + } + + public bool IsUpdateAvailable(Version current) { + HttpWebRequest httpWebRequest = WebRequest.CreateHttp(url); + httpWebRequest.Method = "GET"; + httpWebRequest.ContentType = "application/json"; + httpWebRequest.UserAgent = "OpenZiti UpdateService"; + HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); + StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); + string result = streamReader.ReadToEnd(); + JObject json = JObject.Parse(result); + string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; + + Version published = new Version(serverVersion); + int compare = current.CompareTo(published); + if (compare < 0) { + Logger.Info("an upgrade is available."); + } else if (compare > 0) { + Logger.Info("the version installed is newer than the released version"); + return false; + } + JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); + foreach (JObject asset in assets.Children()) { + downloadUrl = asset.Property("browser_download_url").Value.ToString(); + break; + } + + if (downloadUrl == null) { + Logger.Error("DOWNLOAD URL not found at: {0}", url); + return false; + } + downloadFileName = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1); + + return true; + } + } + + internal class FilesystemCheck : IUpdateCheck { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + public bool AlreadyDownloaded(string destinationFolder, string destinationName) { + return File.Exists(Path.Combine(destinationFolder, destinationName)); + } + + public void CopyUpdatePackage(string destinationFolder, string destinationName) { + throw new NotImplementedException(); + } + + public string FileName() { + return downloadFileName; + } + + public bool IsUpdateAvailable(Version current) { + throw new NotImplementedException(); + } + } +} diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 48d86951..d99dcd2e 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -18,6 +18,7 @@ using ZitiDesktopEdge.ServiceClient; using NLog; using Newtonsoft.Json.Linq; using System.Reflection; +using NLog.Config; namespace ZitiUpdateService { public partial class UpdateService : ServiceBase { @@ -25,29 +26,25 @@ namespace ZitiUpdateService { private string _version = ""; private bool _isNew = true; - + private System.Timers.Timer _updateTimer = new System.Timers.Timer(); private bool inUpdateCheck = false; private string _rootDirectory = ""; private string _logDirectory = ""; private string _versionType = "latest"; + private Client svc = new Client(); + private bool running = false; + ServiceController controller; public UpdateService() { InitializeComponent(); - Client svc = new Client(); + Logger.Info("Initializing"); svc.OnClientConnected += Svc_OnClientConnected; svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; svc.OnClientDisconnected += Svc_OnClientDisconnected; svc.OnShutdownEvent += Svc_OnShutdownEvent; - try { - svc.Connect(); - } catch { - svc.Reconnect(); - } - - svc.WaitForConnection(); } public void Debug() { @@ -55,6 +52,7 @@ namespace ZitiUpdateService { } protected override void OnStart(string[] args) { + try { if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); } catch (Exception e) { @@ -64,16 +62,29 @@ namespace ZitiUpdateService { if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); _logDirectory = Path.Combine(_rootDirectory, "Logs"); if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); - Logger.Info("Setup Watchers"); - SetupServiceWatchers(); + if (!running) { + running = true; + Task.Run(() => { + SetupServiceWatchers(); + }); + } + Logger.Info("initialized and running"); } protected override void OnStop() { - Logger.Info("Stopping update Service"); + Logger.Info("stopping update service"); } private void SetupServiceWatchers() { - Logger.Info("Setting Up Watchers"); + Logger.Info("setting up watches"); + + try { + svc.Connect(); + } catch { + //svc.Reconnect(); + } + + //svc.WaitForConnection(); var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); var upInt = TimeSpan.Zero; @@ -95,142 +106,51 @@ namespace ZitiUpdateService { private void CheckUpdate(object sender, ElapsedEventArgs e) { if (inUpdateCheck) return; inUpdateCheck = true; //simple semaphone + try { + Logger.Info("checking for update"); + var updateUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); + if (string.IsNullOrEmpty(updateUrl)) { + updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; + } + IUpdateCheck check = new GithubCheck(updateUrl); - var updateUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); - if (string.IsNullOrEmpty(updateUrl)) { - updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; - } - - try {/* - HttpWebRequest httpWebRequest = WebRequest.CreateHttp(updateUrl); - httpWebRequest.Method = "GET"; - httpWebRequest.ContentType = "application/json"; - httpWebRequest.UserAgent = "OpenZiti UpdateService"; - HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); - StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); - string result = streamReader.ReadToEnd(); - JObject json = JObject.Parse(result); - string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; - + string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? Version installed = new Version(currentVersion); - Version published = new Version(serverVersion); - int compare = installed.CompareTo(published); - if (compare < 0) { - Logger.Info("an upgrade is available. starting update process."); - } else if (compare > 0) { - Logger.Info("the version installed is newer than the released version"); + if (!check.IsUpdateAvailable(installed)) { + Logger.Debug("update check complete. no update available"); + inUpdateCheck = false; return; } - JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); - string downloadUrl = null; - foreach (JObject asset in assets.Children()) { - downloadUrl = asset.Property("browser_download_url").Value.ToString(); - break; - } - if(downloadUrl == null) { - Logger.Error("DOWNLOAD URL not found at: {0}", updateUrl); - return; - } - */ + Logger.Info("update is available."); + var curdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var updateFolder = $"{curdir}{Path.DirectorySeparatorChar}updates"; Directory.CreateDirectory(updateFolder); - var downloadUrl = "https://github.com/openziti/desktop-edge-win/releases/download/1.2.12/Ziti.Desktop.Edge.Client-1.2.13.exe"; - string filename = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1); + Logger.Info("copying update package"); + string filename = check.FileName(); - if (string.IsNullOrEmpty(filename)) { - Logger.Warn("filename not found from web request. using generic filename {0}", "Ziti.Desktop.Edge.Client.exe"); - } - - string fileDestination = Path.Combine(updateFolder, filename); - Logger.Info("Checking to see if the file has been downloaded previously"); - if (File.Exists(fileDestination)) { - Logger.Info("file already exists at {0}. not downloading again", fileDestination); - } else { - Logger.Info("update found. downloading update from {0} to {1}", downloadUrl, fileDestination); - WebClient myWebClient = new WebClient(); - myWebClient.DownloadFile(downloadUrl, fileDestination); - Logger.Info("update downloaded"); + if (check.AlreadyDownloaded(updateFolder, filename)) { + Logger.Info("package has already been downloaded - moving to install phase"); + } else { + Logger.Info("copying update package begins"); + check.CopyUpdatePackage(updateFolder, filename); + Logger.Info("copying update package complete"); } StopZiti(); // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well - + string fileDestination = Path.Combine(updateFolder, filename); + Process.Start(fileDestination, "/passive"); } catch (Exception ex) { Logger.Error(ex, "Unexpected error has occurred"); - } + } inUpdateCheck = false; } - private void UpdateServiceFiles(string currentVersion, string _versionUrl, string _serviceUrl) { - var request = WebRequest.Create(_versionUrl) as HttpWebRequest; - var response = request.GetResponse(); - Stream receiveStream = response.GetResponseStream(); - StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8); - var result = readStream.ReadToEnd(); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(result); - XmlNode node = xmlDoc.SelectSingleNode("metadata/versioning/"+_versionType); - string version = node.InnerText; - Logger.Info("Got Version: " + version+" from "+_versionType); - - if (_version.Trim() == currentVersion.Trim()) { - _version = currentVersion; - Logger.Info("Version local is the same as the version remote: " + version); - return; - } - - StopZiti(); - string remoteService = _serviceUrl.Replace("${version}", version); - - if (!Directory.Exists(Path.Combine(_rootDirectory, "Service"))) Directory.CreateDirectory(Path.Combine(_rootDirectory, "Service")); - string[] files = Directory.GetFiles(Path.Combine(_rootDirectory, "Service")); - foreach (string file in files) { - try { - Logger.Info("Delete: " + file); - File.Delete(file); - } catch (Exception e) { - EventLog.WriteEntry("Ziti", e.ToString(), EventLogEntryType.Error); - } - } - WebClient webClient = new WebClient(); - Logger.Info("Get From: "+remoteService); - webClient.DownloadFile(remoteService, Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip"); - Logger.Info("Zip Downloaded"); - ZipFile.ExtractToDirectory(Path.Combine(_rootDirectory, "Service") + @"\windows-tunneler.zip", Path.Combine(_rootDirectory, "Service")); - - Logger.Info("Zip UnZipped"); - File.WriteAllText(Path.Combine(_rootDirectory, @"Service\Version.txt"), version); - _version = version; - - if (_isNew) { - Logger.Info("Installing Service " + _version); - ProcessStartInfo installService = new ProcessStartInfo(); - installService.CreateNoWindow = true; - installService.UseShellExecute = true; - installService.FileName = Path.Combine(_rootDirectory, "Service") + @"\ziti-tunnel.exe"; - installService.WindowStyle = ProcessWindowStyle.Hidden; - installService.Arguments = "install"; - Logger.Info("Install Using " + installService.FileName + " " + installService.Arguments); - try { - Process exeProcess = Process.Start(installService); - exeProcess.WaitForExit(); - Logger.Info("Installed " + installService.FileName + " " + installService.Arguments); - - } catch (Exception e) { - Logger.Info("Cannot Install Service - " + e.ToString()); - } - } else { - // nothing to do - } - StartZiti(); - } - private void StartZiti() { controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index 76db24c4..9b676c36 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -103,6 +103,7 @@ + Component @@ -110,6 +111,7 @@ ProjectInstaller.cs + Component From 82176383d8ce1b7bd20c96c0649f7e0376820677 Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Sun, 1 Nov 2020 23:22:20 -0500 Subject: [PATCH 10/10] hopefully autoupdate works now --- DesktopEdge/MainMenu.xaml.cs | 3 ++- ZitiUpdateService/App.config | 2 +- ZitiUpdateService/UpdateCheckers.cs | 8 +++++--- ZitiUpdateService/UpdateService.cs | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/DesktopEdge/MainMenu.xaml.cs b/DesktopEdge/MainMenu.xaml.cs index 11a89570..1a28ca91 100644 --- a/DesktopEdge/MainMenu.xaml.cs +++ b/DesktopEdge/MainMenu.xaml.cs @@ -115,9 +115,10 @@ namespace ZitiDesktopEdge _downloadUrl = asset.Property("browser_download_url").Value.ToString(); break; } - } catch + } catch(Exception ex) { UpdateAvailable.Content = "An exception occurred while performing upgrade check"; + Debug.WriteLine("Error when checking for version: " + ex.Message); UpdateAvailable.Visibility = Visibility.Visible; } } diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config index 429074e0..ed981805 100644 --- a/ZitiUpdateService/App.config +++ b/ZitiUpdateService/App.config @@ -11,7 +11,7 @@ This operation is should probably remain high such as "1:0:0" (every hour) or "1:0:0:0" (every day) --> - + diff --git a/ZitiUpdateService/UpdateCheckers.cs b/ZitiUpdateService/UpdateCheckers.cs index c70c5e46..d478cff9 100644 --- a/ZitiUpdateService/UpdateCheckers.cs +++ b/ZitiUpdateService/UpdateCheckers.cs @@ -69,20 +69,22 @@ namespace ZitiUpdateService { internal class FilesystemCheck : IUpdateCheck { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + string dest = null; public bool AlreadyDownloaded(string destinationFolder, string destinationName) { return File.Exists(Path.Combine(destinationFolder, destinationName)); } public void CopyUpdatePackage(string destinationFolder, string destinationName) { - throw new NotImplementedException(); + dest = Path.Combine(destinationFolder, destinationName); + File.Copy(@"C:\git\github\openziti\desktop-edge-win\Installer\Output\" + FileName(), dest); } public string FileName() { - return downloadFileName; + return "Ziti Desktop Edge Client-1.3.0.exe"; } public bool IsUpdateAvailable(Version current) { - throw new NotImplementedException(); + return true; } } } diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index d99dcd2e..2df6928c 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -113,6 +113,7 @@ namespace ZitiUpdateService { updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; } IUpdateCheck check = new GithubCheck(updateUrl); + //IUpdateCheck check = new FilesystemCheck(); string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? Version installed = new Version(currentVersion);