This commit is contained in:
christophe-duc 2026-01-07 15:05:07 -04:00
parent 39a8e864b9
commit 1ec809ec8a
14 changed files with 152 additions and 241 deletions

View file

@ -6,7 +6,7 @@ test
.goreleaser.yml .goreleaser.yml
*.md *.md
coverage.txt coverage.txt
Dockerfile Containerfile
LICENSE LICENSE
test.sh test.sh
.git .git

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
lazydocker* lazydocker*
lazypodman*
TODO.md TODO.md
Lazydocker.code-workspace Lazydocker.code-workspace
.vscode .vscode

View file

@ -120,7 +120,7 @@ brews:
# # apps: # # apps:
# #
# # # The name of the app must be the same name as the binary built or the snapcraft name. # # # The name of the app must be the same name as the binary built or the snapcraft name.
# # lazydocker: # # lazypodman:
# #
# # # If your app requires extra permissions to work outside of its default # # # If your app requires extra permissions to work outside of its default
# # # confined space, declare them here. # # # confined space, declare them here.

118
CLAUDE.md
View file

@ -2,13 +2,13 @@
## Project Overview ## Project Overview
This is a fork of **lazydocker** being converted to **lazypodman** - a terminal UI for managing Podman containers. This is a fork of **lazydocker** converted to **lazypodman** - a terminal UI for managing Podman containers.
**Goals:** **Goals:**
1. Support native libpod (Podman's Go library) instead of Docker SDK 1. Support native libpod (Podman's Go library) instead of Docker SDK
2. Enable socket-less operation for Podman commands (no daemon required) 2. Enable socket-less operation for Podman commands (no daemon required)
**Current State:** The codebase uses Docker Go SDK (`github.com/docker/docker` v28.5.2) and requires conversion to libpod. **Current State:** Conversion complete. The codebase now uses Podman bindings (`github.com/containers/podman/v5` v5.7.1) with a hybrid runtime architecture supporting both socket mode (REST API) and socket-less mode (direct libpod).
## Quick Start ## Quick Start
@ -26,7 +26,7 @@ go build -mod=vendor
./lazypodman -d ./lazypodman -d
# Run with specific compose files # Run with specific compose files
./lazypodman -f docker-compose.yml -f docker-compose.override.yml ./lazypodman -f podman-compose.yml -f podman-compose.override.yml
``` ```
## Architecture ## Architecture
@ -48,78 +48,83 @@ pkg/
└── cheatsheet/ # Keybinding reference └── cheatsheet/ # Keybinding reference
``` ```
### Key Files for Podman Integration ### Key Files
**Primary targets for libpod conversion:** **Runtime abstraction layer:**
- `pkg/commands/docker.go` - Main client connection and initialization - `pkg/commands/runtime.go` - `ContainerRuntime` interface abstracting all container operations
- `pkg/commands/container.go` - Container operations - `pkg/commands/runtime_socket.go` - Socket mode implementation using Podman REST API bindings
- `pkg/commands/image.go` - Image operations - `pkg/commands/runtime_libpod.go` - Direct libpod implementation (Linux+CGO only)
- `pkg/commands/volume.go` - Volume operations - `pkg/commands/runtime_libpod_stub.go` - Stub for non-Linux platforms
- `pkg/commands/network.go` - Network operations - `pkg/commands/runtime_types.go` - Custom types (ContainerSummary, ImageSummary, etc.)
**Podman integration:**
- `pkg/commands/podman.go` - Main client connection, auto-detection, and initialization
- `pkg/commands/container.go` - Container wrapper operations
- `pkg/commands/image.go` - Image wrapper operations
- `pkg/commands/volume.go` - Volume wrapper operations
- `pkg/commands/network.go` - Network wrapper operations
- `pkg/commands/service.go` - Compose service operations - `pkg/commands/service.go` - Compose service operations
- `pkg/commands/docker_host_unix.go` - Unix socket detection - `pkg/commands/podman_host_unix.go` - Unix socket detection
- `pkg/commands/docker_host_windows.go` - Windows pipe detection - `pkg/commands/podman_host_windows.go` - Windows pipe detection
**Application entry:** **Application entry:**
- `main.go` - Entry point, CLI flags - `main.go` - Entry point, CLI flags
- `pkg/app/app.go` - App struct, initialization flow - `pkg/app/app.go` - App struct, initialization flow
## Current Docker Integration ## Runtime Architecture
### Hybrid Runtime System
The codebase uses a `ContainerRuntime` interface with two implementations:
1. **Socket Mode** (`runtime_socket.go`)
- Uses Podman REST API via `pkg/bindings`
- Connects to local or remote Podman instances
- Supports SSH tunneling for remote hosts
- Real event streaming
2. **Libpod Mode** (`runtime_libpod.go`)
- Direct libpod library calls (no socket required)
- Linux + CGO only
- Event polling (2-second intervals)
- Fallback when socket unavailable
### Connection Flow ### Connection Flow
1. `NewDockerCommand()` in `docker.go` initializes client 1. `NewPodmanCommand()` in `podman.go` initializes runtime
2. Docker host determined from `DOCKER_HOST` env or platform defaults 2. Host determined from `CONTAINER_HOST` env or platform defaults
3. Uses `github.com/docker/docker/client` SDK for API calls 3. Tries socket mode first, falls back to libpod if unavailable
4. SSH tunneling supported for remote hosts 4. Auto-detects compose tool: `podman-compose`, `podman compose`, or `docker-compose`
### API Methods Used ### Runtime Interface Methods
```go ```go
// Container operations // Container operations
Client.ContainerList() ListContainers() / InspectContainer() / ContainerStats()
Client.ContainerInspect() StartContainer() / StopContainer() / PauseContainer() / UnpauseContainer()
Client.ContainerStats() // Streaming RestartContainer() / RemoveContainer() / PruneContainers() / ContainerTop()
Client.ContainerStart/Stop/Pause/Unpause/Restart/Remove()
Client.ContainersPrune()
// Image operations // Image operations
Client.ImageList() ListImages() / InspectImage() / ImageHistory() / RemoveImage() / PruneImages()
Client.ImagesPrune()
// Volume/Network // Volume/Network
Client.VolumeList/VolumesPrune() ListVolumes() / RemoveVolume() / PruneVolumes()
Client.NetworkList/NetworksPrune() ListNetworks() / RemoveNetwork() / PruneNetworks()
// Events
GetEvents() - Streaming (socket) or polling (libpod)
``` ```
### Command Execution Patterns ### Command Execution Patterns
1. **SDK calls** - For most container/image operations 1. **Runtime interface calls** - For most container/image operations
2. **Shell commands** - For interactive operations (attach, logs) and compose 2. **Shell commands** - For interactive operations (attach, logs) and compose
3. **Template-based** - Configurable command templates in config 3. **Template-based** - Configurable command templates in config
## Libpod Integration Path ## Platform Support
### Required Changes | Platform | Socket Mode | Libpod Mode |
|----------|-------------|-------------|
1. **Replace Docker SDK with libpod bindings** | Linux | ✅ | ✅ (requires CGO) |
- Add `github.com/containers/podman/v5/pkg/bindings` dependency | macOS | ✅ | ❌ (stub) |
- Replace `*client.Client` with libpod connection | Windows | ✅ | ❌ (stub) |
2. **Update type mappings**
- Docker `container.Summary` -> Podman equivalent
- Docker `image.Summary` -> Podman equivalent
- All API response types need mapping
3. **Socket-less mode**
- Implement direct libpod calls without socket
- Use `bindings.NewConnection()` with appropriate URI
4. **Compose support**
- Detect `podman-compose` vs `docker-compose`
- Update command templates
### Files NOT requiring changes
- `pkg/gui/*` - UI layer is container-runtime agnostic
- `pkg/config/*` - Configuration structure remains same
- `pkg/i18n/*` - Translations unaffected
## Development Guidelines ## Development Guidelines
@ -153,4 +158,11 @@ go test -mod=vendor ./pkg/gui/...
## Module Info ## Module Info
- Module: `github.com/christophe-duc/lazypodman` - Module: `github.com/christophe-duc/lazypodman`
- Go: 1.22+ (toolchain 1.23.6) - Go: 1.22+ (toolchain 1.23.6)
- Key deps: gocui, docker/docker, logrus - Key deps: gocui, containers/podman/v5, logrus
## Known Limitations
- **Network Prune**: Libpod mode has a TODO for manual pruning implementation
- **Event Streaming**: Libpod mode uses polling (2-second intervals) instead of true event streaming
- **Libpod Availability**: Socket-less mode requires Linux + CGO compilation
- **Docker SDK**: Present in go.mod as indirect transitive dependency (pulled by Podman itself, not used by application code)

53
Containerfile Normal file
View file

@ -0,0 +1,53 @@
ARG BASE_IMAGE_BUILDER=golang
ARG ALPINE_VERSION=3.20
ARG GO_VERSION=1.23
FROM ${BASE_IMAGE_BUILDER}:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
ARG GOARCH=amd64
ARG GOARM
ARG VERSION
ARG VCS_REF
WORKDIR /tmp/gobuild
COPY ./ .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} GOARM=${GOARM} \
go build -a -mod=vendor \
-ldflags="-s -w \
-X main.commit=${VCS_REF} \
-X main.version=${VERSION} \
-X main.buildSource=Podman"
FROM ${BASE_IMAGE_BUILDER}:${GO_VERSION}-alpine${ALPINE_VERSION} AS podman-builder
ARG GOARCH=amd64
ARG GOARM
ARG PODMAN_VERSION=v5.3.1
RUN apk add -U -q --progress --no-cache curl tar gzip
WORKDIR /tmp/podman
# Download pre-built podman-remote binary from GitHub releases
RUN ARCH=$(case "${GOARCH}" in \
amd64) echo "amd64" ;; \
arm64) echo "arm64" ;; \
arm) echo "arm" ;; \
*) echo "amd64" ;; \
esac) && \
curl -fsSL "https://github.com/containers/podman/releases/download/${PODMAN_VERSION}/podman-remote-static-linux_${ARCH}.tar.gz" | \
tar -xzf - && \
mv bin/podman-remote-static-linux_${ARCH} /usr/local/bin/podman && \
chmod +x /usr/local/bin/podman
FROM scratch
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION
LABEL \
org.opencontainers.image.authors="christophe-duc" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.url="https://github.com/christophe-duc/lazypodman" \
org.opencontainers.image.documentation="https://github.com/christophe-duc/lazypodman" \
org.opencontainers.image.source="https://github.com/christophe-duc/lazypodman" \
org.opencontainers.image.title="lazypodman" \
org.opencontainers.image.description="The lazier way to manage everything podman"
ENTRYPOINT [ "/bin/lazypodman" ]
COPY --from=podman-builder /usr/local/bin/podman /bin/podman
COPY --from=builder /tmp/gobuild/lazypodman /bin/lazypodman

View file

@ -1,49 +0,0 @@
ARG BASE_IMAGE_BUILDER=golang
ARG ALPINE_VERSION=3.20
ARG GO_VERSION=1.23
FROM ${BASE_IMAGE_BUILDER}:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
ARG GOARCH=amd64
ARG GOARM
ARG VERSION
ARG VCS_REF
WORKDIR /tmp/gobuild
COPY ./ .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} GOARM=${GOARM} \
go build -a -mod=vendor \
-ldflags="-s -w \
-X main.commit=${VCS_REF} \
-X main.version=${VERSION} \
-X main.buildSource=Docker"
FROM ${BASE_IMAGE_BUILDER}:${GO_VERSION}-alpine${ALPINE_VERSION} AS docker-builder
ARG GOARCH=amd64
ARG GOARM
ARG DOCKER_VERSION=v27.0.3
RUN apk add -U -q --progress --no-cache git bash coreutils gcc musl-dev
WORKDIR /go/src/github.com/docker/cli
RUN git clone --branch ${DOCKER_VERSION} --single-branch --depth 1 https://github.com/docker/cli.git . > /dev/null 2>&1
ENV CGO_ENABLED=0 \
GOARCH=${GOARCH} \
GOARM=${GOARM} \
DISABLE_WARN_OUTSIDE_CONTAINER=1
RUN ./scripts/build/binary
RUN rm build/docker && mv build/docker-linux-* build/docker
FROM scratch
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION
LABEL \
org.opencontainers.image.authors="jessedduffield@gmail.com" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.url="https://github.com/jesseduffield/lazydocker" \
org.opencontainers.image.documentation="https://github.com/jesseduffield/lazydocker" \
org.opencontainers.image.source="https://github.com/jesseduffield/lazydocker" \
org.opencontainers.image.title="lazydocker" \
org.opencontainers.image.description="The lazier way to manage everything docker"
ENTRYPOINT [ "/bin/lazydocker" ]
COPY --from=docker-builder /go/src/github.com/docker/cli/build/docker /bin/docker
COPY --from=builder /tmp/gobuild/lazydocker /bin/lazydocker

117
README.md

File diff suppressed because one or more lines are too long

View file

@ -1,16 +0,0 @@
version: '3'
services:
lazypodman:
build:
context: https://github.com/christophe-duc/lazypodman.git
args:
BASE_IMAGE_BUILDER: golang
GOARCH: amd64
GOARM:
image: christophe-duc/lazypodman
container_name: lazypodman
stdin_open: true
tty: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/.config/christophe-duc/lazypodman

View file

@ -5,7 +5,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Proje ## Proje
<pre> <pre>
<kbd>e</kbd>: lazzydocker ayarlarını düzenle <kbd>e</kbd>: lazypodman ayarlarını düzenle
<kbd>o</kbd>: lazypodman ayarlarını <kbd>o</kbd>: lazypodman ayarlarını
<kbd>m</kbd>: kayıt defterini görüntüle <kbd>m</kbd>: kayıt defterini görüntüle
<kbd>enter</kbd>: ana panele odaklan <kbd>enter</kbd>: ana panele odaklan

View file

@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
docker build --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ podman build -f Containerfile \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \ --build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION=`git describe --abbrev=0 --tag` \ --build-arg VERSION=`git describe --abbrev=0 --tag` \
-t $IMAGE_NAME . -t $IMAGE_NAME .

Binary file not shown.

View file

@ -126,7 +126,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
} }
func formatSections(mApp *app.App, bindingSections []*bindingSection) string { func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
content := fmt.Sprintf("# Lazydocker %s\n", mApp.Tr.Menu) content := fmt.Sprintf("# Lazypodman %s\n", mApp.Tr.Menu)
for _, section := range bindingSections { for _, section := range bindingSections {
content += formatTitle(section.title) content += formatTitle(section.title)

View file

@ -84,7 +84,7 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
return err return err
} }
// ExecutableFromString takes a string like `docker ps -a` and returns an executable command for it // ExecutableFromString takes a string like `podman ps -a` and returns an executable command for it
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd { func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr) splitCmd := str.ToArgv(commandStr)
return c.NewCmd(splitCmd[0], splitCmd[1:]...) return c.NewCmd(splitCmd[0], splitCmd[1:]...)
@ -369,7 +369,7 @@ func (c *OSCommand) Kill(cmd *exec.Cmd) error {
return kill.Kill(cmd) return kill.Kill(cmd)
} }
// PrepareForChildren sets Setpgid to true on the cmd, so that when we run it as a subprocess, we can kill its group rather than the process itself. This is because some commands, like `docker-compose logs` spawn multiple children processes, and killing the parent process isn't sufficient for killing those child processes. We set the group id here, and then in subprocess.go we check if the group id is set and if so, we kill the whole group rather than just the one process. // PrepareForChildren sets Setpgid to true on the cmd, so that when we run it as a subprocess, we can kill its group rather than the process itself. This is because some commands, like `podman-compose logs` spawn multiple children processes, and killing the parent process isn't sufficient for killing those child processes. We set the group id here, and then in subprocess.go we check if the group id is set and if so, we kill the whole group rather than just the one process.
func (c *OSCommand) PrepareForChildren(cmd *exec.Cmd) { func (c *OSCommand) PrepareForChildren(cmd *exec.Cmd) {
kill.PrepareForChildren(cmd) kill.PrepareForChildren(cmd)
} }

24
podman-compose.yml Normal file
View file

@ -0,0 +1,24 @@
version: '3'
services:
lazypodman:
build:
context: https://github.com/christophe-duc/lazypodman.git
dockerfile: Containerfile
args:
BASE_IMAGE_BUILDER: golang
GOARCH: amd64
GOARM:
image: christophe-duc/lazypodman
container_name: lazypodman
stdin_open: true
tty: true
volumes:
# Rootless Podman socket (default for most users)
# For rootful: use /run/podman/podman.sock instead
- ${XDG_RUNTIME_DIR:-/run/user/1000}/podman/podman.sock:/run/podman/podman.sock:ro
# Config directory
- ./config:/.config/lazypodman
environment:
- CONTAINER_HOST=unix:///run/podman/podman.sock
security_opt:
- label=disable