master
cs 129 lines 4.33 KB
Raw
1 // WSLC-NextCloud
2 //
3 // A Windows console application that runs a Nextcloud server using the WSL
4 // Container SDK, written in modern C# with the C#/WinRT projection
5 // (Microsoft.WSL.Containers). The container exposes Nextcloud on
6 // http://localhost:8080 with persistent data stored next to the executable.
7
8 using Microsoft.WSL.Containers;
9
10 const string imageName = "nextcloud:latest";
11
12 // Storage lives beside the executable — no hard-coded absolute paths. The
13 // session storage directory must be empty for the session to be created (the
14 // SDK creates and reuses its own VHD inside it), so the persistent Nextcloud
15 // data volume lives in a separate sibling directory.
16 string baseDir = AppContext.BaseDirectory;
17 string sessionPath = Path.Combine(baseDir, "WslcNextcloudStorage");
18 string volumePath = Path.Combine(baseDir, "WslcNextcloudData");
19 Directory.CreateDirectory(volumePath);
20
21 int exitCode = 1;
22 using var stopEvent = new ManualResetEventSlim(false);
23 var consoleLock = new object();
24 using Stream stdout = Console.OpenStandardOutput();
25 using Stream stderr = Console.OpenStandardError();
26
27 void Write(Stream target, byte[] data)
28 {
29 lock (consoleLock)
30 {
31 target.Write(data, 0, data.Length);
32 target.Flush();
33 }
34 }
35
36 try
37 {
38 // ---- Session ----
39 Console.Error.WriteLine("[wslc] Creating session...");
40 var sessionSettings = new SessionSettings("WSLCNextCloud", sessionPath)
41 {
42 CpuCount = 4,
43 MemorySizeInMB = 4096,
44 // Nextcloud image is ~1.5 GB; use a 10 GB dynamic VHD.
45 VhdRequirements = new VhdOptions(string.Empty, 10UL * 1024 * 1024 * 1024, VhdType.Dynamic),
46 };
47
48 using var session = new Session(sessionSettings);
49 session.Start();
50
51 // ---- Pull image ----
52 Console.Error.WriteLine($"[wslc] Pulling image '{imageName}' (this may take several minutes)...");
53 session.PullImage(new PullImageOptions(imageName));
54
55 // ---- Create & start container ----
56 Console.Error.WriteLine("[wslc] Starting container...");
57
58 // The init process keeps the container alive while we exec the entrypoint.
59 var initProcess = new ProcessSettings
60 {
61 CommandLine = new List<string> { "/bin/sleep", "infinity" },
62 };
63
64 var containerSettings = new ContainerSettings(imageName)
65 {
66 InitProcess = initProcess,
67 EnableAutoRemove = true,
68 NetworkingMode = ContainerNetworkingMode.Bridged,
69 // Port mapping: host 8080 -> container 80.
70 PortMappings = new List<ContainerPortMapping> { new(8080, 80, PortProtocol.TCP) },
71 // Persistent data volume: bind-mount only the data directory, not the
72 // entire webroot. Mounting /var/www/html over 9P is extremely slow
73 // because Nextcloud writes thousands of PHP files there during init.
74 Volumes = new List<ContainerVolume> { new(volumePath, "/var/www/html/data", false) },
75 };
76
77 using var container = session.CreateContainer(containerSettings);
78 container.Start();
79
80 // ---- Exec the Nextcloud entrypoint ----
81 Console.Error.WriteLine("[wslc] Launching Nextcloud entrypoint...");
82 var processSettings = new ProcessSettings
83 {
84 CommandLine = new List<string> { "/entrypoint.sh", "apache2-foreground" },
85 OutputMode = ProcessOutputMode.Event,
86 };
87
88 using var process = container.CreateProcess(processSettings);
89 process.OutputReceived += data => Write(stdout, data);
90 process.ErrorReceived += data => Write(stderr, data);
91 process.Exited += code =>
92 {
93 exitCode = code;
94 stopEvent.Set();
95 };
96
97 process.Start();
98
99 Console.Error.WriteLine();
100 Console.Error.WriteLine("[wslc] Nextcloud is running at http://localhost:8080");
101 Console.Error.WriteLine("[wslc] Press Enter to stop...");
102 Console.Error.WriteLine();
103
104 // Stop when the user presses Enter (or the entrypoint exits on its own).
105 var inputThread = new Thread(() =>
106 {
107 Console.ReadLine();
108 if (!stopEvent.IsSet)
109 {
110 exitCode = 0;
111 stopEvent.Set();
112 }
113 })
114 { IsBackground = true };
115 inputThread.Start();
116
117 stopEvent.Wait();
118
119 Console.Error.WriteLine("[wslc] Shutting down...");
120 container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10));
121 session.Terminate();
122 Console.Error.WriteLine("[wslc] Done.");
123 }
124 catch (Exception ex)
125 {
126 Console.Error.WriteLine($"[wslc] Error: {ex.Message}");
127 }
128
129 return exitCode;