C++ Rewrite!
This commit is contained in:
parent
de0a65dd81
commit
797824a417
5 changed files with 379 additions and 78 deletions
2
LICENSE
2
LICENSE
|
|
@ -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
131
README.md
Normal 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 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.*
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
3
compile.sh
Executable file
3
compile.sh
Executable 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
244
main.cpp
Normal 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 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<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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
77
tux-dock.sh
77
tux-dock.sh
|
|
@ -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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue