C++ Rewrite!

This commit is contained in:
2025-10-18 23:38:32 +00:00
parent de0a65dd81
commit 797824a417
5 changed files with 379 additions and 78 deletions

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 MARKMENTAL Copyright (c) 2025 MARKMENTAL
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

131
README.md Normal file
View File

@@ -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 containers 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 Containers 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 — its 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 youd 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.*
---

3
compile.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/ash
g++ -std=c++17 main.cpp -o tux-dock && echo "tux-dock successfully compiled!"

244
main.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <sstream>
#include <array>
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<pair<string, string>> getContainerList() const;
string selectContainer(const string& prompt);
};
// ---------------- Core Utility ----------------
void DockerManager::runCommand(const string& cmd) {
system(cmd.c_str());
}
vector<pair<string, string>> DockerManager::getContainerList() const {
vector<pair<string, string>> containers;
array<char, 256> 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<int>(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<string> 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 containers 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<char, 128> 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";
}
}
}

View File

@@ -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