Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,29 @@ Pix requires a `docker` engine installed and running on your host system.
$ mix escript.install github athonet-open/pix ref vX.Y.Z
```

#### Option 2: Docker Installation
Requires Erlang/Elixir to be installed on the host.

```bash
$ docker run --rm -it \
--volume $PWD:/$PWD --workdir /$PWD \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume $SSH_AUTH_SOCK:$SSH_AUTH_SOCK \
--env SSH_AUTH_SOCK=$SSH_AUTH_SOCK \
ghcr.io/athonet-open/pix:X.Y.Z "$@"
```
#### Option 2: Wrapper Script

A self-managing shell script that automatically resolves the latest version, builds the Docker image from source, and keeps itself up to date.

Important considerations:
- Docker engine access is required via either Docker Socket Mounting (DooD) or Docker-in-Docker (dind)
- For SSH access, forward the SSH agent socket to the Pix container
- For macOS users with Docker Desktop:
```bash
--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock \
--env SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock
$ curl -fsSL https://raw.githubusercontent.com/athonet-open/pix/main/bin/pix -o /usr/local/bin/pix && chmod +x /usr/local/bin/pix
```

Requirements: `bash`, `docker`, `git`, `curl`.

On first run, the script will:
1. Detect the latest release tag from GitHub
2. Build the Pix Docker image locally from source
3. Run the requested command

On subsequent runs, the cached image is reused. When a new version is released, the script
automatically updates itself and rebuilds the image.

The script handles Docker socket mounting, SSH agent forwarding (with macOS Docker Desktop support),
and mounts `~/.ssh`, `~/.gitconfig*`, and `~/.config/pix/settings.exs` if present.

### Shell completion

Shell completion scripts are available for the following shells:
Expand Down
123 changes: 123 additions & 0 deletions bin/pix
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# pix - wrapper script for running Pix via Docker
#
# Automatically resolves the latest version from GitHub tags,
# self-updates the script and builds the image from source when a new version is detected.
#
# Install: curl -fsSL https://raw.githubusercontent.com/athonet-open/pix/main/bin/pix -o /usr/local/bin/pix && chmod +x /usr/local/bin/pix
set -euo pipefail

readonly PIX_REPO="https://github.com/athonet-open/pix.git"

preflight_checks() {
local -a required=(docker git)
for cmd in "${required[@]}"; do
command -v "$cmd" >/dev/null 2>&1 || { echo "error: $cmd is not installed or not in PATH" >&2; exit 1; }
done
docker info >/dev/null 2>&1 || { echo "error: docker daemon is not running" >&2; exit 1; }
}

resolve_version() {
local latest_tag
latest_tag=$(git ls-remote --tags --sort=-v:refname "$PIX_REPO" 'v*' 2>/dev/null \
| head -1 | sed 's|.*refs/tags/||')
[[ -z "$latest_tag" ]] && { echo "error: could not determine latest pix version from $PIX_REPO" >&2; exit 1; }

local -n _tag=$1 _ver=$2 _img=$3
_tag="$latest_tag"
_ver="${latest_tag#v}"
_img="athonet-open/pix:${_ver}"
}

ensure_image() {
local tag="$1" version="$2" image="$3"
shift 3

docker image inspect "$image" >/dev/null 2>&1 && return

echo "pix: new version detected ($version), updating..." >&2

local script_url="https://raw.githubusercontent.com/athonet-open/pix/${tag}/bin/pix"
curl -fsSL "$script_url" -o "$0"
chmod +x "$0"
echo "pix: script updated to $version" >&2

echo "pix: building $image from source..." >&2
local build_dir
build_dir=$(mktemp -d)
trap "rm -rf '$build_dir'" EXIT
git clone --depth 1 --branch "$tag" "$PIX_REPO" "$build_dir"
docker build --build-arg "VERSION=${version}" -t "$image" "$build_dir"
echo "pix: image $image built successfully" >&2

exec "$0" "$@"
}

docker_run_opts() {
local -n _opts=$1
local docker_sock="/var/run/docker.sock"

[[ -S "$docker_sock" ]] || { echo "error: docker socket not found at $docker_sock" >&2; exit 1; }
_opts=(--volume "$docker_sock:$docker_sock")

# SSH agent forwarding
case "$(uname -s)" in
Darwin)
_opts+=(--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock)
_opts+=(--env SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock)
;;
*)
if [[ -n "${SSH_AUTH_SOCK:-}" ]]; then
if [[ ! -S "$SSH_AUTH_SOCK" ]]; then
echo "warning: SSH_AUTH_SOCK is set but $SSH_AUTH_SOCK is not a valid socket, skipping SSH forwarding" >&2
else
_opts+=(--volume "$SSH_AUTH_SOCK:$SSH_AUTH_SOCK" --env "SSH_AUTH_SOCK=$SSH_AUTH_SOCK")
fi
fi
;;
esac

# User settings
local pix_settings="$HOME/.config/pix/settings.exs"
[[ -f "$pix_settings" ]] && _opts+=(--volume "$pix_settings:$pix_settings:ro")

# SSH keys
[[ -d "$HOME/.ssh" ]] && _opts+=(--volume "$HOME/.ssh:$HOME/.ssh:ro")

# Git config
local f
for f in "$HOME"/.gitconfig*; do
[[ -f "$f" ]] && _opts+=(--volume "$f:$f:ro")
done

# TTY
[[ -t 0 ]] && _opts+=(-it)

# Extra user options
if [[ -n "${PIX_DOCKER_RUN_OPTS:-}" ]]; then
local -a extra
read -ra extra <<< "$PIX_DOCKER_RUN_OPTS"
_opts+=("${extra[@]}")
fi
}

main() {
preflight_checks

local tag version image
resolve_version tag version image

ensure_image "$tag" "$version" "$image" "$@"

local -a opts
docker_run_opts opts

exec docker run --rm \
--env HOME="$HOME" \
--env PIX_HOST_OS="$(uname -s)" \
--volume "$PWD:/$PWD" --workdir "/$PWD" \
"${opts[@]}" \
"$image" "$@"
}

main "$@"
2 changes: 2 additions & 0 deletions lib/pix/command/help.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ defmodule Pix.Command.Help do
#{_var("PIX_DOCKER_BUILDX_DEBUG")}: Set to "true" to enable `docker buildx debug`.
If enabled and an error occurs in a `RUN` command, an interactive shell is presented
which can be used for investigating the error interactively.
#{_var("PIX_HOST_OS")}: Override the detected host OS (e.g. "Darwin", "Linux").
Useful when running pix inside a container via the wrapper script.
""")

:ok
Expand Down
16 changes: 13 additions & 3 deletions lib/pix/env.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,22 @@ defmodule Pix.Env do
end

@doc """
Returns the operating system name by executing `uname -s`.
Returns the operating system name.
Can be overridden by setting `PIX_HOST_OS` environment variable,
which is useful when pix runs inside a container (e.g. via the wrapper script)
but needs to report the actual host OS.
Falls back to `uname -s`.
"""
@spec os :: String.t()
def os do
{res, 0} = System.cmd("uname", ~w[-s])
String.trim(res)
case System.get_env("PIX_HOST_OS") do
nil ->
{res, 0} = System.cmd("uname", ~w[-s])
String.trim(res)

os ->
os
end
end

@doc """
Expand Down
48 changes: 30 additions & 18 deletions shell_completions/pix.fish
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,37 @@
complete -c pix -e
complete -c pix -f -d "Pipelines for buildx"

# Function to find the pipeline name from the command line
# Scans tokens starting after "pix <subcommand>", returning the first non-flag positional argument
function __pix_find_pipeline
set --local cmd_tokens (commandline --tokens-expanded)
for i in (seq 3 (count $cmd_tokens))
if not string match -q -- "-*" $cmd_tokens[$i]
echo $cmd_tokens[$i]
return
end
end
end

# Function to get available pipelines
function __pix_get_pipelines
command pix __complete_fish pipeline 2>/dev/null
end

# Function to get available targets for a pipeline
function __pix_get_run_targets
set --local cmd_tokens (commandline --tokens-expanded)

# Pipeline should be the last argument
set pipeline $cmd_tokens[-1]
set --local pipeline (__pix_find_pipeline)

command pix __complete_fish target $pipeline 2>/dev/null
if test -n "$pipeline"
command pix __complete_fish target $pipeline 2>/dev/null
end
end

# Function to get available args for a target of a pipeline
function __pix_get_run_target_args
set --local pipeline (__pix_find_pipeline)
set --local cmd_tokens (commandline --tokens-expanded)

# Pipeline should be the last argument
set pipeline $cmd_tokens[-1]

# Find index of "--target"
set --local target_index 0
set --local target ""
Expand Down Expand Up @@ -65,27 +74,27 @@ complete -c pix -n "__fish_seen_subcommand_from ls" -l "hidden" -d "Show also pr

# graph command options
complete -c pix -n "__fish_seen_subcommand_from graph" -a "(__pix_get_pipelines)" -d "Pipeline"
complete -c pix -n "__fish_seen_subcommand_from graph" -l "format" -d "Output format" -a "pretty dot"
complete -c pix -n "__fish_seen_subcommand_from graph" -l "format" -rf -d "Output format" -a "pretty dot"

# run command options
complete -c pix -n "__fish_seen_subcommand_from run" -a "(__pix_get_pipelines)" -d "Pipeline"
complete -c pix -n "__fish_seen_subcommand_from run" -l "output" -d "Output the target artifacts under .pipeline/output directory"
complete -c pix -n "__fish_seen_subcommand_from run" -l "ssh" -d "Forward SSH agent/keys to buildx build (default, or id=path)" -rf
complete -c pix -n "__fish_seen_subcommand_from run" -l "arg" -d "Set one or more pipeline ARG (format KEY=value)" -a "(__pix_get_run_target_args)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "progress" -d "Set type of progress output" -a "auto plain tty rawjson"
complete -c pix -n "__fish_seen_subcommand_from run" -l "secret" -d "Forward one or more secrets to `buildx build`"
complete -c pix -n "__fish_seen_subcommand_from run" -l "target" -d "Run PIPELINE for a specific TARGET" -a "(__pix_get_run_targets)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "tag" -d "Tag the TARGET's docker image (requires --target)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "arg" -rf -d "Set one or more pipeline ARG (format KEY=value)" -a "(__pix_get_run_target_args)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "progress" -rf -d "Set type of progress output" -a "auto plain tty rawjson"
complete -c pix -n "__fish_seen_subcommand_from run" -l "secret" -rf -d "Forward one or more secrets to `buildx build`"
complete -c pix -n "__fish_seen_subcommand_from run" -l "target" -rf -d "Run PIPELINE for a specific TARGET" -a "(__pix_get_run_targets)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "tag" -rf -d "Tag the TARGET's docker image (requires --target)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "save" -d "Save the TARGET's docker image to a file (requires --target and --tag)" -rF
complete -c pix -n "__fish_seen_subcommand_from run" -l "no-cache" -d "Do not use cache when building the image"
complete -c pix -n "__fish_seen_subcommand_from run" -l "no-cache-filter" -d "Do not cache specified targets" -a "(__pix_get_run_targets)"
complete -c pix -n "__fish_seen_subcommand_from run" -l "no-cache-filter" -rf -d "Do not cache specified targets" -a "(__pix_get_run_targets)"

# shell command options
complete -c pix -n "__fish_seen_subcommand_from shell" -a "(__pix_get_pipelines)" -d "Pipeline"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "ssh" -d "Forward SSH agent/keys to shell container (default, or id=path)" -rf
complete -c pix -n "__fish_seen_subcommand_from shell" -l "arg" -d "Set one or more pipeline ARG (format KEY=value)"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "secret" -d "Forward one or more secrets to `buildx build`"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "target" -d "The shell target"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "arg" -rf -d "Set one or more pipeline ARG (format KEY=value)" -a "(__pix_get_run_target_args)"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "secret" -rf -d "Forward one or more secrets to `buildx build`"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "target" -rf -d "The shell target" -a "(__pix_get_run_targets)"
complete -c pix -n "__fish_seen_subcommand_from shell" -l "host" -d "Bind mount the current working dir"

# upgrade command options
Expand All @@ -94,5 +103,8 @@ complete -c pix -n "__fish_seen_subcommand_from upgrade" -l "dry-run" -d "Only c
# cache command options
complete -c pix -n "__fish_seen_subcommand_from cache" -a "info\t'Show info about the cache' update\t'Update the cache of remote git pipelines' clear\t'Clear the cache of remote git pipelines'"

# help command options
complete -c pix -n "__fish_seen_subcommand_from help" -a "ls graph run shell upgrade cache completion_script" -d "Command"

# completion_script command options
complete -c pix -n "__fish_seen_subcommand_from completion_script" -a "fish" -d "Fish completion script"
Loading