Deploy: pod install scaffold -- supervisor entry-point, postinstall, packaging
Network-agnostic, air-gapped pod install for the two DOSBox titles (BT/RP 4.10), fitting the existing TeslaConsole/TeslaLauncher pod-bay architecture. emulator/DEPLOYMENT-PLAN.md Full design: bridge on the one NIC (pods form a source-proven P2P TCP mesh, so NAT/slirp is out); launcher keeps the bay IP, the DOSBox guest bridges at bayIP+100 (egg/mesh/game endpoint); two-edit +100 convention (postinstall + console DOSBox flag); static, air-gapped, <=32 pods. emulator/pod-launch/ (C# net8 supervising entry-point) Creates a Job Object (KILL_ON_JOB_CLOSE), launches DOSBox-X + the render bridge into it and blocks -- kill this process and both die (kernel-enforced, even on a hard TerminateProcess of a hung session; verified). Mode dispatch: bt/rp wired (also serve camera + live mission-review via egg hostType); review + diagnostics recognized but fail clean until their DOS launch args are known. emulator/deploy/ (install side) postinstall.bat (thin elevated wrapper) + configure.ps1 (NIC detect, realnic bind as a contiguous letter-leading GUID fragment, game IP = bayIP+100, stable MAC, render net_*.conf templates, stamp WATTCP.CFG my_ip) + tokenized conf templates + package.ps1 (assemble the zip). Render/bind/stamp + packaging verified against a scratch tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// A Windows Job Object configured to KILL every process in it the instant the
|
||||
// job's last handle closes. This process holds that handle for the whole
|
||||
// session, so when it dies -- gracefully, on Ctrl-C, or on a hard
|
||||
// TerminateProcess by TeslaLauncher -- DOSBox and the render bridge die too.
|
||||
// Kernel-enforced; no cooperative shutdown required (the hung-DOSBox case).
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VwePod
|
||||
{
|
||||
internal sealed class JobObject : IDisposable
|
||||
{
|
||||
private IntPtr _handle;
|
||||
private bool _disposed;
|
||||
|
||||
public JobObject()
|
||||
{
|
||||
_handle = CreateJobObject(IntPtr.Zero, null);
|
||||
if (_handle == IntPtr.Zero)
|
||||
throw new InvalidOperationException("CreateJobObject failed: " + Marshal.GetLastWin32Error());
|
||||
|
||||
var ext = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
{
|
||||
BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
||||
}
|
||||
};
|
||||
|
||||
int len = Marshal.SizeOf(ext);
|
||||
IntPtr p = Marshal.AllocHGlobal(len);
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(ext, p, false);
|
||||
if (!SetInformationJobObject(_handle, JobObjectExtendedLimitInformation, p, (uint)len))
|
||||
throw new InvalidOperationException("SetInformationJobObject failed: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(p); }
|
||||
}
|
||||
|
||||
// Assign a process to the job. Any process the job member later spawns is
|
||||
// automatically in the job too, so a whole tree is captured.
|
||||
public void Add(IntPtr processHandle)
|
||||
{
|
||||
if (!AssignProcessToJobObject(_handle, processHandle))
|
||||
throw new InvalidOperationException("AssignProcessToJobObject failed: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
if (_handle != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(_handle); // last handle -> KILL_ON_JOB_CLOSE fires
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- native ----
|
||||
private const int JobObjectExtendedLimitInformation = 9;
|
||||
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
public long PerProcessUserTimeLimit;
|
||||
public long PerJobUserTimeLimit;
|
||||
public uint LimitFlags;
|
||||
public UIntPtr MinimumWorkingSetSize;
|
||||
public UIntPtr MaximumWorkingSetSize;
|
||||
public uint ActiveProcessLimit;
|
||||
public UIntPtr Affinity;
|
||||
public uint PriorityClass;
|
||||
public uint SchedulingClass;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct IO_COUNTERS
|
||||
{
|
||||
public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount;
|
||||
public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
{
|
||||
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
||||
public IO_COUNTERS IoInfo;
|
||||
public UIntPtr ProcessMemoryLimit;
|
||||
public UIntPtr JobMemoryLimit;
|
||||
public UIntPtr PeakProcessMemoryUsed;
|
||||
public UIntPtr PeakJobMemoryUsed;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetInformationJobObject(IntPtr hJob, int infoClass, IntPtr lpInfo, uint cbInfoLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user