| 1 | // WSLC-CustomContainer |
| 2 | // |
| 3 | // A barebones Windows console application that turns text (e.g. a URL) into a |
| 4 | // scannable QR code, rendered by a tiny Python tool running inside a *custom* |
| 5 | // Linux container. |
| 6 | // |
| 7 | // What makes this sample different from the others: it ships its own |
| 8 | // Containerfile that is built automatically as part of the normal build (F5) |
| 9 | // via the <WslcImage> item in WSLCCustomContainer.csproj. The built image is |
| 10 | // saved to customcontainer.tar next to the executable, and this app loads that |
| 11 | // local tar (no registry pull) before running the tool. |
| 12 | // |
| 13 | // dotnet run -- "https://aka.ms/wslc" |
| 14 | |
| 15 | using Microsoft.WSL.Containers; |
| 16 | |
| 17 | const string imageName = "customcontainer:latest"; |
| 18 | |
| 19 | // The text to encode comes from the command line; fall back to a sample URL. |
| 20 | string text = args.Length > 0 ? string.Join(' ', args) : "https://aka.ms/wslc"; |
| 21 | |
| 22 | // Everything lives beside the executable — no hard-coded absolute paths. The |
| 23 | // container image tar is produced next to the exe by the build. |
| 24 | string baseDir = AppContext.BaseDirectory; |
| 25 | string sessionPath = Path.Combine(baseDir, "WslcQrStorage"); |
| 26 | string imageTarPath = Path.Combine(baseDir, "customcontainer.tar"); |
| 27 | |
| 28 | if (!File.Exists(imageTarPath)) |
| 29 | { |
| 30 | Console.Error.WriteLine($"[wslc] Image tar not found: {imageTarPath}"); |
| 31 | Console.Error.WriteLine("[wslc] Build the project first so the custom image is auto-built."); |
| 32 | return 1; |
| 33 | } |
| 34 | |
| 35 | int exitCode = 1; |
| 36 | using var stopEvent = new ManualResetEventSlim(false); |
| 37 | var consoleLock = new object(); |
| 38 | using Stream stdout = Console.OpenStandardOutput(); |
| 39 | using Stream stderr = Console.OpenStandardError(); |
| 40 | |
| 41 | void Write(Stream target, byte[] data) |
| 42 | { |
| 43 | lock (consoleLock) |
| 44 | { |
| 45 | target.Write(data, 0, data.Length); |
| 46 | target.Flush(); |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | try |
| 51 | { |
| 52 | // ---- Session ---- |
| 53 | Console.Error.WriteLine("[wslc] Creating session..."); |
| 54 | var sessionSettings = new SessionSettings("WSLCCustomContainer", sessionPath) |
| 55 | { |
| 56 | CpuCount = 2, |
| 57 | MemorySizeInMB = 2048, |
| 58 | VhdRequirements = new VhdOptions(string.Empty, 4UL * 1024 * 1024 * 1024, VhdType.Dynamic), |
| 59 | }; |
| 60 | |
| 61 | using var session = new Session(sessionSettings); |
| 62 | session.Start(); |
| 63 | |
| 64 | // ---- Load the locally built image from the tar (no registry pull) ---- |
| 65 | Console.Error.WriteLine($"[wslc] Loading image from {Path.GetFileName(imageTarPath)}..."); |
| 66 | session.LoadImage(imageTarPath); |
| 67 | |
| 68 | // ---- Create & start container ---- |
| 69 | Console.Error.WriteLine("[wslc] Starting container..."); |
| 70 | |
| 71 | // The init process keeps the container alive while we exec our tool. |
| 72 | var initProcess = new ProcessSettings |
| 73 | { |
| 74 | CommandLine = new List<string> { "/bin/sleep", "infinity" }, |
| 75 | }; |
| 76 | |
| 77 | var containerSettings = new ContainerSettings(imageName) |
| 78 | { |
| 79 | InitProcess = initProcess, |
| 80 | EnableAutoRemove = true, |
| 81 | }; |
| 82 | |
| 83 | using var container = session.CreateContainer(containerSettings); |
| 84 | container.Start(); |
| 85 | |
| 86 | // ---- Exec the QR tool, passing the text to encode ---- |
| 87 | Console.Error.WriteLine($"[wslc] Generating QR code for: {text}"); |
| 88 | Console.Error.WriteLine(); |
| 89 | |
| 90 | var processSettings = new ProcessSettings |
| 91 | { |
| 92 | CommandLine = new List<string> { "python", "/app/qr.py", text }, |
| 93 | OutputMode = ProcessOutputMode.Event, |
| 94 | }; |
| 95 | |
| 96 | using var process = container.CreateProcess(processSettings); |
| 97 | process.OutputReceived += data => Write(stdout, data); |
| 98 | process.ErrorReceived += data => Write(stderr, data); |
| 99 | process.Exited += code => |
| 100 | { |
| 101 | exitCode = code; |
| 102 | stopEvent.Set(); |
| 103 | }; |
| 104 | |
| 105 | process.Start(); |
| 106 | stopEvent.Wait(); |
| 107 | |
| 108 | Console.Error.WriteLine("[wslc] Shutting down..."); |
| 109 | container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10)); |
| 110 | session.Terminate(); |
| 111 | } |
| 112 | catch (Exception ex) |
| 113 | { |
| 114 | Console.Error.WriteLine($"[wslc] Error: {ex.Message}"); |
| 115 | } |
| 116 | |
| 117 | return exitCode; |