mirror of
https://github.com/acedanger/shell.git
synced 2026-03-24 18:01:49 -07:00
Compare commits
2 Commits
ddf83a6564
...
c5760f6dad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5760f6dad | ||
|
|
23ab917542 |
9
.github/prompts/docker-manager-python.prompt.md
vendored
Normal file
9
.github/prompts/docker-manager-python.prompt.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Create a Python command-line application in the `~/shell/docker-manager/` directory to manage Docker containers defined in subdirectories of `~/docker/`. Each subdirectory contains either a `docker-compose.yml` or `compose.yml` file. The application must:
|
||||
|
||||
- List the names of currently running containers defined in `~/docker/`.
|
||||
- Provide commands to stop, update (pull latest images), and restart these containers.
|
||||
- Be executable from any directory via the command line, without requiring navigation to the script's location.
|
||||
- Output results and status messages to the console.
|
||||
- Include setup instructions for installation and usage.
|
||||
|
||||
Provide complete Python code and a `README.md` with setup and usage instructions. Use only files within `~/shell/docker-manager/` to avoid cluttering other directories. Reference the [Docker Compose CLI documentation](https://docs.docker.com/compose/reference/) for command usage.
|
||||
95
docker-manager/README.md
Normal file
95
docker-manager/README.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Docker Manager
|
||||
|
||||
A Python command-line application to manage Docker containers defined in subdirectories of `~/docker/`.
|
||||
|
||||
## Features
|
||||
|
||||
- **List**: View currently running containers across all your projects.
|
||||
- **Stop**: Stop containers for a specific project or all projects.
|
||||
- **Update**: Pull the latest images and recreate containers (equivalent to `docker compose pull && docker compose up -d`).
|
||||
- **Restart**: Restart containers for a specific project or all projects.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3
|
||||
- Docker and Docker Compose (plugin) installed.
|
||||
- A `~/docker/` directory containing subdirectories for each of your projects.
|
||||
- Each project subdirectory must contain a `docker-compose.yml` or `compose.yml` file.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Ensure the script is executable:**
|
||||
```bash
|
||||
chmod +x ~/shell/docker-manager/docker-manager.py
|
||||
```
|
||||
|
||||
2. **Make it accessible from anywhere:**
|
||||
You can create a symbolic link to a directory in your `$PATH` (e.g., `~/.local/bin` or `/usr/local/bin`), or add an alias.
|
||||
|
||||
**Option A: Symbolic Link (Recommended)**
|
||||
```bash
|
||||
mkdir -p ~/.local/bin
|
||||
ln -s ~/shell/docker-manager/docker-manager.py ~/.local/bin/dm
|
||||
```
|
||||
*Note: Ensure `~/.local/bin` is in your `$PATH`.*
|
||||
|
||||
**Option B: Alias**
|
||||
Add the following to your `~/.zshrc` or `~/.bashrc`:
|
||||
```bash
|
||||
alias dm='~/shell/docker-manager/docker-manager.py'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run the application using the command name you set up (e.g., `dm`).
|
||||
|
||||
### List Running Containers
|
||||
```bash
|
||||
dm list
|
||||
```
|
||||
|
||||
### Stop Containers
|
||||
Stop a specific project:
|
||||
```bash
|
||||
dm stop project_name
|
||||
```
|
||||
Stop all projects:
|
||||
```bash
|
||||
dm stop --all
|
||||
```
|
||||
|
||||
### Update Containers
|
||||
Pull latest images and recreate containers for a specific project:
|
||||
```bash
|
||||
dm update project_name
|
||||
```
|
||||
Update all projects:
|
||||
```bash
|
||||
dm update --all
|
||||
```
|
||||
|
||||
### Restart Containers
|
||||
Restart a specific project:
|
||||
```bash
|
||||
dm restart project_name
|
||||
```
|
||||
Restart all projects:
|
||||
```bash
|
||||
dm restart --all
|
||||
```
|
||||
|
||||
## Directory Structure Example
|
||||
|
||||
The tool expects a structure like this:
|
||||
|
||||
```
|
||||
~/docker/
|
||||
├── media-server/
|
||||
│ └── docker-compose.yml
|
||||
├── web-app/
|
||||
│ └── compose.yml
|
||||
└── database/
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
In this example, the project names are `media-server`, `web-app`, and `database`.
|
||||
272
docker-manager/docker-manager.py
Executable file
272
docker-manager/docker-manager.py
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Docker Manager Application
|
||||
|
||||
This script manages Docker containers defined in subdirectories of `~/docker/`.
|
||||
Each subdirectory is treated as a project and must contain a `docker-compose.yml` or `compose.yml`.
|
||||
|
||||
Usage Examples:
|
||||
1. List all running containers:
|
||||
$ dm list
|
||||
|
||||
2. Update a specific project (pulls latest images and recreates containers):
|
||||
$ dm update media-server
|
||||
|
||||
3. Stop all projects (with confirmation prompt):
|
||||
$ dm stop
|
||||
|
||||
4. Restart all projects without confirmation:
|
||||
$ dm restart --all
|
||||
|
||||
5. Stop a specific project:
|
||||
$ dm stop web-app
|
||||
|
||||
Purpose:
|
||||
- Simplifies management of multiple Docker Compose projects.
|
||||
- Provides a unified interface for common operations (list, stop, update, restart).
|
||||
- Eliminates the need to navigate to specific directories to run docker commands.
|
||||
|
||||
To make the command available as dm from any directory, run:
|
||||
chmod +x ~/shell/docker-manager/docker-manager.py
|
||||
mkdir -p ~/.local/bin
|
||||
ln -s ~/shell/docker-manager/docker-manager.py ~/.local/bin/dm
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Configuration
|
||||
DOCKER_ROOT = Path(os.path.expanduser("~/docker"))
|
||||
|
||||
|
||||
def get_projects():
|
||||
"""Scan DOCKER_ROOT for subdirectories with compose files."""
|
||||
projects = {}
|
||||
if not DOCKER_ROOT.exists():
|
||||
print(f"Error: Directory {DOCKER_ROOT} does not exist.")
|
||||
return projects
|
||||
|
||||
for item in DOCKER_ROOT.iterdir():
|
||||
if item.is_dir():
|
||||
if (item / "docker-compose.yml").exists() or (item / "compose.yml").exists():
|
||||
projects[item.name] = item
|
||||
return projects
|
||||
|
||||
|
||||
def run_command(cmd, cwd, capture_output=False):
|
||||
"""Run a shell command in a specific directory."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=capture_output
|
||||
)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
if capture_output:
|
||||
return e
|
||||
print(f"Error running command: {' '.join(cmd)}")
|
||||
print(f"cwd: {cwd}")
|
||||
return None
|
||||
|
||||
|
||||
def list_containers(projects):
|
||||
"""List running containers for all projects."""
|
||||
print(f"{'Project':<20} | {'Container Name':<40} | {'State':<10}")
|
||||
print("-" * 75)
|
||||
|
||||
found_any = False
|
||||
for name, path in sorted(projects.items()):
|
||||
# Get running container names
|
||||
cmd = ["docker", "compose", "ps", "--format",
|
||||
"{{.Names}}", "--filter", "status=running"]
|
||||
res = run_command(cmd, path, capture_output=True)
|
||||
|
||||
if res and res.returncode == 0:
|
||||
containers = res.stdout.strip().split('\n')
|
||||
containers = [c for c in containers if c] # filter empty
|
||||
if containers:
|
||||
found_any = True
|
||||
for container in containers:
|
||||
print(f"{name:<20} | {container:<40} | Running")
|
||||
|
||||
if not found_any:
|
||||
print("No running containers found in managed projects.")
|
||||
|
||||
|
||||
def is_project_running(path):
|
||||
"""Check if any containers in the project are running."""
|
||||
cmd = ["docker", "compose", "ps", "--format",
|
||||
"json", "--filter", "status=running"]
|
||||
res = run_command(cmd, path, capture_output=True)
|
||||
if res and res.returncode == 0:
|
||||
# If output is not empty/just brackets, something is running
|
||||
output = res.stdout.strip()
|
||||
return output and output != "[]"
|
||||
return False
|
||||
|
||||
|
||||
def manage_project(projects, action, target, extra_args=None):
|
||||
"""
|
||||
Execute the specified action (stop, update, restart, logs) on target project(s).
|
||||
|
||||
Args:
|
||||
projects (dict): Dictionary of project names to their paths.
|
||||
action (str): The action to perform ('stop', 'update', 'restart', 'logs').
|
||||
target (str): The target project name or 'all'.
|
||||
extra_args (list): Additional arguments for the command (e.g. for logs).
|
||||
"""
|
||||
|
||||
targets = []
|
||||
# Determine which projects to target
|
||||
if target == "all":
|
||||
targets = sorted(projects.items())
|
||||
elif target in projects:
|
||||
targets = [(target, projects[target])]
|
||||
else:
|
||||
print(f"Error: Project '{target}' not found in {DOCKER_ROOT}")
|
||||
print("Available projects:", ", ".join(sorted(projects.keys())))
|
||||
return
|
||||
|
||||
# Logs are special, usually run on one project interactively
|
||||
if action == "logs":
|
||||
if len(targets) > 1:
|
||||
print("Error: Logs can only be viewed for one project at a time.")
|
||||
return
|
||||
name, path = targets[0]
|
||||
print(f"Viewing logs for {name}...")
|
||||
cmd = ["docker", "compose", "logs"] + (extra_args or [])
|
||||
# For logs, we want to stream output directly to stdout, so no capture_output
|
||||
# and we might want to allow Ctrl+C to exit gracefully
|
||||
try:
|
||||
subprocess.run(cmd, cwd=path, check=False)
|
||||
except KeyboardInterrupt:
|
||||
print("\nLog view stopped.")
|
||||
return
|
||||
|
||||
print(f"Performing '{action}' on {len(targets)} project(s)...")
|
||||
|
||||
for name, path in targets:
|
||||
print(f"\n[{name}] -> {action}...")
|
||||
|
||||
# Execute the requested action
|
||||
if action == "stop":
|
||||
# Stop containers without removing them
|
||||
run_command(["docker", "compose", "stop"], path)
|
||||
elif action == "restart":
|
||||
# Restart containers
|
||||
run_command(["docker", "compose", "restart"], path)
|
||||
elif action == "update":
|
||||
# Update process: Check running -> Stop -> Pull -> Start if was running
|
||||
was_running = is_project_running(path)
|
||||
|
||||
if was_running:
|
||||
print(f" Stopping running containers for {name}...")
|
||||
run_command(["docker", "compose", "stop"], path)
|
||||
|
||||
print(f" Pulling latest images for {name}...")
|
||||
pull_res = run_command(["docker", "compose", "pull"], path)
|
||||
|
||||
if pull_res and pull_res.returncode == 0:
|
||||
if was_running:
|
||||
print(f" Restarting containers for {name}...")
|
||||
run_command(["docker", "compose", "up", "-d"], path)
|
||||
else:
|
||||
print(
|
||||
f" Project {name} was not running. Images updated, but not started.")
|
||||
else:
|
||||
print(f" Failed to pull images for {name}. Skipping update.")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the application."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Manage Docker containers in ~/docker/")
|
||||
subparsers = parser.add_subparsers(
|
||||
dest="command", help="Command to execute")
|
||||
|
||||
# List command configuration
|
||||
subparsers.add_parser("list", help="List running containers")
|
||||
|
||||
# Stop command configuration
|
||||
stop_parser = subparsers.add_parser("stop", help="Stop containers")
|
||||
stop_parser.add_argument("project", nargs="?", default=None,
|
||||
help="Project name (optional). If omitted, asks for confirmation to stop ALL.")
|
||||
stop_parser.add_argument("--all", action="store_true",
|
||||
help="Stop all projects without confirmation prompt if specified")
|
||||
|
||||
# Update command configuration
|
||||
update_parser = subparsers.add_parser(
|
||||
"update", help="Pull and update containers")
|
||||
update_parser.add_argument("project", nargs="?", default=None,
|
||||
help="Project name (optional). If omitted, asks for confirmation to update ALL.")
|
||||
update_parser.add_argument("--all", action="store_true",
|
||||
help="Update all projects without confirmation prompt if specified")
|
||||
|
||||
# Restart command configuration
|
||||
restart_parser = subparsers.add_parser(
|
||||
"restart", help="Restart containers")
|
||||
restart_parser.add_argument("project", nargs="?", default=None,
|
||||
help="Project name (optional). If omitted, asks for confirmation to restart ALL.")
|
||||
restart_parser.add_argument("--all", action="store_true",
|
||||
help="Restart all projects without confirmation prompt if specified")
|
||||
|
||||
# Logs command configuration
|
||||
logs_parser = subparsers.add_parser("logs", help="View container logs")
|
||||
logs_parser.add_argument("project", help="Project name to view logs for")
|
||||
logs_parser.add_argument(
|
||||
"-f", "--follow", action="store_true", help="Follow log output")
|
||||
logs_parser.add_argument(
|
||||
"--tail", default="all", help="Number of lines to show from the end of the logs (default: all)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Scan for projects
|
||||
projects = get_projects()
|
||||
if not projects:
|
||||
print(f"No docker projects found in {DOCKER_ROOT}")
|
||||
return
|
||||
|
||||
# Dispatch commands
|
||||
if args.command == "list":
|
||||
list_containers(projects)
|
||||
elif args.command == "logs":
|
||||
# Prepare extra args for logs
|
||||
extra_args = []
|
||||
if args.follow:
|
||||
extra_args.append("-f")
|
||||
if args.tail:
|
||||
extra_args.extend(["--tail", args.tail])
|
||||
|
||||
manage_project(projects, "logs", args.project, extra_args=extra_args)
|
||||
|
||||
elif args.command in ["stop", "update", "restart"]:
|
||||
target = args.project
|
||||
|
||||
# Handle "all" logic safely with user confirmation
|
||||
if target is None:
|
||||
if args.all:
|
||||
target = "all"
|
||||
else:
|
||||
# Interactive check if user wants to apply to all
|
||||
print(
|
||||
f"No project specified. Do you want to {args.command} ALL projects? (y/N)")
|
||||
choice = input().lower()
|
||||
if choice == 'y':
|
||||
target = "all"
|
||||
else:
|
||||
print("Operation cancelled. Specify a project name or use --all.")
|
||||
return
|
||||
|
||||
manage_project(projects, args.command, target)
|
||||
else:
|
||||
# Show help if no command provided
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -17,7 +17,7 @@ nala // Modern apt frontend
|
||||
fd-find // Modern find alternative (available as 'fd' or 'fdfind')
|
||||
eza // Modern ls alternative
|
||||
|
||||
// Note: lazygit, lazydocker, and fabric require special installation (GitHub releases/scripts)
|
||||
// Note: lazygit and lazydocker require special installation (GitHub releases/scripts)
|
||||
// These are handled separately in the setup script
|
||||
// lazygit
|
||||
// lazydocker
|
||||
Reference in New Issue
Block a user