Metadata-Version: 2.5
Name: compose-manager
Version: 0.1.0
Summary: Interactive terminal dashboard for remote Docker Compose stacks
Author: Samir Koirala
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: textual<9,>=1.0
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# Compose Manager

Compose Manager includes a pip-installable terminal dashboard and an open-source web UI for managing Docker Compose stacks on a remote server over SSH. It discovers compose projects, shows their status and ports, and lets you bring services up, down, or inspect logs from a clean browser interface.


## Terminal dashboard (Python package)

Requires Python 3.10+, the `ssh` executable on your local machine, and Docker
Compose v2 on the remote host. The SSH user must have permission to run Docker.
No Node.js, browser, or local Docker installation is required for the terminal UI.

Install from this checkout (the new Python package has not been published to PyPI):

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install .
compose-manager
```

For development, use `pip install -e '.[dev]'` instead. You can also install this
checkout with `pipx install .` if you use pipx for terminal applications.

On first launch, fill in the connection form:

- Profile name and host: IP address, DNS name, or existing SSH alias.
- SSH username: leave blank to use SSH defaults.
- SSH port: leave blank to use SSH configuration (otherwise the SSH default is 22).
- Key path: optional; leave blank to use `ssh-agent` or default SSH keys.
- Project directories: comma-separated absolute remote paths, searched four levels deep.
- Refresh interval: at least two seconds.
- Use jump host: optional; enabling it reveals jump host, username, port, and
  a separate optional jump key path. Both hops must be able to authenticate.

A private IP does **not** automatically enable a jump host. Direct SSH works when
the host is reachable through your VPN or local network. Use a jump host when
that is the route needed to reach the target.

Profiles are saved to `~/.config/compose-manager/profiles.json` (or under
`XDG_CONFIG_HOME`) with file permissions `0600`. Only settings and key paths are
saved, never private key contents. Use Add host and Edit to manage connections.
Existing `~/.ssh/config` and SSH agent settings are used by OpenSSH.

Before first use, connect with normal SSH to verify and accept the host key, and
load passphrase-protected keys into your agent (`ssh-add /path/to/key`). Background
commands use batch mode: they do not prompt for passwords, passphrases, or unknown
host keys. For a jump connection, verify both hosts with your normal SSH workflow.
If your public key is already authorized on the server, the corresponding private
key must still be available on the machine running Compose Manager or its agent.

Select a host, then select a container row with the mouse or keyboard:

| Control | Behavior |
|---|---|
| Up stack | Runs `docker compose up -d` for the selected Compose file. |
| Stop | Stops the selected service; for an empty stack row, stops the stack. |
| Down stack | Confirms, then removes stack containers and networks; does not pass `--volumes`. |
| Restart | Restarts the selected service, or the stack for an empty row. |
| Logs | Shows the latest 100 log lines for the service or stack. |
| Refresh / `r` | Rediscovers Compose files and updates container status. |
| Test SSH | Checks SSH authentication and remote Compose availability. |
| Add host / `n` | Opens a new connection form. |
| `q` | Quits. |

The table shows running and stopped containers, health, and published ports.
Stacks without containers appear as `down` rows and can be brought up. Scheduled
refreshes update status without repeating discovery; Refresh discovers new stacks.
Command output streams into the bottom panel, and failed commands report failure.
Mouse clicks require a terminal with mouse support; keyboard navigation is also
available. Port-conflict analysis remains available in the existing web UI.

## Python development and releases

```bash
pip install -e '.[dev]'
pytest -q
python -m build
```

The build produces a wheel and source distribution under `dist/`. The Python CI
workflow tests supported Python versions and builds both artifacts.

### Publish through GitHub Actions and the PyPI web UI

`.github/workflows/pypi-publish.yml` uses PyPI Trusted Publishing. No API token or
GitHub repository secret is needed.

1. Commit and push the package and workflows to `samirkoirala/compose-manager`
   on GitHub's `main` branch.
2. In GitHub repository **Settings → Environments**, create an environment named
   `pypi`. You may add required reviewers if you want a release approval step.
3. Sign in to PyPI and open [Publishing](https://pypi.org/manage/account/publishing/).
   For the first release, add a **pending publisher** with these exact values:

   | PyPI field | Value |
   |---|---|
   | PyPI project name | `compose-manager` |
   | Owner | `samirkoirala` |
   | Repository name | `compose-manager` |
   | Workflow name | `pypi-publish.yml` |
   | Environment name | `pypi` |

   The workflow field is the filename, not the workflow's display name or full
   path. If the project already belongs to your PyPI account, add the same
   publisher under the project's **Manage → Publishing** page instead.
4. In GitHub **Actions → Publish Python package to PyPI → Run workflow**, select
   `main`. This publishes the current package version after tests, a build, and
   strict metadata validation pass. Approve the environment if reviewers were
   configured. The first successful upload creates the PyPI project.
5. After publishing, users can run `pip install compose-manager` or
   `pipx install compose-manager`, then `compose-manager`.

For subsequent releases, update the version in both `pyproject.toml` and
`src/compose_manager/__init__.py`, commit and push, then either run the workflow
manually on `main` or publish a GitHub release with tag `python-vX.Y.Z` (for example,
`python-v0.1.0` for the first version). The release tag must match the package
version. Re-uploading an already published version fails; increment it first.
Docker's integer `vN` tags do not trigger Python publishing. Tests and builds run
without publishing credentials; only the separate publishing job has OIDC access.

See the official [PyPI Trusted Publishing setup guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
and [publishing guide](https://docs.pypi.org/trusted-publishers/using-a-publisher/).


## Features

- Discover `docker-compose.yml`, `docker-compose.yaml`, `compose.yml`, and `compose.yaml` files on remote servers.
- Manage multiple VMs or a single host from one dashboard.
- View running status, service counts, published ports, and port conflicts.
- Start and stop stacks with streaming terminal output.
- Inspect the last 50 log lines for any project.
- Run as a Docker container with a simple `docker compose up` workflow.

## Docker Hub Image

The published image name is:

`samirkoirala/compose-manager`

Versioning is automated by GitHub Actions:

- Each merge to `main` builds and pushes a new image.
- The workflow auto-increments the latest `vN` tag and publishes the next release as `vN+1`.
- The same release is also pushed as `latest` and as a commit-specific `sha-...` tag.

If the repository has no prior release tags, the workflow starts from the next integer version. With the existing `v1` Docker Hub tag, the next release will be `v2`.

## Quick Start

1. Create a `.env` file in the project root.
2. Configure your SSH credentials and target server.
3. Start the app.

```bash
docker compose up -d --build
```

Open `http://localhost:3000` in your browser.

## Configuration

All runtime configuration is loaded from `.env`.

### Server Connection

| Variable | Example | Description |
|---|---|---|
| `VM_SERVERS` | `10.x.x.x,10.x.x.x` | Optional comma-separated list of VMs shown in the dropdown. |
| `SERVER_IP` | `10.xx.xx.xx` | Fallback target server if `VM_SERVERS` is not set. |
| `SERVER_SSH_PORT` | `22` | Target SSH port. |
| `SERVER_USERNAME` | `ubuntu` | SSH username on the target server. |
| `PORT` | `3000` | Local web port for the app container. |

### Jump Host

| Variable | Example | Description |
|---|---|---|
| `JUMP_HOST_IP` | `2x.xx.xxx.x` | Optional jump host IP or hostname. Leave empty for direct SSH. |
| `JUMP_HOST_USER` | `ubuntu` | SSH username for the jump host. |
| `JUMP_HOST_KEY_PATH` | `/keys/jump.pem` | Optional separate private key for the jump host. |
| `JUMP_HOST_PORT` | `2345` | SSH port for the jump host. |

### Project Discovery

| Variable | Example | Description |
|---|---|---|
| `PROJECTS_BASE_DIRS` | `/home/ubuntu/projects,/srv/projects` | Comma-separated directories searched on the remote host. |

### SSH Key Options

Set `SSH_KEY_PATH` to use a custom key path in the web container. Alternatively,
mount a default key or supply key contents below. If no explicit/default key file
is present, the backend can use `SSH_AUTH_SOCK`; using a host agent in Docker also
requires mounting its socket into the container.

You can provide the SSH private key in either of these ways:

#### Option A: Mount a file

```yaml
volumes:
  - ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro
```

#### Option B: Use an environment variable

Set `SSH_PRIVATE_KEY` in `.env` with either raw key contents or a base64-encoded key.

## Local Development

```bash
docker compose up --build
```

The backend serves the frontend from `frontend/public`, so there is no separate build step for the UI.

## Use the published Docker image directly

You can run the published Docker image from Docker Hub without building locally. This is useful for quick deployments or using the image inside orchestration systems.

Run with `docker run` (example):

```bash
docker run --rm -p 3000:3000 \
  -e SERVER_IP=10.0.0.10 \
  -e SERVER_USERNAME=ubuntu \
  -e PROJECTS_BASE_DIRS=/home/ubuntu/projects \
  -v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
  samirkoirala/compose-manager:latest
```

Or reference it from your own `docker-compose.yml`:

```yaml
services:
  compose-manager:
    image: samirkoirala/compose-manager:latest # or a specific tag like v2
    container_name: compose-manager
    env_file: .env
    ports:
      - "3000:3000"
    volumes:
      - ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro
    restart: unless-stopped
```

Notes:
- Put your runtime variables (SSH user, server IP(s), jump host, and `PROJECTS_BASE_DIRS`) into the `.env` file referenced by `env_file` above.
- If you prefer not to mount a private key, set `SSH_PRIVATE_KEY` in `.env` with the raw key contents (or base64-encoded) — the container writes it to a secure temp file at runtime.


## Repository Structure

```text
.
├── backend/
│   ├── package.json
│   └── server.js
├── frontend/
│   └── public/
│       └── index.html
├── Dockerfile
├── docker-compose.yml
├── LICENSE
└── README.md
```

## Release Workflow

The GitHub Actions workflow in `.github/workflows/docker-publish.yml` is responsible for:

- Building the Docker image.
- Pushing the image to Docker Hub.
- Publishing versioned tags such as `v2`, `v3`, and so on.
- Updating `latest` to always point at the newest release.

To enable publishing, add these repository secrets in GitHub:

- `DOCKERHUB_USERNAME`
- `DOCKERHUB_TOKEN`

## Contributing

Pull requests and issues are welcome. Please keep changes focused and include enough detail for maintainers to review behavior changes quickly.

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.