diff --git a/.dockerignore b/.containerignore similarity index 87% rename from .dockerignore rename to .containerignore index af55e3c1..3cf60af2 100644 --- a/.dockerignore +++ b/.containerignore @@ -6,7 +6,7 @@ test .goreleaser.yml *.md coverage.txt -Dockerfile +Containerfile LICENSE test.sh .git diff --git a/.gitignore b/.gitignore index b9f700a9..26f5e6a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ lazydocker* +lazypodman* TODO.md Lazydocker.code-workspace .vscode diff --git a/.goreleaser.yml b/.goreleaser.yml index 0caa8172..23a8896d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -120,7 +120,7 @@ brews: # # apps: # # # # 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 # # # confined space, declare them here. diff --git a/CLAUDE.md b/CLAUDE.md index 21e0ead1..20cde975 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,13 +2,13 @@ ## 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:** 1. Support native libpod (Podman's Go library) instead of Docker SDK 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 @@ -26,7 +26,7 @@ go build -mod=vendor ./lazypodman -d # 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 @@ -48,78 +48,83 @@ pkg/ └── cheatsheet/ # Keybinding reference ``` -### Key Files for Podman Integration +### Key Files -**Primary targets for libpod conversion:** -- `pkg/commands/docker.go` - Main client connection and initialization -- `pkg/commands/container.go` - Container operations -- `pkg/commands/image.go` - Image operations -- `pkg/commands/volume.go` - Volume operations -- `pkg/commands/network.go` - Network operations +**Runtime abstraction layer:** +- `pkg/commands/runtime.go` - `ContainerRuntime` interface abstracting all container operations +- `pkg/commands/runtime_socket.go` - Socket mode implementation using Podman REST API bindings +- `pkg/commands/runtime_libpod.go` - Direct libpod implementation (Linux+CGO only) +- `pkg/commands/runtime_libpod_stub.go` - Stub for non-Linux platforms +- `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/docker_host_unix.go` - Unix socket detection -- `pkg/commands/docker_host_windows.go` - Windows pipe detection +- `pkg/commands/podman_host_unix.go` - Unix socket detection +- `pkg/commands/podman_host_windows.go` - Windows pipe detection **Application entry:** - `main.go` - Entry point, CLI flags - `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 -1. `NewDockerCommand()` in `docker.go` initializes client -2. Docker host determined from `DOCKER_HOST` env or platform defaults -3. Uses `github.com/docker/docker/client` SDK for API calls -4. SSH tunneling supported for remote hosts +1. `NewPodmanCommand()` in `podman.go` initializes runtime +2. Host determined from `CONTAINER_HOST` env or platform defaults +3. Tries socket mode first, falls back to libpod if unavailable +4. Auto-detects compose tool: `podman-compose`, `podman compose`, or `docker-compose` -### API Methods Used +### Runtime Interface Methods ```go // Container operations -Client.ContainerList() -Client.ContainerInspect() -Client.ContainerStats() // Streaming -Client.ContainerStart/Stop/Pause/Unpause/Restart/Remove() -Client.ContainersPrune() +ListContainers() / InspectContainer() / ContainerStats() +StartContainer() / StopContainer() / PauseContainer() / UnpauseContainer() +RestartContainer() / RemoveContainer() / PruneContainers() / ContainerTop() // Image operations -Client.ImageList() -Client.ImagesPrune() +ListImages() / InspectImage() / ImageHistory() / RemoveImage() / PruneImages() // Volume/Network -Client.VolumeList/VolumesPrune() -Client.NetworkList/NetworksPrune() +ListVolumes() / RemoveVolume() / PruneVolumes() +ListNetworks() / RemoveNetwork() / PruneNetworks() + +// Events +GetEvents() - Streaming (socket) or polling (libpod) ``` ### 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 3. **Template-based** - Configurable command templates in config -## Libpod Integration Path +## Platform Support -### Required Changes - -1. **Replace Docker SDK with libpod bindings** - - Add `github.com/containers/podman/v5/pkg/bindings` dependency - - Replace `*client.Client` with libpod connection - -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 +| Platform | Socket Mode | Libpod Mode | +|----------|-------------|-------------| +| Linux | ✅ | ✅ (requires CGO) | +| macOS | ✅ | ❌ (stub) | +| Windows | ✅ | ❌ (stub) | ## Development Guidelines @@ -153,4 +158,11 @@ go test -mod=vendor ./pkg/gui/... ## Module Info - Module: `github.com/christophe-duc/lazypodman` - 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) diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..adfb55b9 --- /dev/null +++ b/Containerfile @@ -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 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 334591a0..00000000 --- a/Dockerfile +++ /dev/null @@ -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 diff --git a/README.md b/README.md index a8fcbd03..e080f5f5 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,8 @@ -
-
-
-
-
-
- Maintenance of this project is made possible by all the contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear below click here. 💙 -
- - - ## Elevator Pitch Minor rant incoming: Something's not working? Maybe a service is down. `podman-compose ps`. Yep, it's that microservice that's still buggy. No issue, I'll just restart it: `podman-compose restart`. Okay now let's try again. Oh wait the issue is still there. Hmm. `podman-compose ps`. Right so the service must have just stopped immediately after starting. I probably would have known that if I was reading the log stream, but there is a lot of clutter in there from other services. I could get the logs for just that one service with `podman-compose logs --follow myservice` but that dies everytime the service dies so I'd need to run that command every time I restart the service. I could alternatively run `podman-compose up myservice` and in that terminal window if the service is down I could just `up` it again, but now I've got one service hogging a terminal window even after I no longer care about its logs. I guess when I want to reclaim the terminal realestate I can do `ctrl+P,Q`, but... wait, that's not working for some reason. Should I use ctrl+C instead? I can't remember if that closes the foreground process or kills the actual service. @@ -93,50 +41,7 @@ Memorising podman commands is hard. Memorising aliases is slightly less hard. Ke ## Installation -### Homebrew - -Normally `lazypodman` formula can be found in the Homebrew core but we suggest you to tap our formula to get frequently updated one. It works with Linux, too. - -**Tap**: -```sh -brew install christophe-duc/lazypodman/lazypodman -``` - -**Core**: -```sh -brew install lazypodman -``` - -### Scoop (Windows) - -You can install `lazypodman` using [scoop](https://scoop.sh/): - -```sh -scoop install lazypodman -``` -### Chocolatey (Windows) - -You can install `lazypodman` using [Chocolatey](https://chocolatey.org/): - -```sh -choco install lazypodman -``` -### asdf-vm - -You can install [asdf-lazypodman plugin](https://github.com/comdotlinux/asdf-lazypodman) using [asdf-vm](https://asdf-vm.com/): -#### Setup (Once) -```sh -asdf plugin add lazypodman https://github.com/comdotlinux/asdf-lazypodman.git -``` - -#### For Install / Upgrade -```sh -asdf list all lazypodman -asdf install lazypodman latest -asdf global lazypodman latest -``` - -### Binary Release (Linux/OSX/Windows) +### Binary Release (Linux) You can manually download a binary release from [the release page](https://github.com/christophe-duc/lazypodman/releases). @@ -161,14 +66,6 @@ Required Go version >= **1.8**, <= **1.17** go get github.com/christophe-duc/lazypodman ``` -### Arch Linux AUR - -You can install lazypodman using the [AUR](https://aur.archlinux.org/packages/lazypodman) by running: - -```sh -yay -S lazypodman -``` - ### Podman Container 1.
@@ -284,19 +181,7 @@ everything is one keypress away (or one click away! Mouse support FTW):
## Contributing
There is still a lot of work to go! Please check out the [contributing guide](CONTRIBUTING.md).
-For contributor discussion about things not better discussed here in the repo, join the discord channel
-
-
-## Donate
-
-If you would like to support the development of lazypodman, consider [sponsoring me](https://github.com/sponsors/jesseduffield) (github is matching all donations dollar-for-dollar for 12 months)
-
-## Social
-
-If you want to see what I (Jesse) am up to in terms of development, follow me on
-[twitter](https://twitter.com/DuffieldJesse) or watch me program on
-[twitch](https://www.twitch.tv/jesseduffield)
## FAQ
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 8e14562d..00000000
--- a/docker-compose.yml
+++ /dev/null
@@ -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
diff --git a/docs/keybindings/Keybindings_tr.md b/docs/keybindings/Keybindings_tr.md
index 648da27b..934d39b8 100644
--- a/docs/keybindings/Keybindings_tr.md
+++ b/docs/keybindings/Keybindings_tr.md
@@ -5,7 +5,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Proje
- e: lazzydocker ayarlarını düzenle
+ e: lazypodman ayarlarını düzenle
o: lazypodman ayarlarını aç
m: kayıt defterini görüntüle
enter: ana panele odaklan
diff --git a/hooks/build b/hooks/build
index 17973ef3..6940fdd7 100644
--- a/hooks/build
+++ b/hooks/build
@@ -1,6 +1,7 @@
#!/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 VERSION=`git describe --abbrev=0 --tag` \
-t $IMAGE_NAME .
diff --git a/lazypodman b/lazypodman
deleted file mode 100755
index e09b2e43..00000000
Binary files a/lazypodman and /dev/null differ
diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
index e11e7c33..c85bd19b 100644
--- a/pkg/cheatsheet/generate.go
+++ b/pkg/cheatsheet/generate.go
@@ -126,7 +126,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
}
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 {
content += formatTitle(section.title)
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index bf696893..dec65e18 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -84,7 +84,7 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
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 {
splitCmd := str.ToArgv(commandStr)
return c.NewCmd(splitCmd[0], splitCmd[1:]...)
@@ -369,7 +369,7 @@ func (c *OSCommand) Kill(cmd *exec.Cmd) error {
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) {
kill.PrepareForChildren(cmd)
}
diff --git a/podman-compose.yml b/podman-compose.yml
new file mode 100644
index 00000000..e221ce55
--- /dev/null
+++ b/podman-compose.yml
@@ -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