tuxdock/main.cpp
2025-11-02 08:32:31 -05:00

341 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <sstream>
#include <array>
#include <fstream>
#include <filesystem>
#include <limits>
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 execDetachedCommand();
void createDockerfile();
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::execDetachedCommand() {
string id = selectContainer("Select container to run command in (detached)");
if (id.empty()) return;
// Flush any leftover newline before using getline
cin.ignore(numeric_limits<streamsize>::max(), '\n');
string command;
cout << "Enter command to execute inside the container: ";
getline(cin, command);
if (command.empty()) {
cout << "No command entered. Aborting.\n";
return;
}
string escapedCommand;
escapedCommand.reserve(command.size() * 2);
for (char c : command) {
if (c == '"' || c == '\\')
escapedCommand += '\\';
escapedCommand += c;
}
cout << "Executing command in detached mode...\n";
runCommand("docker exec -d " + id + " /bin/sh -c \"" + escapedCommand + "\"");
cout << "Command dispatched.\n";
}
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;
}
void DockerManager::createDockerfile() {
string baseImage, bashScriptPath, outputFile, imageName;
cout << "Enter base Docker image (e.g., ubuntu:22.04): ";
cin >> baseImage;
cout << "Enter path to bash script to convert (e.g., setup.sh): ";
cin >> bashScriptPath;
if (!filesystem::exists(bashScriptPath)) {
cout << "Error: Bash script not found.\n";
return;
}
cout << "Enter output Dockerfile name (e.g., Dockerfile or Dockerfile_app): ";
cin >> outputFile;
if (outputFile.empty()) outputFile = "Dockerfile";
ifstream scriptFile(bashScriptPath);
ofstream dockerfile(outputFile);
if (!scriptFile.is_open() || !dockerfile.is_open()) {
cout << "Error: Unable to open file(s).\n";
return;
}
// Write Dockerfile header
dockerfile << "FROM " << baseImage << "\n";
dockerfile << "WORKDIR /app\n\n";
dockerfile << "# Auto-generated by Tux-Dock\n";
string line;
while (getline(scriptFile, line)) {
if (line.empty()) continue;
// Skip comments or shebang
if (line.rfind("#", 0) == 0 || line.rfind("#!", 0) == 0)
continue;
dockerfile << "RUN " << line << "\n";
}
dockerfile << "\nCMD [\"/bin/bash\"]\n";
dockerfile.close();
scriptFile.close();
cout << "Dockerfile created successfully: " << outputFile << "\n";
cout << "Enter image name to build from this Dockerfile (e.g., myimage): ";
cin >> imageName;
if (imageName.empty()) {
cout << "No image name provided. Skipping build.\n";
return;
}
cout << "Building Docker image '" << imageName << "'...\n";
runCommand("docker build -t " + imageName + " -f " + outputFile + " .");
cout << "Docker build command executed.\n";
}
// ---------------- 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. Run Detached Command in Container\n"
<< "12. Spin Up MySQL Container\n"
<< "13. Get Container IP Address\n"
<< "14. Create Dockerfile & Build Image from Bash Script\n"
<< "15. 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.execDetachedCommand(); break;
case 12: docker.spinUpMySQL(); break;
case 13: docker.showContainerIP(); break;
case 14: docker.createDockerfile(); break;
case 15:
cout << "Exiting Tux-Dock.\n";
return 0;
default:
cout << "Invalid option.\n";
}
}
}