diff --git a/app/gitspace/orchestrator/common/script/os_info.sh b/app/gitspace/orchestrator/common/script/os_info.sh index 0d186bc1b..6acdfb59e 100644 --- a/app/gitspace/orchestrator/common/script/os_info.sh +++ b/app/gitspace/orchestrator/common/script/os_info.sh @@ -1,4 +1,4 @@ -# Detect OS type + os() { uname="$(uname)" case $uname in @@ -9,7 +9,6 @@ os() { esac } -# Detect Linux distro type distro() { local os_name os_name=$(os) @@ -38,7 +37,6 @@ distro() { fi } -# Print a human-readable name for the OS/distro distro_name() { if [ "$(uname)" = "Darwin" ]; then echo "macOS v$(sw_vers -productVersion)" diff --git a/app/gitspace/orchestrator/common/utils.go b/app/gitspace/orchestrator/common/utils.go new file mode 100644 index 000000000..2e5be8a55 --- /dev/null +++ b/app/gitspace/orchestrator/common/utils.go @@ -0,0 +1,114 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "fmt" + + "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" + "github.com/harness/gitness/app/gitspace/orchestrator/template" + "github.com/harness/gitness/types/enum" +) + +const templateSupportedOSDistribution = "supported_os_distribution.sh" +const templateVsCodeWebToolsInstallation = "install_tools_vs_code_web.sh" +const templateVsCodeToolsInstallation = "install_tools_vs_code.sh" + +func ValidateSupportedOS(ctx context.Context, exec *devcontainer.Exec) ([]byte, error) { + // TODO: Currently not supporting arch, freebsd and alpine. + // For alpine wee need to install multiple things from + // https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites + script, err := template.GenerateScriptFromTemplate( + templateSupportedOSDistribution, &template.SupportedOSDistributionPayload{ + OSInfoScript: osDetectScript, + }) + if err != nil { + return nil, fmt.Errorf("failed to generate scipt to validate supported os distribution from template %s: %w", + templateSupportedOSDistribution, err) + } + + output, err := exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + if err != nil { + return nil, fmt.Errorf("error while detecting os distribution: %w", err) + } + return output, nil +} + +func InstallTools(ctx context.Context, exec *devcontainer.Exec, ideType enum.IDEType) ([]byte, error) { + switch ideType { + case enum.IDETypeVSCodeWeb: + { + output, err := InstallToolsForVsCodeWeb(ctx, exec) + if err != nil { + return []byte(output), err + } + return []byte(output), nil + } + case enum.IDETypeVSCode: + { + output, err := InstallToolsForVsCode(ctx, exec) + if err != nil { + return []byte(output), err + } + return []byte(output), nil + } + } + return nil, nil +} + +func InstallToolsForVsCodeWeb(ctx context.Context, exec *devcontainer.Exec) (string, error) { + script, err := template.GenerateScriptFromTemplate( + templateVsCodeWebToolsInstallation, &template.InstallToolsPayload{ + OSInfoScript: osDetectScript, + }) + if err != nil { + return "", fmt.Errorf( + "failed to generate scipt to install tools for vs code web from template %s: %w", + templateVsCodeWebToolsInstallation, err) + } + + output := "Installing tools for vs code web inside container\n" + _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + if err != nil { + return "", fmt.Errorf("failed to install tools for vs code web: %w", err) + } + + output += "Successfully installed tools for vs code web\n" + + return output, nil +} + +func InstallToolsForVsCode(ctx context.Context, exec *devcontainer.Exec) (string, error) { + script, err := template.GenerateScriptFromTemplate( + templateVsCodeToolsInstallation, &template.InstallToolsPayload{ + OSInfoScript: osDetectScript, + }) + if err != nil { + return "", fmt.Errorf( + "failed to generate scipt to install tools for vs code from template %s: %w", + templateVsCodeToolsInstallation, err) + } + + output := "Installing tools for vs code inside container\n" + _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) + if err != nil { + return "", fmt.Errorf("failed to install tools for vs code: %w", err) + } + + output += "Successfully installed tools for vs code\n" + + return output, nil +} diff --git a/app/gitspace/orchestrator/container/embedded_docker.go b/app/gitspace/orchestrator/container/embedded_docker.go index a86edd3be..25cb55845 100644 --- a/app/gitspace/orchestrator/container/embedded_docker.go +++ b/app/gitspace/orchestrator/container/embedded_docker.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/harness/gitness/app/gitspace/logutil" + "github.com/harness/gitness/app/gitspace/orchestrator/common" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/git" "github.com/harness/gitness/app/gitspace/orchestrator/ide" @@ -31,6 +32,7 @@ import ( "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/infraprovider" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -274,11 +276,21 @@ func (e *EmbeddedDockerOrchestrator) startGitspace( AccessType: gitspaceConfig.GitspaceInstance.AccessType, } + err = e.validateSupportedOS(ctx, exec, logStreamInstance) + if err != nil { + return err + } + err = e.manageUser(ctx, exec, logStreamInstance) if err != nil { return err } + err = e.installTools(ctx, exec, logStreamInstance, gitspaceConfig.IDE) + if err != nil { + return err + } + err = e.setupIDE(ctx, exec, ideService, logStreamInstance) if err != nil { return err @@ -544,9 +556,9 @@ func (e *EmbeddedDockerOrchestrator) executePostCreateCommand( return fmt.Errorf("logging error: %w", loggingErr) } - output, err := exec.ExecuteCommand(ctx, devcontainerConfig.PostCreateCommand, false, false, codeRepoDir) + output, err := exec.ExecuteCommand(ctx, devcontainerConfig.PostCreateCommand, true, false, codeRepoDir) if err != nil { - loggingErr = logStreamInstance.Write("Error while executing postCreate command") + loggingErr = logStreamInstance.Write("Error while executing postCreate command" + err.Error()) err = fmt.Errorf("failed to execute postCreate command %q: %w", devcontainerConfig.PostCreateCommand, err) @@ -951,6 +963,59 @@ func (e *EmbeddedDockerOrchestrator) removeContainer( return nil } +func (e *EmbeddedDockerOrchestrator) validateSupportedOS( + ctx context.Context, + exec *devcontainer.Exec, + logStreamInstance *logutil.LogStreamInstance, +) error { + output, err := common.ValidateSupportedOS(ctx, exec) + if err != nil { + loggingErr := logStreamInstance.Write("Error while detecting os inside container: " + err.Error()) + + err = fmt.Errorf("failed to detect os in %s: %w", exec.ContainerName, err) + + if loggingErr != nil { + err = fmt.Errorf("original error: %w; logging error: %w", err, loggingErr) + } + + return err + } + + loggingErr := logStreamInstance.Write("Validate supported OSes...\n" + string(output)) + if loggingErr != nil { + return fmt.Errorf("logging error: %w", loggingErr) + } + + return nil +} + +func (e *EmbeddedDockerOrchestrator) installTools( + ctx context.Context, + exec *devcontainer.Exec, + logStreamInstance *logutil.LogStreamInstance, + ideType enum.IDEType, +) error { + output, err := common.InstallTools(ctx, exec, ideType) + if err != nil { + loggingErr := logStreamInstance.Write("Error while installing tools inside container: " + err.Error()) + + err = fmt.Errorf("failed to install tools in %s: %w", exec.ContainerName, err) + + if loggingErr != nil { + err = fmt.Errorf("original error: %w; logging error: %w", err, loggingErr) + } + + return err + } + + loggingErr := logStreamInstance.Write("Tools installation output...\n" + string(output)) + if loggingErr != nil { + return fmt.Errorf("logging error: %w", loggingErr) + } + + return nil +} + func (e *EmbeddedDockerOrchestrator) StreamLogs( _ context.Context, _ types.GitspaceConfig, diff --git a/app/gitspace/orchestrator/git/git_impl.go b/app/gitspace/orchestrator/git/git_impl.go index e7a633d5b..9d042d3f0 100644 --- a/app/gitspace/orchestrator/git/git_impl.go +++ b/app/gitspace/orchestrator/git/git_impl.go @@ -48,7 +48,7 @@ func (g *ServiceImpl) Install(ctx context.Context, exec *devcontainer.Exec) ([]b "failed to generate scipt to setup git install from template %s: %w", templateGitInstallScript, err) } output := "Setting up git inside container\n" - _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, false, false) + _, err = exec.ExecuteCommandInHomeDirectory(ctx, script, true, false) if err != nil { return nil, fmt.Errorf("failed to setup git: %w", err) } diff --git a/app/gitspace/orchestrator/ide/vscode.go b/app/gitspace/orchestrator/ide/vscode.go index fabd3dc22..9bb3d7911 100644 --- a/app/gitspace/orchestrator/ide/vscode.go +++ b/app/gitspace/orchestrator/ide/vscode.go @@ -87,7 +87,7 @@ func (v *VSCode) Run(ctx context.Context, exec *devcontainer.Exec) ([]byte, erro execOutput, err := exec.ExecuteCommandInHomeDirectory(ctx, runSSHScript, true, false) if err != nil { - return nil, fmt.Errorf("failed to run SSH serverr: %w", err) + return nil, fmt.Errorf("failed to run SSH server: %w", err) } output += "SSH server run output...\n" + string(execOutput) + "\nSuccessfully run ssh-server\n" diff --git a/app/gitspace/orchestrator/template/template.go b/app/gitspace/orchestrator/template/template.go index d8eb64fa3..ae7aa1b7b 100644 --- a/app/gitspace/orchestrator/template/template.go +++ b/app/gitspace/orchestrator/template/template.go @@ -74,6 +74,14 @@ type RunSSHServerPayload struct { Port string } +type InstallToolsPayload struct { + OSInfoScript string +} + +type SupportedOSDistributionPayload struct { + OSInfoScript string +} + func init() { err := LoadTemplates() if err != nil { diff --git a/app/gitspace/orchestrator/template/templates/install_git.sh b/app/gitspace/orchestrator/template/templates/install_git.sh index 3d97214ac..c47ba207a 100644 --- a/app/gitspace/orchestrator/template/templates/install_git.sh +++ b/app/gitspace/orchestrator/template/templates/install_git.sh @@ -9,34 +9,30 @@ install_git() { # Check if Git is installed if ! command -v git >/dev/null 2>&1; then echo "Git is not installed. Installing Git..." - case "$(distro)" in - debian | ubuntu) - apt-get update && apt-get install -y git - ;; - fedora | centos | rhel) - dnf install -y git - ;; - opensuse) - zypper install -y git - ;; - alpine) - apk add git - ;; - arch | manjaro) - pacman -Sy --noconfirm git - ;; - freebsd) - pkg install -y git - ;; - macos) - brew install git - ;; - *) - echo "Unsupported OS for automatic Git installation." - return 1 - ;; - esac + debian) + apt-get update && apt-get install -y git + ;; + fedora) + dnf install -y git + ;; + opensuse) + zypper install -y git + ;; + alpine) + apk add git + ;; + arch) + pacman -Sy --noconfirm git + ;; + freebsd) + pkg install -y git + ;; + *) + echo "Unsupported OS for automatic Git installation." + return 1 + ;; + esac fi # Verify installation diff --git a/app/gitspace/orchestrator/template/templates/install_tools_vs_code.sh b/app/gitspace/orchestrator/template/templates/install_tools_vs_code.sh new file mode 100644 index 000000000..2ea806e9e --- /dev/null +++ b/app/gitspace/orchestrator/template/templates/install_tools_vs_code.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +osInfoScript={{ .OSInfoScript }} + +eval "$osInfoScript" + +echo "Checking if tar is installed..." +if ! command -v tar >/dev/null 2>&1; then + echo "Installing tar for $(distro)" + case "$(distro)" in + opensuse) + zypper install -y tar + zypper install -y gzip + ;; + *) + echo "Distribution: $distro. tar installation not required" + exit 1 + ;; + esac + echo "tar installation completed." +fi \ No newline at end of file diff --git a/app/gitspace/orchestrator/template/templates/install_tools_vs_code_web.sh b/app/gitspace/orchestrator/template/templates/install_tools_vs_code_web.sh new file mode 100644 index 000000000..6c79f741c --- /dev/null +++ b/app/gitspace/orchestrator/template/templates/install_tools_vs_code_web.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +osInfoScript={{ .OSInfoScript }} + +eval "$osInfoScript" + +# Check if curl is installed +echo "Checking if curl is installed..." + +if ! command -v curl >/dev/null 2>&1; then + echo "Installing curl for $(distro)" + case "$(distro)" in + debian) + apt update && apt install -y curl + ;; + fedora) + dnf install -y curl + ;; + opensuse) + zypper install -y curl + ;; + alpine) + apk add --no-cache curl + ;; + arch) + pacman -Syu --noconfirm curl + ;; + freebsd) + pkg install -y curl + ;; + *) + echo "Unsupported distribution: $distro." + exit 1 + ;; + esac + echo "Curl installation completed." +fi + +if ! command -v npm >/dev/null 2>&1; then + echo "Installing npm..." + case "$(distro)" in + alpine) + echo "Detected Alpine Linux. Installing npm..." + apk update + apk add nodejs npm + ;; + freebsd) + echo "Detected FreeBSD. Installing npm..." + pkg update + pkg install -y node + ;; + *) + echo "Distribution: $distro. npm installation not required" + ;; + esac + echo "npm installation completed." +fi \ No newline at end of file diff --git a/app/gitspace/orchestrator/template/templates/manage_user.sh b/app/gitspace/orchestrator/template/templates/manage_user.sh index 5ae118d88..953dd5a16 100644 --- a/app/gitspace/orchestrator/template/templates/manage_user.sh +++ b/app/gitspace/orchestrator/template/templates/manage_user.sh @@ -10,18 +10,39 @@ eval "$osInfoScript" # Check if the user already exists if id "$username" >/dev/null 2>&1; then - echo "User $username already exists." + echo "User $username already exists." else - # Create a new user - case "$(distro)" in - debian | ubuntu) + # Create a new user + case "$(distro)" in + debian) + apt-get update && apt-get install -y adduser adduser --disabled-password --home "$homeDir" --gecos "" "$username" + usermod -aG sudo "$username" ;; - fedora | centos | rhel) + fedora) useradd -m -d "$homeDir" "$username" + usermod -aG wheel "$username" + ;; + opensuse) + useradd -m -d "$homeDir" "$username" + passwd -l "$username" # Locks the password to prevent login + zypper in -y sudo + groupadd sudo + usermod -aG sudo "$username" + echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers + chown -R "$username":sudo "$homeDir" + ;; + alpine) + adduser -h "$homeDir" -s /bin/ash -D "$username" # Default shell is ash for Alpine + ;; + arch) + useradd -m -d "$homeDir" -s /bin/bash "$username" + ;; + freebsd) + pw useradd -n "$username" -d "$homeDir" -m ;; *) - echo "Unsupported distribution for user creation. Exiting..." + echo "Unsupported distribution: $distro." exit 1 ;; esac diff --git a/app/gitspace/orchestrator/template/templates/run_ssh_server.sh b/app/gitspace/orchestrator/template/templates/run_ssh_server.sh index 1e1f00df2..0c57cd84d 100644 --- a/app/gitspace/orchestrator/template/templates/run_ssh_server.sh +++ b/app/gitspace/orchestrator/template/templates/run_ssh_server.sh @@ -4,6 +4,28 @@ SSH_PORT={{ .Port }} config_file='/etc/ssh/sshd_config' +HOST_KEYS="/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_ed25519_key" +for KEY in $HOST_KEYS; do + if [ ! -f "$KEY" ]; then + echo "Generating host key: $KEY" + case "$KEY" in + "/etc/ssh/ssh_host_rsa_key") + ssh-keygen -t rsa -b 4096 -f "$KEY" -N "" + ;; + "/etc/ssh/ssh_host_ecdsa_key") + ssh-keygen -t ecdsa -b 521 -f "$KEY" -N "" + ;; + "/etc/ssh/ssh_host_ed25519_key") + ssh-keygen -t ed25519 -f "$KEY" -N "" + ;; + esac + chmod 600 "$KEY" + chmod 644 "${KEY}.pub" + else + echo "Host key already exists: $KEY" + fi +done + # Change the default SSH port sed -i "s/^#Port 22/Port $SSH_PORT/" $config_file if ! grep -q "^Port $SSH_PORT" $config_file; then diff --git a/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh b/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh index a9838370c..c5b48a241 100644 --- a/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh +++ b/app/gitspace/orchestrator/template/templates/setup_ssh_server.sh @@ -9,24 +9,32 @@ if ! command -v sshd >/dev/null 2>&1; then echo "OpenSSH server is not installed. Installing..." case "$(distro)" in - debian | ubuntu) + debian) apt-get update apt-get install -y openssh-server - ;; - fedora | centos | rhel) + ;; + fedora) dnf install -y openssh-server - ;; + ;; opensuse) zypper install -y openssh - ;; + ;; alpine) - apk add openssh - ;; + apk add openssh + ;; + arch) + pacman -Syu --noconfirm openssh + ;; + freebsd) + pkg install -y openssh-portable + ;; *) - echo "Unsupported distribution for SSH installation. Exiting..." - exit 1 - ;; - esac + echo "Unsupported distribution: $distro." + exit 1 + ;; + esac + + else echo "OpenSSH server is already installed." fi diff --git a/app/gitspace/orchestrator/template/templates/supported_os_distribution.sh b/app/gitspace/orchestrator/template/templates/supported_os_distribution.sh new file mode 100644 index 000000000..6a3c16b90 --- /dev/null +++ b/app/gitspace/orchestrator/template/templates/supported_os_distribution.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +osInfoScript={{ .OSInfoScript }} + +eval "$osInfoScript" + +case "$(distro)" in + debian|fedora|opensuse) + echo "Detected $(distro) distribution" + ;; + *) + echo "Unsupported distribution: $distro." >&2 + exit 1 + ;; +esac