Add container restart capability from admin UI

- Added POST /api/admin/restart endpoint using Docker socket
- Mounts Docker socket read-only in docker-compose.yml
- Admin UI now has working 'Restart Container' button
- Auto-reloads page after container comes back up
- Falls back to manual restart instructions if socket unavailable
This commit is contained in:
2026-02-03 15:05:43 -05:00
parent 3826f29019
commit 3ddf51924b
3 changed files with 139 additions and 2 deletions

View File

@@ -502,6 +502,92 @@ public class AdminController : ControllerBase
return Ok(new { message = "Cache cleared", filesDeleted = clearedFiles });
}
/// <summary>
/// Restart the allstarr container to apply configuration changes
/// </summary>
[HttpPost("restart")]
public async Task<IActionResult> RestartContainer()
{
_logger.LogInformation("Container restart requested from admin UI");
try
{
// Use Docker socket to restart the container
var socketPath = "/var/run/docker.sock";
if (!System.IO.File.Exists(socketPath))
{
_logger.LogWarning("Docker socket not available at {Path}", socketPath);
return StatusCode(503, new {
error = "Docker socket not available",
message = "Please restart manually: docker-compose restart allstarr"
});
}
// Get container ID from hostname (Docker sets hostname to container ID by default)
// Or use the well-known container name
var containerId = Environment.MachineName;
var containerName = "allstarr";
_logger.LogInformation("Attempting to restart container {ContainerId} / {ContainerName}", containerId, containerName);
// Create Unix socket HTTP client
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
{
var socket = new System.Net.Sockets.Socket(
System.Net.Sockets.AddressFamily.Unix,
System.Net.Sockets.SocketType.Stream,
System.Net.Sockets.ProtocolType.Unspecified);
var endpoint = new System.Net.Sockets.UnixDomainSocketEndPoint(socketPath);
await socket.ConnectAsync(endpoint, cancellationToken);
return new System.Net.Sockets.NetworkStream(socket, ownsSocket: true);
}
};
using var dockerClient = new HttpClient(handler)
{
BaseAddress = new Uri("http://localhost")
};
// Try to restart by container name first, then by ID
var response = await dockerClient.PostAsync($"/containers/{containerName}/restart?t=5", null);
if (!response.IsSuccessStatusCode)
{
// Try by container ID
response = await dockerClient.PostAsync($"/containers/{containerId}/restart?t=5", null);
}
if (response.IsSuccessStatusCode)
{
_logger.LogInformation("Container restart initiated successfully");
return Ok(new { message = "Restarting container...", success = true });
}
else
{
var errorBody = await response.Content.ReadAsStringAsync();
_logger.LogError("Failed to restart container: {StatusCode} - {Body}", response.StatusCode, errorBody);
return StatusCode((int)response.StatusCode, new {
error = "Failed to restart container",
message = "Please restart manually: docker-compose restart allstarr"
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error restarting container");
return StatusCode(500, new {
error = "Failed to restart container",
details = ex.Message,
message = "Please restart manually: docker-compose restart allstarr"
});
}
}
/// <summary>
/// Initialize cookie date to current date if cookie exists but date is not set
/// </summary>

View File

@@ -1146,8 +1146,57 @@
}
}
function restartContainer() {
alert('To apply configuration changes, please restart the container manually via Dockge or docker-compose:\n\ndocker-compose restart allstarr\n\nor use Dockge UI to restart the stack.');
async function restartContainer() {
if (!confirm('Restart the container to apply configuration changes?\n\nThe dashboard will be temporarily unavailable.')) {
return;
}
try {
showToast('Restarting container...', 'success');
const res = await fetch('/api/admin/restart', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast('Container restarting... Page will reload shortly.', 'success');
// Wait a bit then start checking if the server is back
setTimeout(() => {
checkServerAndReload();
}, 3000);
} else {
showToast(data.message || data.error || 'Failed to restart', 'error');
}
} catch (error) {
showToast('Failed to restart container', 'error');
}
}
async function checkServerAndReload() {
let attempts = 0;
const maxAttempts = 30; // Try for 30 seconds
const checkHealth = async () => {
try {
const res = await fetch('/api/admin/status', {
method: 'GET',
cache: 'no-store'
});
if (res.ok) {
window.location.reload();
return;
}
} catch (e) {
// Server still restarting
}
attempts++;
if (attempts < maxAttempts) {
setTimeout(checkHealth, 1000);
} else {
showToast('Server may still be restarting. Please refresh manually.', 'warning');
}
};
checkHealth();
}
function openAddPlaylist() {