Add README, LICENSE (GPL-3), and update config dir to XDG path
Config moved to ~/.config/portainer-cli/config.yml following XDG convention. README covers installation, configuration, all commands, container name resolution logic, and debugging. Tool noted as AI-generated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2026 Grzegorz Luszczek
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for software and
|
||||||
|
other kinds of works.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not price. Our
|
||||||
|
General Public Licenses are designed to make sure that you have the freedom
|
||||||
|
to distribute copies of free software (and charge for them if you wish), that
|
||||||
|
you receive source code or can get it if you want it, that you can change the
|
||||||
|
software or use pieces of it in new free programs, and that you know you can
|
||||||
|
do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you these
|
||||||
|
rights or asking you to surrender the rights. Therefore, you have certain
|
||||||
|
responsibilities if you distribute copies of the software, or if you modify
|
||||||
|
it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For the full license text, see: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# portainer-cli
|
||||||
|
|
||||||
|
A Ruby command-line tool for interacting with [Portainer](https://www.portainer.io/) via its REST API. List environments, stacks, containers, volumes and networks — and open fully interactive shells inside running containers directly from your terminal.
|
||||||
|
|
||||||
|
> **This tool was fully generated by AI** (Claude, by Anthropic) through an iterative conversation-driven development session.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- List endpoints, stacks, containers, volumes, and networks
|
||||||
|
- Create stacks (from file or inline compose) and containers
|
||||||
|
- **Interactive exec into containers** via WebSocket — works like `docker exec -it` but through Portainer
|
||||||
|
- Smart container name resolution: fuzzy matching using Docker Compose naming conventions (`<stack>-<service>-<N>`)
|
||||||
|
- Automatic endpoint resolution — no need to look up endpoint IDs manually
|
||||||
|
- Supports both API key and JWT token authentication
|
||||||
|
- Configurable SSL verification (useful for self-signed certificates)
|
||||||
|
- `--run CMD` flag for running a specific command (e.g. `rails c`)
|
||||||
|
- `--quiet` flag for scripting and AI agent use cases
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Ruby 3.x
|
||||||
|
- [`websocket`](https://rubygems.org/gems/websocket) gem
|
||||||
|
- A running Portainer instance (v2.x or later)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/grzlus/portainer-cli
|
||||||
|
cd portainer-cli
|
||||||
|
bundle install
|
||||||
|
chmod +x bin/portainer-cli
|
||||||
|
|
||||||
|
# Link into your PATH (adjust the target to wherever suits you)
|
||||||
|
ln -sf "$PWD/bin/portainer-cli" ~/bin/portainer-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli configure
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be prompted for:
|
||||||
|
|
||||||
|
- **Portainer URL** — e.g. `https://portainer.example.com`
|
||||||
|
- **Authentication** — API key (recommended, no expiry) or JWT token
|
||||||
|
- **SSL verification** — answer `n` if your instance uses a self-signed certificate
|
||||||
|
|
||||||
|
Configuration is saved to `~/.config/portainer-cli/config.yml` (mode `0600`).
|
||||||
|
|
||||||
|
### Manual config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ~/.config/portainer-cli/config.yml
|
||||||
|
url: https://portainer.example.com
|
||||||
|
api_key: ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
ssl_verify: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### List resources
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli list endpoints
|
||||||
|
portainer-cli list stacks
|
||||||
|
portainer-cli list containers <endpoint-id>
|
||||||
|
portainer-cli list volumes <endpoint-id>
|
||||||
|
portainer-cli list networks <endpoint-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Open an interactive shell
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Resolves endpoint automatically from the stack name.
|
||||||
|
# Container name defaults to the stack name, then falls back to 'app'.
|
||||||
|
portainer-cli console <stack>
|
||||||
|
portainer-cli console <stack> <container>
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
portainer-cli console myapp
|
||||||
|
portainer-cli console myapp worker
|
||||||
|
portainer-cli console myapp --shell /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run a specific command
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli console myapp --run "rails c"
|
||||||
|
portainer-cli console myapp --run "bundle exec rake db:migrate"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quiet mode (scripting / AI agents)
|
||||||
|
|
||||||
|
Suppresses all status output — only the container's own stdout/stderr is printed.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli console myapp --quiet --run "rails runner 'puts User.count'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create resources
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Stack from a compose file
|
||||||
|
portainer-cli create stack --endpoint 1 --name myapp --file docker-compose.yml
|
||||||
|
|
||||||
|
# Stack from inline compose
|
||||||
|
portainer-cli create stack --endpoint 1 --name myapp --compose "version: '3'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: nginx"
|
||||||
|
|
||||||
|
# Container
|
||||||
|
portainer-cli create container --endpoint 1 --image nginx:alpine --name web \
|
||||||
|
--port 8080:80 --env APP_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Low-level exec (manual endpoint ID)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli exec <endpoint-id> <container-name-or-id>
|
||||||
|
portainer-cli exec <endpoint-id> <container-name-or-id> --shell /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container name resolution
|
||||||
|
|
||||||
|
When using `console`, names are resolved in this order:
|
||||||
|
|
||||||
|
1. **Exact match** — full container name or ID prefix
|
||||||
|
2. **Compose pattern** — `<stack>-<service>-<N>` (e.g. `librarian` → `librarian-librarian-1`)
|
||||||
|
3. **Substring match** — scoped to the stack's own containers
|
||||||
|
4. **`app` fallback** — tries `<stack>-app-1` as a last resort
|
||||||
|
|
||||||
|
If multiple containers match ambiguously, a list is printed and the command exits.
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
```sh
|
||||||
|
portainer-cli --debug console myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints the WebSocket URL, auth headers, and the full HTTP upgrade handshake request/response — useful for diagnosing connection issues behind reverse proxies.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) for the full text.
|
||||||
@@ -5,7 +5,7 @@ require 'fileutils'
|
|||||||
|
|
||||||
module PortainerCli
|
module PortainerCli
|
||||||
class Config
|
class Config
|
||||||
CONFIG_DIR = File.expand_path('~/.portainer-cli').freeze
|
CONFIG_DIR = File.expand_path('~/.config/portainer-cli').freeze
|
||||||
CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml').freeze
|
CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml').freeze
|
||||||
|
|
||||||
attr_accessor :url, :token, :api_key, :ssl_verify
|
attr_accessor :url, :token, :api_key, :ssl_verify
|
||||||
|
|||||||
Reference in New Issue
Block a user