diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index 8cd8ba8..465cfd5 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -502,6 +502,92 @@ public class AdminController : ControllerBase return Ok(new { message = "Cache cleared", filesDeleted = clearedFiles }); } + /// + /// Restart the allstarr container to apply configuration changes + /// + [HttpPost("restart")] + public async Task 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" + }); + } + } + /// /// Initialize cookie date to current date if cookie exists but date is not set /// diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index 2238004..3fc764a 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -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() { diff --git a/docker-compose.yml b/docker-compose.yml index a8e09a5..2a8d61f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,6 +111,8 @@ services: - ${DOWNLOAD_PATH:-./downloads}:/app/downloads - ${KEPT_PATH:-./kept}:/app/kept - ${CACHE_PATH:-./cache}:/app/cache + # Docker socket for self-restart capability (admin UI only) + - /var/run/docker.sock:/var/run/docker.sock:ro networks: allstarr-network: