using System.Net.Http.Headers; namespace VFXReviewWorker; /// /// `VFXReviewWorker.exe --check` — install verification for a new machine. /// Resolves every path and dependency the worker needs, reports what is /// missing, and exits non-zero if anything would stop it running. Cheaper than /// installing the service and reading logs to find a typo'd path. /// public static class Preflight { public static async Task RunAsync(string? configPath) { var ok = true; Console.WriteLine("VFXReview RenderWorker preflight"); Console.WriteLine(); var resolvedConfigPath = configPath ?? WorkerOptions.DefaultConfigPath; WorkerOptions? options = null; try { options = WorkerOptions.Load(configPath); ok &= Report("config file", resolvedConfigPath, true); } catch (Exception ex) { Report("config file", resolvedConfigPath, false, ex.Message); Console.WriteLine(); Console.WriteLine("Cannot continue without a config file. See RenderWorker/README.md."); return 1; } Console.WriteLine($" machine name : {options.MachineName}"); Console.WriteLine($" server : {options.ServerUrl}"); Console.WriteLine($" api key : {(string.IsNullOrWhiteSpace(options.ApiKey) ? "(missing)" : "set")}"); Console.WriteLine(); ok &= Report("aerender.exe", options.AerenderPath, File.Exists(options.AerenderPath)); var afterFx = options.ResolveAfterFxPath(); ok &= Report("AfterFX.com", afterFx, File.Exists(afterFx), "needed for preview builds only"); var script = options.ResolvePreviewScriptPath(); ok &= Report("preview script", script, File.Exists(script), "needed for preview builds only"); if (!string.IsNullOrWhiteSpace(options.FfmpegPath)) { Report("ffmpeg", options.FfmpegPath!, File.Exists(options.FfmpegPath!), "optional — thumbnails are skipped without it"); } else { Console.WriteLine(" [ - ] ffmpeg not configured (thumbnails will be skipped)"); } // Path mappings: prove the mapped roots are actually reachable here. Console.WriteLine(); if (options.PathMappings.Count == 0) { Console.WriteLine(" [ ! ] no pathMappings configured — manifest UNC paths will be used as-is"); } foreach (var m in options.PathMappings) { var reachable = Directory.Exists(m.To); Report($"path map {m.From}", m.To, reachable, "target must be reachable from this machine"); ok &= reachable; } // Server reachability + API key validity in one call. Console.WriteLine(); try { using var http = new HttpClient { BaseAddress = new Uri(options.ServerUrl.TrimEnd('/') + "/"), Timeout = TimeSpan.FromSeconds(15) }; http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey); var res = await http.GetAsync("api/ext/projects"); if (res.IsSuccessStatusCode) { Console.WriteLine(" [ok ] server reachable and API key accepted"); } else if ((int)res.StatusCode == 401) { Console.WriteLine(" [FAIL] server reachable but API key was rejected (401)"); ok = false; } else { Console.WriteLine($" [FAIL] server responded {(int)res.StatusCode}"); ok = false; } } catch (Exception ex) { Console.WriteLine($" [FAIL] cannot reach {options.ServerUrl}: {ex.Message}"); ok = false; } Console.WriteLine(); Console.WriteLine(ok ? "All checks passed — safe to install the service." : "One or more checks FAILED — fix the above before installing."); return ok ? 0 : 1; } private static bool Report(string label, string path, bool exists, string? note = null) { var status = exists ? "ok " : "FAIL"; Console.WriteLine($" [{status}] {label,-16} {path}"); if (!exists && note is not null) { Console.WriteLine($" ({note})"); } return exists; } }