diff --git a/LICENSE b/LICENSE index b9fbe58..062a2e8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 MARKMENTAL +Copyright (c) 2025 MARKMENTAL Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md new file mode 100644 index 0000000..be06f83 --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +# 🐧 Tux-Dock +### A lightweight C++ Docker management CLI + +Tux-Dock is a simple, modern, and fast **C++17** command-line utility for managing Docker containers and images. +It offers a clean, interactive menu for common Docker operations like pulling images, running containers, and inspecting IP addresses — without the overhead of complex GUIs or scripts. + +--- + +## ✨ Features + +- 🔹 **Interactive container management** — start, stop, remove, or attach to containers with simple numbered menus. +- 🔹 **Port mapping made clear** — automatically prompts for and explains host ↔ container port bindings. +- 🔹 **Image operations** — pull, list, and delete Docker images. +- 🔹 **Quick MySQL setup** — spin up a MySQL container with version, password, and port configuration in seconds. +- 🔹 **Get container IP address** — cleanly retrieves and displays only the container’s assigned IP. +- 🔹 **Modern C++ design** — built with classes, minimal dependencies, and clear abstractions. + +--- + +## 🧩 Build Requirements + +- **C++17 or newer** compiler + (e.g., `g++`, `clang++`) +- **Docker Engine** installed and running + (tested on Debian 12/13, Alpine Linux, and Arch Linux) + +--- + +## ⚙️ Build & Run + +```bash +# Clone the repo +git clone https://github.com/markmental/tuxdock.git +cd tuxdock + +# Build the binary +g++ -std=c++17 tux-dock.cpp -o tux-dock + +# Run it (requires Docker permissions) +sudo ./tux-dock +```` + +--- + +## 🖥️ Menu Overview + +``` +Tux-Dock: Docker Management Menu +---------------------------------- +1. Pull Docker Image +2. Run/Create Interactive Container +3. List All Containers +4. List All Images +5. Start Container Interactively (boot new session) +6. Start Detached Container Session +7. Delete Docker Image +8. Stop Container +9. Remove Container +10. Attach Shell to Running Container +11. Spin Up MySQL Container +12. Get Container IP Address +13. Exit +``` + +Each action guides you through the required steps. +For container-related commands, Tux-Dock automatically lists available containers and lets you choose by **number**, not by typing long IDs. + +--- + +## 📡 Example: Getting a Container’s IP Address + +``` +Available Containers: +1. mysql-container (ebaf5dbae393) +2. webapp (fa29b6c1f1e8) + +Select container to view IP (1-2): 2 +Container IP Address: 172.17.0.3 +``` + +--- + +## 🧱 Design Overview + +Tux-Dock is built using a single class: + +```cpp +class DockerManager { + void pullImage(); + void runContainerInteractive(); + void listContainers() const; + void startInteractive(); + void stopContainer(); + void showContainerIP(); +}; +``` + +This makes the codebase **extensible** — adding new Docker features like `docker logs` or `docker stats` requires only a small new method. + +--- + +## 🧠 Why C++? + +C++ was one of my formative languages when I was learning to program — it’s where I first grasped core concepts like memory management, data structures, OOP and abstraction. +Writing Tux-Dock in C++ is both nostalgic and practical: it combines the clarity of modern design with the raw performance and control that first inspired me to code. + +--- + +## 📜 License + +MIT License — free to use, modify, and share. + +--- + +### 💡 Tip + +If you’d like Tux-Dock to run without `sudo`, add your user to the Docker group: + +```bash +sudo usermod -aG docker $USER +``` + +Then log out and back in. + +--- + +**Author:** MARKMENTAL +**Project:** Tux-Dock — *A clean and modern CLI for Docker power users.* + + +--- diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..1d7488d --- /dev/null +++ b/compile.sh @@ -0,0 +1,3 @@ +#!/bin/ash + +g++ -std=c++17 main.cpp -o tux-dock && echo "tux-dock successfully compiled!" diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4cf75d1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; + +class DockerManager { +public: + void pullImage(); + void runContainerInteractive(); + void listContainers() const; + void listImages() const; + void startInteractive(); + void startDetached(); + void deleteImage(); + void stopContainer(); + void removeContainer(); + void execShell(); + void spinUpMySQL(); + void showContainerIP(); + +private: + static void runCommand(const string& cmd); + vector> getContainerList() const; + string selectContainer(const string& prompt); +}; + +// ---------------- Core Utility ---------------- + +void DockerManager::runCommand(const string& cmd) { + system(cmd.c_str()); +} + +vector> DockerManager::getContainerList() const { + vector> containers; + array buffer{}; + string result; + + FILE* pipe = popen("docker ps -a --format '{{.ID}} {{.Names}}'", "r"); + if (!pipe) return containers; + + while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + result = buffer.data(); + stringstream ss(result); + string id, name; + ss >> id >> name; + if (!id.empty() && !name.empty()) + containers.emplace_back(id, name); + } + pclose(pipe); + return containers; +} + +string DockerManager::selectContainer(const string& prompt) { + auto containers = getContainerList(); + if (containers.empty()) { + cout << "No containers available.\n"; + return ""; + } + + cout << "\nAvailable Containers:\n"; + int i = 1; + for (const auto& c : containers) + cout << i++ << ". " << c.second << " (" << c.first.substr(0, 12) << ")\n"; + + int choice; + cout << prompt << " (1-" << containers.size() << "): "; + cin >> choice; + + if (choice < 1 || choice > static_cast(containers.size())) { + cout << "Invalid selection.\n"; + return ""; + } + + return containers[choice - 1].first; +} + +// ---------------- Docker Actions ---------------- + +void DockerManager::pullImage() { + string image; + cout << "Enter Docker image to pull (e.g., alpine): "; + cin >> image; + runCommand("docker pull " + image); +} + +void DockerManager::runContainerInteractive() { + string image; + cout << "Enter Docker image to run interactively (e.g., alpine): "; + cin >> image; + + int portCount; + cout << "How many port mappings? "; + cin >> portCount; + + vector ports; + for (int i = 0; i < portCount; ++i) { + string port; + cout << "Enter mapping #" << (i + 1) + << " (format host:container, e.g., 8080:80): "; + cin >> port; + ports.push_back("-p " + port); + } + + string portArgs; + for (const auto& p : ports) portArgs += p + " "; + + cout << "\nPort Forwarding Explanation:\n" + << " '-p hostPort:containerPort' exposes the container’s port to the host.\n" + << " Example: '-p 8080:80' allows access via http://localhost:8080\n\n"; + + runCommand("docker run -it " + portArgs + image + " /bin/sh"); +} + +void DockerManager::listContainers() const { runCommand("docker ps -a"); } +void DockerManager::listImages() const { runCommand("docker images"); } + +void DockerManager::startInteractive() { + string id = selectContainer("Select container to start interactively"); + if (!id.empty()) runCommand("docker start -ai " + id); +} + +void DockerManager::startDetached() { + string id = selectContainer("Select container to start detached"); + if (!id.empty()) runCommand("docker start " + id); +} + +void DockerManager::deleteImage() { + string image; + cout << "Enter image name or ID to delete: "; + cin >> image; + runCommand("docker rmi " + image); +} + +void DockerManager::stopContainer() { + string id = selectContainer("Select container to stop"); + if (!id.empty()) runCommand("docker stop " + id); +} + +void DockerManager::removeContainer() { + string id = selectContainer("Select container to remove"); + if (!id.empty()) runCommand("docker rm " + id); +} + +void DockerManager::execShell() { + string id = selectContainer("Select running container for shell access"); + if (!id.empty()) runCommand("docker exec -it " + id + " /bin/sh"); +} + +void DockerManager::spinUpMySQL() { + string port, password, version; + cout << "Enter port mapping (e.g., 3306:3306): "; + cin >> port; + cout << "Enter MySQL root password: "; + cin >> password; + cout << "Enter MySQL version tag (e.g., 8): "; + cin >> version; + + cout << "\nLaunching MySQL container (accessible via localhost:" + << port.substr(0, port.find(':')) << ")\n"; + runCommand("docker run -p " + port + + " --name mysql-container -e MYSQL_ROOT_PASSWORD=" + password + + " -d mysql:" + version); +} + +// ---------------- Parsed IP Feature ---------------- + +void DockerManager::showContainerIP() { + string id = selectContainer("Select container to view IP"); + if (id.empty()) return; + + string command = "docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' " + id; + + array buffer{}; + string ip; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) { + cout << "Failed to inspect container.\n"; + return; + } + + if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + ip = buffer.data(); + } + pclose(pipe); + + if (ip.empty() || ip == "\n") + cout << "No IP address found (container may be stopped or not attached to a network).\n"; + else + cout << "Container IP Address: " << ip; +} + +// ---------------- Menu ---------------- + +int main() { + DockerManager docker; + int option; + + while (true) { + cout << "\nTux-Dock: Docker Management Menu\n" + << "----------------------------------\n" + << "1. Pull Docker Image\n" + << "2. Run/Create Interactive Container\n" + << "3. List All Containers\n" + << "4. List All Images\n" + << "5. Start Container Interactively (boot new session)\n" + << "6. Start Detached Container Session\n" + << "7. Delete Docker Image\n" + << "8. Stop Container\n" + << "9. Remove Container\n" + << "10. Attach Shell to Running Container\n" + << "11. Spin Up MySQL Container\n" + << "12. Get Container IP Address\n" + << "13. Exit\n" + << "----------------------------------\n" + << "Choose an option: "; + + cin >> option; + + switch (option) { + case 1: docker.pullImage(); break; + case 2: docker.runContainerInteractive(); break; + case 3: docker.listContainers(); break; + case 4: docker.listImages(); break; + case 5: docker.startInteractive(); break; + case 6: docker.startDetached(); break; + case 7: docker.deleteImage(); break; + case 8: docker.stopContainer(); break; + case 9: docker.removeContainer(); break; + case 10: docker.execShell(); break; + case 11: docker.spinUpMySQL(); break; + case 12: docker.showContainerIP(); break; + case 13: + cout << "Exiting Tux-Dock.\n"; + return 0; + default: + cout << "Invalid option.\n"; + } + } +} + diff --git a/tux-dock.sh b/tux-dock.sh deleted file mode 100644 index 61e84a2..0000000 --- a/tux-dock.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -while true; do - echo "Docker Management Menu" - echo "1. Pull Docker Image" - echo "2. Run/Create Docker Container Interactively" - echo "3. List All Docker Containers" - echo "4. List All Docker Images" - echo "5. Start Interactive Container Session" - echo "6. Start Detached Container Session" - echo "7. Delete Docker Image" - echo "8. Stop Docker Container" - echo "9. Remove Docker Container" - echo "10. Access Interactive Shell of Running Container" - echo "11. Spin Up MySQL Docker Container" - echo "12. Exit" - read -p "Choose an option: " option - - case $option in - 1) - read -p "Enter the Docker image to pull (e.g., alpine): " image - docker pull $image - ;; - 2) - read -p "Enter the Docker image to run interactively (e.g., alpine): " image - read -p "How many port mappings do you want to add? " port_count - ports="" - for ((i=1; i<=port_count; i++)); do - read -p "Enter port mapping #$i (e.g., 8081:8081): " port - ports+="-p $port " - done - docker run -it $ports $image /bin/sh - ;; - 3) - docker ps -a - ;; - 4) - docker images - ;; - 5) - read -p "Enter the container ID to start interactively: " containerid - docker start -ai $containerid - ;; - 6) - read -p "Enter the container ID to start in detached mode: " containerid - docker start $containerid - ;; - 7) - read -p "Enter the Docker image name or ID to delete: " image - docker rmi $image - ;; - 8) - read -p "Enter the container ID to stop: " containerid - docker stop $containerid - ;; - 9) - read -p "Enter the container ID to remove: " containerid - docker rm $containerid - ;; - 10) - read -p "Enter the container ID to access an interactive shell: " containerid - docker exec -it $containerid /bin/sh - ;; - 11) - read -p "Enter the port range (e.g., 3306:3306): " port - read -p "Enter the MySQL root password: " mysql_password - read -p "Enter the MySQL version tag (e.g., 8): " mysql_version - docker run -p $port --name mysql-container -e MYSQL_ROOT_PASSWORD=$mysql_password -d mysql:$mysql_version - ;; - 12) - break - ;; - *) - echo "Invalid option. Please try again." - ;; - esac -done