From c86f2b723e6956a6544bf98dc5011bd303280c6e Mon Sep 17 00:00:00 2001 From: Leonardo Bishop Date: Fri, 25 Apr 2025 01:13:03 +0100 Subject: Restructure repository --- bin/authenticate.php | 35 +++++++++++++++ bin/index.php | 22 ++++++++++ bin/key.php.example | 4 ++ bin/manage.php | 68 ++++++++++++++++++++++++++++ bin/mount.php | 107 +++++++++++++++++++++++++++++++++++++++++++++ bin/serviceDefinitions.php | 52 ++++++++++++++++++++++ bin/status.php | 72 ++++++++++++++++++++++++++++++ bin/styles.css | 50 +++++++++++++++++++++ bin/util.php | 94 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 504 insertions(+) create mode 100644 bin/authenticate.php create mode 100644 bin/index.php create mode 100644 bin/key.php.example create mode 100644 bin/manage.php create mode 100644 bin/mount.php create mode 100644 bin/serviceDefinitions.php create mode 100644 bin/status.php create mode 100644 bin/styles.css create mode 100644 bin/util.php (limited to 'bin') diff --git a/bin/authenticate.php b/bin/authenticate.php new file mode 100644 index 0000000..7decfb5 --- /dev/null +++ b/bin/authenticate.php @@ -0,0 +1,35 @@ + + + + + + Authenticate + + + + + +
+

Authenticate

+
+

Please enter the secret key to continue.

+
+ Key: + +
+
+ diff --git a/bin/index.php b/bin/index.php new file mode 100644 index 0000000..d828e0f --- /dev/null +++ b/bin/index.php @@ -0,0 +1,22 @@ + + + + + Welcome to bongo + + + + + +
+

Welcome to bongo

+ +
+ Get status +
+ diff --git a/bin/key.php.example b/bin/key.php.example new file mode 100644 index 0000000..94610ad --- /dev/null +++ b/bin/key.php.example @@ -0,0 +1,4 @@ + + + + + + Manage container + + + + + +
+

Manage container:

+ Home + Status +
+ name; + //}, $services))) { + // Util\createBanner('✗', "Service '$service' is unknown", 'bad'); + // return; + //} + + $status = Util\getDockerStatus($container); + + if ($status->status === '-') { + Util\createBanner('✗', "Container '$container' not found", 'bad'); + return; + } + + if ($action === 'start' || $action === 'stop' || $action === 'restart' || $action === 'logs') { + // if ($action === 'start' || $action === 'stop' || $action === 'restart') { + $safeService = escapeshellarg($container); + Util\doShellExec('sudo docker ' . $action . ' ' . $safeService, '/manage.php?container=' . $container, $action); + } + + Util\createStatusBanner($status); + ?> +

+

+ Status as reported by Docker + +
+

+ +

+ [Logs] + [Start] + [Stop] + [Restart] +

+
+ diff --git a/bin/mount.php b/bin/mount.php new file mode 100644 index 0000000..fa602d8 --- /dev/null +++ b/bin/mount.php @@ -0,0 +1,107 @@ + + + + + + Mount LUKS device for <?php echo $service ?> + + + + + +
+

Mount LUKS device for

+ Home + Status +
+ luks; + + if ($luksDevice === null) { + Util\createBanner('✗', $service . ' has no LUKS device to mount', 'bad'); + return; + } + + $key = $_POST['key']; + $mount = $_GET['mount']; + + $disk = exec('blkid /dev/' . $luksDevice->deviceName . ' | grep "UUID=\"' . $luksDevice->uuid . '\""'); + $diskOk = !empty($disk); + + $cryptdevice = exec('lsblk -lno NAME,TYPE,MOUNTPOINT /dev/' . $luksDevice->deviceName . ' | grep "' . $luksDevice->mountPoint . '[[:space:]]*crypt"'); + $cryptdeviceOk = !empty($cryptdevice); + + $cryptdeviceMapping = exec('lsblk -lno NAME,TYPE,MOUNTPOINT /dev/' . $luksDevice->deviceName . ' | grep "crypt" | awk \'{print $1}\''); + + $mountpoint = exec('cat /proc/mounts | grep "/dev/mapper/' . $luksDevice->mountPoint . ' /mnt/' . $luksDevice->mountPoint . '"'); + $mountpointOk = !empty($mountpoint); + + if (!empty($key) && $diskOk && !$cryptdeviceOk) { + $safeKey = escapeshellarg($key); + Util\doShellExec('echo ' . $safeKey . ' | sudo cryptsetup --verbose luksOpen /dev/' . $luksDevice->deviceName . ' ' . $luksDevice->mountPoint . ' 2>&1', '/mount.php?service=' . $service, 'cryptsetup'); + } + + if (!empty($mount) && $diskOk && $cryptdeviceOk && !$mountpointOk) { + Util\doShellExec('sudo mount -v /dev/mapper/' . $luksDevice->mountPoint . ' /mnt/' . $luksDevice->mountPoint, '/mount.php?service=' . $service, 'mount'); + } + + if (!$diskOk) { + Util\createBanner('✗', '/dev/' . $luksDevice->deviceName . ' is not attached or has incorrect UUID', 'bad'); + echo '

Attach /dev/' . $luksDevice->deviceName . ' with UUID="' . $luksDevice->uuid . '" to continue.

'; + return; + } else { + Util\createBanner('✓', '/dev/' . $luksDevice->deviceName . ' is attached', 'good'); + } + + if (!$cryptdeviceOk) { + if (!empty($cryptdeviceMapping)) { + Util\createBanner('✗', "/dev/" . $luksDevice->deviceName . " has incorrect mapping '" . $cryptdeviceMapping. "'", 'bad'); + echo '

Cannot continue. Close luks device /dev/' . $luksDevice->deviceName . ' first.

'; + return; + } + Util\createBanner('✗', '/dev/' . $luksDevice->deviceName . ' is locked', 'bad'); + echo "

"; + echo "Provide the encryption key for /dev/" . $luksDevice->deviceName . " (" . $luksDevice->uuid . ")"; + echo "

"; + echo "
"; + echo "
"; + echo "Unlock /dev/" . $luksDevice->deviceName . ""; + echo ""; + echo "

"; + echo ""; + echo "
"; + echo "
"; + return; + } else { + Util\createBanner('✓', "/dev/" . $luksDevice->deviceName . " is open and has mapping '" . $luksDevice->mountPoint . "'", 'good'); + } + + if (!$mountpointOk) { + Util\createBanner('✗', '/dev/mapper/' . $luksDevice->mountPoint . ' is not mounted at /mnt/' . $luksDevice->mountPoint, 'bad'); + echo "

Mount /dev/mapper/" . $luksDevice->mountPoint . " at /mnt/" . $luksDevice->mountPoint . ".

"; + echo "

[Mount device]

"; + return; + } else { + Util\createBanner('✓', '/dev/mapper/' . $luksDevice->mountPoint . ' is mounted at /mnt/' . $luksDevice->mountPoint, 'good'); + } + ?> +

There is nothing to do.

+
+ diff --git a/bin/serviceDefinitions.php b/bin/serviceDefinitions.php new file mode 100644 index 0000000..84f2216 --- /dev/null +++ b/bin/serviceDefinitions.php @@ -0,0 +1,52 @@ +name = $name; + $this->prettyName = $prettyName; + $this->containerName = $containerName; + $this->luks = $luks; + } +} + +class LuksDataDisk +{ + public $deviceName; + public $uuid; + public $mountPoint; + + public function __construct($deviceName, $uuid, $mountPoint) + { + $this->deviceName = $deviceName; + $this->uuid = $uuid; + $this->mountPoint = $mountPoint; + } +} + +$services = [ + new ServiceDefinition('vaultwarden', 'Vaultwarden', 'vaultwarden', null), + new ServiceDefinition('nextcloud', 'Nextcloud', ['nextcloud', 'nextcloud_db'], new LuksDataDisk('mmcblk0p3', '19537ab9-d855-416c-99c3-ebc85e02bfbf', 'cloud')), + new ServiceDefinition('jellyfin', 'Jellyfin', 'jellyfin', new LuksDataDisk('sda', '12158df0-2738-4c32-a7b9-36c11dde427f', 'media')), + new ServiceDefinition('minecraft', 'Minecraft server', 'minecraft', null), +]; + +function getServiceDefinition($name) { + global $services; + + $matching = array_filter($services, function ($service) use ($name) { return $service->name === $name; }); + if (count($matching) === 0) { + return null; + } + return reset($matching); +} + +?> + diff --git a/bin/status.php b/bin/status.php new file mode 100644 index 0000000..3789d9e --- /dev/null +++ b/bin/status.php @@ -0,0 +1,72 @@ + + + + + + Bongo status + + + + + +
+

Bongo status

+ Home + +
+ ' . $service->prettyName . ''; + $containers = []; + if (is_array($service->containerName)) { + $containers = $service->containerName; + } else { + $containers = [$service->containerName]; + } + ?> + luks !== null) : ?> + luks; + $mountpoint = exec('cat /proc/mounts | grep "/dev/mapper/' . $luksDevice->mountPoint . ' /mnt/' . $luksDevice->mountPoint . '"'); + if (empty($mountpoint)) { + Util\createBanner('✗', '/dev/mapper/' . $luksDevice->mountPoint . ' is not mounted at /mnt/' . $luksDevice->mountPoint, 'bad'); + } else { + Util\createBanner('✓', '/dev/mapper/' . $luksDevice->mountPoint . ' is mounted at /mnt/' . $luksDevice->mountPoint, 'good'); + } + ?> +

+ [Mount device or provide encryption key] +

+

+

+ Output + + + + +
+

+ + + + isNotFound === false) : ?> +

+ [Manage container] +

+

+

+ Status as reported by Docker + + +
+

+ + + +
+ diff --git a/bin/styles.css b/bin/styles.css new file mode 100644 index 0000000..d984996 --- /dev/null +++ b/bin/styles.css @@ -0,0 +1,50 @@ +html, body { + font-family: sans-serif; + +} +table { + border-collapse: collapse; +} + +th, td { + text-align: left; + padding: 8px; + border: black 1px solid; +} + +th { + background-color: #f2f2f2; +} + +a { + color: #0000EE; +} + +a :visited{ + color: #0000EE; +} + +.status-banner { + background-color: #f2f2f2; + padding: 0 5px; + border: 1px solid #e7e7e7; +} + +.status-banner.good { + background-color: #aaffaa; + border: 1px solid #90ee90; +} + +.status-banner.bad { + background-color: #ffcccb; + border: 1px solid #ff6666; +} + +.container { + max-width: 800px; + margin: 0 auto; +} + +.control-list a { + text-decoration: none; +} \ No newline at end of file diff --git a/bin/util.php b/bin/util.php new file mode 100644 index 0000000..b5cb9c7 --- /dev/null +++ b/bin/util.php @@ -0,0 +1,94 @@ +name = $name; + $this->containerId = $containerId; + $this->status = $status; + $this->startedAt = $startedAt; + $this->finishedAt = $finishedAt; + $this->isNotFound = $isNotFound; + } +} + +function getDockerStatus($containerName): ServiceStatus +{ + $dockerOutput = exec('sudo docker inspect --format=\'{{.Id}} {{.State.Status}} {{.State.StartedAt}} {{.State.FinishedAt}}\' ' . $containerName); + if (empty($dockerOutput)) { + return new ServiceStatus($containerName, '-', '-', '-', '-', true); + } + $parts = explode(' ', $dockerOutput); + $status = new ServiceStatus($containerName, substr($parts[0], 0, 12), $parts[1], $parts[2], $parts[3], false); + return $status; +} + +function createStatusTable(ServiceStatus $status) +{ + echo (''); + echo (''); + echo (''); + echo (''); + echo (''); + echo (''); + echo (''); + echo (''); + echo (''); + echo ('
Container IDNameStatusStarted atFinished at
' . $status->containerId . '' . $status->name . '' . $status->status . '' . $status->startedAt . '' . $status->finishedAt . '
'); +} + +function createStatusBanner(ServiceStatus $status) +{ + if ($status->isNotFound) { + createBanner('✗', "Container '" . $status->name . "' not found", 'bad'); + return; + } + $state = $status->status === 'running' ? 'good' : 'bad'; + $symbol = $status->status === 'running' ? '✓' : '✗'; + createBanner($symbol, "Status of '$status->name' is '$status->status'", $state); +} + +function createBanner($symbol, $message, $state) +{ + echo ('
'); + echo ("

$symbol $message

"); + echo ('
'); +} + +function doShellExec($command, $redirect, $action) +{ + $output = shell_exec($command); + //if (empty($output)) { + // header("Location: $redirect"); + // exit; + //} + echo "

Output of $action

"; + echo "
$output
"; + echo "

[Acknowledge]

"; + exit; +} + +function doSessionCheck($redirect) +{ + if (!isset($_SESSION['token']) || $_SESSION['token'] !== getSuperSecretToken()) { + header('Location: authenticate.php?redirect=/' . $redirect); + exit; + } +} + +include('key.php'); + +function getSuperSecretToken() +{ + global $superSecretToken; + return $superSecretToken; +} -- cgit v1.2.3-70-g09d2