Skip to content
Open
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
6 changes: 5 additions & 1 deletion lua/remote-nvim/providers/devpod/devpod_provider.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ function DevpodProvider:_handle_provider_setup()
if self._devpod_provider then
self.local_provider:run_command(
("%s provider list --output json"):format(remote_nvim.config.devpod.binary),
("Checking if the %s provider is present"):format(self._devpod_provider)
("Checking if the %s provider is present"):format(self._devpod_provider),
nil,
nil,
false,
false
)
local stdout = self.local_provider.executor:job_stdout()
local provider_list_output = vim.json.decode(vim.tbl_isempty(stdout) and "{}" or table.concat(stdout, "\n"))
Expand Down
3 changes: 2 additions & 1 deletion lua/remote-nvim/providers/executor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local Executor = require("remote-nvim.middleclass")("Executor")
---@field additional_opts? string[] Additional options to pass to the `tar` command. See `man tar` for possible options

---@class remote-nvim.provider.Executor.JobOpts
---@field pty boolean? Whether to set pty option
---@field additional_conn_opts string? Connection options
---@field stdout_cb function? Standard output callback
---@field exit_cb function? On exit callback
Expand Down Expand Up @@ -76,7 +77,7 @@ function Executor:run_executor_job(command, job_opts)

self:reset() -- Reset job internal state variables
self._job_id = vim.fn.jobstart(command, {
pty = true,
pty = job_opts.pty == nil or job_opts.pty,
on_stdout = function(_, data_chunk)
self:process_stdout(data_chunk, job_opts.stdout_cb)
end,
Expand Down
50 changes: 47 additions & 3 deletions lua/remote-nvim/providers/provider.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---@alias provider_type "ssh"|"devpod"|"local"
---@alias os_type "macOS"|"Windows"|"Linux"
---@alias arch_type "x86_64"|"arm64"
---@alias neovim_install_method "binary"|"source"|"system"
---@alias neovim_install_method "binary"|"tarball"|"source"|"system"

---@class remote-nvim.providers.WorkspaceConfig
---@field provider provider_type? Which provider is responsible for managing this workspace
Expand All @@ -17,6 +17,7 @@
---@field client_auto_start boolean? Flag indicating if the client should be auto started or not
---@field offline_mode boolean? Should we operate in offline mode
---@field devpod_source_opts remote-nvim.providers.DevpodSourceOpts? Devpod related source options
---@field is_docker_container boolean? Flag indicating if the remote is running in a Docker container

---@class remote-nvim.providers.Provider: remote-nvim.Object
---@field host string Host name
Expand Down Expand Up @@ -175,6 +176,18 @@ function Provider:_setup_workspace_variables()
self._host_config.neovim_install_method = "source"
prompt_title = "Binary release not available. Choose Neovim version to install"
end

-- Check if remote is running in Docker container and override install method
local is_docker = self:_is_remote_docker_container()
if is_docker and self._host_config.neovim_install_method == "binary" then
self._host_config.neovim_install_method = "tarball"
prompt_title = "Docker container detected. Choose Neovim version to install (using tarball)"
self.logger.info("Overriding install method from 'binary' to 'tarball' due to Docker container detection")
end

-- Store Docker detection result for future reference
self._host_config.is_docker_container = is_docker

self._remote_neovim_install_method = self._host_config.neovim_install_method
self._host_config.neovim_version = self:_get_remote_neovim_version_preference(prompt_title)

Expand All @@ -186,6 +199,7 @@ function Provider:_setup_workspace_variables()
self._config_provider:update_workspace_config(self.unique_host_id, {
neovim_install_method = self._host_config.neovim_install_method,
neovim_version = self._host_config.neovim_version,
is_docker_container = self._host_config.is_docker_container,
})
end
self._remote_neovim_version = self._host_config.neovim_version
Expand Down Expand Up @@ -358,6 +372,34 @@ function Provider:_get_remote_os_and_arch()
return self._remote_os, self._remote_arch
end

---@private
---Detect if the remote host is running in a Docker container
---@return boolean is_docker True if running in Docker container
function Provider:_is_remote_docker_container()
-- Check multiple indicators of container environment
local docker_checks = {
"test -f /.dockerenv", -- Standard Docker indicator file
"test -f /run/.containerenv", -- Standard Podman indicator file
'test -n "${container}"', -- Container environment variable
"grep -q 'docker\\|lxc' /proc/1/cgroup 2>/dev/null", -- Process cgroup check
"test -f /proc/self/mountinfo && grep -q 'docker' /proc/self/mountinfo 2>/dev/null", -- Mount info check
}

local check_cmd = table.concat(docker_checks, " || ")
local full_cmd = ("(%s) && echo 'DOCKER_DETECTED' || echo 'NOT_DOCKER'"):format(check_cmd)

self:run_command(full_cmd, "Checking if remote environment is a Docker container")
local cmd_output = self.executor:job_stdout()
local result = cmd_output[#cmd_output] or ""

local is_docker = result:find("DOCKER_DETECTED") ~= nil
if is_docker then
self.logger.info("Docker container detected on remote host - will use tarball installation instead of AppImage")
end

return is_docker
end

---@private
---Get user's home directory on the remote host
---@return string home_path User's home directory path
Expand Down Expand Up @@ -655,7 +697,7 @@ function Provider:_setup_remote()
)
local local_upload_paths = { local_release_path }

if self._remote_neovim_install_method == "binary" then
if self._remote_neovim_install_method == "binary" or self._remote_neovim_install_method == "tarball" then
table.insert(local_upload_paths, ("%s.sha256sum"):format(local_release_path))
end
self:upload(
Expand Down Expand Up @@ -1016,7 +1058,8 @@ end
---@param extra_opts string? Extra options to pass to the underlying command
---@param exit_cb function? Exit callback to execute
---@param on_local_executor boolean? Should run this command on the local executor
function Provider:run_command(command, desc, extra_opts, exit_cb, on_local_executor)
---@param pty boolean? Whether to use pty or not
function Provider:run_command(command, desc, extra_opts, exit_cb, on_local_executor, pty)
self.logger.fmt_debug("[%s][%s] Running %s", self.provider_type, self.unique_host_id, command)
on_local_executor = on_local_executor or false
local executor = on_local_executor and self.local_executor or self.executor
Expand All @@ -1033,6 +1076,7 @@ function Provider:run_command(command, desc, extra_opts, exit_cb, on_local_execu
exit_cb = exit_cb(section_node)
end
executor:run_command(command, {
pty = pty == nil or pty,
additional_conn_opts = extra_opts,
exit_cb = exit_cb,
stdout_cb = self:_get_stdout_fn_for_node(section_node),
Expand Down
39 changes: 37 additions & 2 deletions scripts/neovim_download.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Options:
-v Specify the desired Neovim version to download.
-d Specify directory inside which Neovim release should be downloaded.
-o OS whose binary is to be downloaded.
-t What to download: 'binary' or 'source'
-t What to download: 'binary', 'tarball' or 'source'
-a Specify architecture that should be downloaded.
-h Display this help message and exit.
EOM
Expand Down Expand Up @@ -67,6 +67,39 @@ function download_neovim_source() {
info "Downloaded Neovim source version ${version} to ${download_path}"
}

# Download Neovim tarball
function download_neovim_tarball() {
local os="$1" version="$2" download_dir="$3" arch_type="$4"

local download_url
local download_path
local checksum_path
local expected_checksum
local actual_checksum
download_url=$(safe_subshell build_tarball_github_uri "$version" "$os" "$arch_type")
download_path="$download_dir/$(basename "$download_url")"

checksum_path="$download_path".sha256sum
actual_checksum="$expected_checksum-actual" # This ensures that they do not match
expected_checksum=$(safe_subshell get_tarball_sha256 "$version" "$os" "$arch_type")

if [ -e "$download_path" ] && [ -e "$checksum_path" ]; then
expected_checksum=$(<"$checksum_path")
actual_checksum=$(sha256sum "$download_path" | cut -d ' ' -f 1)
fi

if [ "$actual_checksum" == "$expected_checksum" ]; then
info "Existing installation with matching checksum found. Skipping downloading..."
return 0
fi

download_file "$download_url" "$download_path"
info "Downloaded Neovim tarball ${version} for ${os} (${arch_type}) to ${download_path}"

# Save checksum for future verification
echo "$expected_checksum" >"$checksum_path"
}

# Parse command-line options
while getopts "v:d:o:t:a:h" opt; do
case $opt in
Expand Down Expand Up @@ -129,8 +162,10 @@ fi

if [[ $download_type == "source" ]]; then
download_neovim_source "$nvim_version" "$download_dir"
elif [[ $download_type == "tarball" ]]; then
download_neovim_tarball "$os_name" "$nvim_version" "$download_dir" "$arch_type"
elif [[ $download_type == "system" ]]; then
error "Cannot download a system-type Neovim release. Choose from either 'source' or 'binary'."
error "Cannot download a system-type Neovim release. Choose from 'source', 'binary', or 'tarball'."
exit 1
else
download_neovim "$os_name" "$nvim_version" "$download_dir" "$arch_type"
Expand Down
63 changes: 62 additions & 1 deletion scripts/neovim_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Options:
-d Specify directory for storing Neovim binaries.
NOTE: Installation would happen in 'nvim-downloads' subdirectory.
-f Force installation. Would overwrite any existing installation.
-m Installation method: binary, source, system
-m Installation method: binary, tarball, source, system
-a Architecture type of the machine
-o Offline mode. Assume release is already downloaded.
-h Display this help message and exit.
Expand Down Expand Up @@ -126,6 +126,51 @@ function setup_neovim_linux_appimage() {
ln -sf "$nvim_version_dir"/usr/bin/nvim "$nvim_binary"
}

# Install on Linux using tarball
function setup_neovim_linux_tarball() {
local version="$1" arch_type="$2"

local nvim_release_name
local download_url
download_url=$(safe_subshell build_tarball_github_uri "$version" "Linux" "$arch_type")
nvim_release_name=$(basename "$download_url")
local nvim_tarball_temp_path="$temp_dir/$nvim_release_name"

if [ ! -e "$nvim_version_dir/$nvim_release_name" ]; then
error "Expected release to be present at $nvim_version_dir/$nvim_release_name. Aborting..."
exit 1
fi

cp "$nvim_version_dir/$nvim_release_name" "$nvim_tarball_temp_path"

info "Extracting Neovim tarball..."
tar -xzf "$nvim_tarball_temp_path" -C "$temp_dir"

info "Finishing up installing Neovim..."
# Determine the extracted directory name based on version
local extracted_dir
local is_lesser
is_lesser_version "$version" v0.10.4
is_lesser=$?

if [[ $is_lesser -eq 0 ]]; then
# Older versions extract to nvim-linux64
extracted_dir="nvim-linux64"
else
# Newer versions extract to nvim-linux-<arch>
extracted_dir="nvim-linux-${arch_type}"
fi

if [ ! -d "$temp_dir/$extracted_dir" ]; then
error "Expected extracted directory $temp_dir/$extracted_dir not found. Aborting..."
exit 1
fi

# Move extracted contents to version directory
mkdir -p "$nvim_version_dir"
mv -f "$temp_dir/$extracted_dir"/* "$nvim_version_dir"
}

# Function to download and decompress Neovim binary for macOS
function setup_neovim_macos() {
local version="$1" arch_type="$2"
Expand Down Expand Up @@ -196,6 +241,22 @@ function install_neovim() {
echo "Unsupported operating system: $(uname)"
exit 1
fi
elif [[ $install_method == "tarball" ]]; then
if [ "$offline_mode" == true ]; then
info "Operating in offline mode. Will not download Neovim tarball"
else
"$download_neovim_script" -o "$os" -v "$nvim_version" -d "$nvim_version_dir" -t "tarball" -a "$arch_type"
fi

# Install Neovim tarball based on the detected OS
if [[ $os == "Linux" ]]; then
safe_subshell setup_neovim_linux_tarball "$nvim_version" "$arch_type"
elif [[ $os == "Darwin" ]]; then
safe_subshell setup_neovim_macos "$nvim_version" "$arch_type"
else
echo "Unsupported operating system: $(uname)"
exit 1
fi
elif [[ $install_method == "source" ]]; then
if [ "$offline_mode" == true ]; then
info "Operating in offline mode. Will not download Neovim source"
Expand Down
85 changes: 85 additions & 0 deletions scripts/utils/neovim.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ function _linux_asset_name {
echo "$asset_name"
}

function _linux_tarball_asset_name {
# If version is less than 0.10.4, tarball name is nvim-linux64.tar.gz
# else it includes architecture: nvim-linux-x86_64.tar.gz or nvim-linux-arm64.tar.gz
local version="$1" arch_type="$2"
local asset_name="nvim-linux-${arch_type}.tar.gz"

info "Determining tarball asset name for Neovim version $version on Linux with architecture $arch_type"
local is_lesser
is_lesser_version "$version" v0.10.4
is_lesser=$?

if [[ $is_lesser -eq 0 ]]; then
asset_name="nvim-linux64.tar.gz"
fi
echo "$asset_name"
}

function _macos_asset_name {
# If version is less than 0.10.0, there is no architecture in the download URL
# else it has to be added into the URL suffix.
Expand Down Expand Up @@ -89,6 +106,22 @@ function _get_asset_name {
echo "$asset_name"
}

function _get_tarball_asset_name {
local version="$1" os="$2" arch_type="$3"

local asset_name
if [[ $os == "Linux" ]]; then
asset_name=$(safe_subshell _linux_tarball_asset_name "$version" "$arch_type")
elif [[ $os == "Darwin" ]]; then
asset_name=$(safe_subshell _macos_asset_name "$version" "$arch_type")
else
fatal --status=3 "Unsupported OS: $os"
fi

debug "Tarball asset name for ${os} ${arch_type} version ${version}: ${asset_name}"
echo "$asset_name"
}

# Get release download URL based on OS, Arch type and version
function build_github_uri {
local VERSION=$1 OS=$2 ARCH_TYPE=$3
Expand All @@ -101,6 +134,18 @@ function build_github_uri {
echo "${BASE_GITHUB_URI_PATH}/${ASSET_NAME}"
}

# Get tarball download URL based on OS, Arch type and version
function build_tarball_github_uri {
local VERSION=$1 OS=$2 ARCH_TYPE=$3

local BASE_GITHUB_URI_PATH="https://github.com/neovim/neovim/releases/download/${VERSION}"

local ASSET_NAME
ASSET_NAME=$(safe_subshell _get_tarball_asset_name "$VERSION" "$OS" "$ARCH_TYPE")

echo "${BASE_GITHUB_URI_PATH}/${ASSET_NAME}"
}

function _find_sha256_for_version {
local url="$1"
local release_json
Expand Down Expand Up @@ -168,3 +213,43 @@ function get_sha256 {
debug "SHA256 sum: $SHA256_SUM for release item $DOWNLOAD_URI"
echo "$SHA256_SUM"
}

function get_tarball_sha256 {
local VERSION=$1 OS=$2 ARCH_TYPE=$3

local DOWNLOAD_URI
DOWNLOAD_URI=$(safe_subshell build_tarball_github_uri "$VERSION" "$OS" "$ARCH_TYPE")

local is_lesser
is_lesser_version "$VERSION" v0.11.3
is_lesser=$?

local SHA256_URI
local SHA256_SUM
if [[ $is_lesser -eq 0 ]]; then
info "Neovim version $VERSION is less than 0.11.3, using legacy checksum file"

SHA256_URI="${DOWNLOAD_URI}.sha256sum"
SHA256_SUM=$(safe_subshell run_api_call "$SHA256_URI")

SHA256_SUM=$(safe_subshell printf '%s\n' "$SHA256_SUM" | awk '{print $1}')
else
info "Neovim version $VERSION is greater than or equal to 0.11.3, using GitHub's checksum API"

SHA256_URI="https://api.github.com/repos/neovim/neovim/releases/tags/${VERSION}"
debug "Downloading SHA256 from $SHA256_URI"

local response
response=$(safe_subshell run_api_call "$SHA256_URI")
debug "Response from GitHub API: $response"

SHA256_SUM=$(safe_subshell printf '%s\n' "$response" | _find_sha256_for_version "$DOWNLOAD_URI")
fi

if [[ -z $SHA256_SUM ]]; then
fatal --status=3 "Failed to retrieve SHA256 sum for release item $DOWNLOAD_URI"
fi

debug "SHA256 sum: $SHA256_SUM for release item $DOWNLOAD_URI"
echo "$SHA256_SUM"
}
1 change: 1 addition & 0 deletions tests/remote-nvim/providers/provider_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ describe("Provider", function()
assert.are.same({
provider = "local",
host = provider.host,
is_docker_container = false,
arch = "x86_64",
neovim_install_method = "binary",
connection_options = provider.conn_opts,
Expand Down