From aa127f77ee6a0b502f47f954d61546dafeb39e7c Mon Sep 17 00:00:00 2001 From: Szymon Osiecki Date: Sun, 1 Oct 2023 21:21:23 +0200 Subject: [PATCH] feat: repository modules --- .assets/config/omp_cfg/base.omp.json | 2 +- .assets/provision/fix_certifi_certs.sh | 1 - .assets/provision/install_miniconda.sh | 7 + .assets/provision/install_pwsh.sh | 2 +- .assets/provision/setup_gh_repos.sh | 5 +- .assets/scripts/linux_setup.sh | 44 +++-- .assets/scripts/modules_update.ps1 | 91 ++++++++++ .assets/tools/gh_repo_clone.ps1 | 65 ------- modules/InstallUtils/Functions/common.ps1 | 71 ++++++++ modules/InstallUtils/Functions/git.ps1 | 68 +++++++ modules/InstallUtils/InstallUtils.psd1 | 138 +++++++++++++++ modules/InstallUtils/InstallUtils.psm1 | 19 ++ modules/SetupUtils/Functions/certs.ps1 | 91 ++++++++++ modules/SetupUtils/Functions/common.ps1 | 205 ++++++++++++++++++++++ modules/SetupUtils/SetupUtils.psd1 | 139 +++++++++++++++ modules/SetupUtils/SetupUtils.psm1 | 20 +++ scripts_egsave.ps1 | 7 +- wsl/pwsh_setup.ps1 | 4 +- wsl/wsl_certs_add.ps1 | 41 ++--- wsl/wsl_install.ps1 | 75 ++++++++ wsl/wsl_network_fix.ps1 | 19 +- wsl/wsl_setup.ps1 | 73 ++++---- wsl/wsl_systemd.ps1 | 19 +- 23 files changed, 1029 insertions(+), 177 deletions(-) create mode 100644 .assets/scripts/modules_update.ps1 delete mode 100755 .assets/tools/gh_repo_clone.ps1 create mode 100644 modules/InstallUtils/Functions/common.ps1 create mode 100644 modules/InstallUtils/Functions/git.ps1 create mode 100644 modules/InstallUtils/InstallUtils.psd1 create mode 100644 modules/InstallUtils/InstallUtils.psm1 create mode 100644 modules/SetupUtils/Functions/certs.ps1 create mode 100644 modules/SetupUtils/Functions/common.ps1 create mode 100644 modules/SetupUtils/SetupUtils.psd1 create mode 100644 modules/SetupUtils/SetupUtils.psm1 create mode 100644 wsl/wsl_install.ps1 diff --git a/.assets/config/omp_cfg/base.omp.json b/.assets/config/omp_cfg/base.omp.json index b94088c6..b3537a02 100644 --- a/.assets/config/omp_cfg/base.omp.json +++ b/.assets/config/omp_cfg/base.omp.json @@ -72,7 +72,7 @@ "tag_icon": "" }, "style": "plain", - "template": "<#E8CC97>({{ .HEAD }}<#E8CC97>) ", + "template": "<#E8CC97>({{ .HEAD }}<#E8CC97>) ", "type": "git" }, { diff --git a/.assets/provision/fix_certifi_certs.sh b/.assets/provision/fix_certifi_certs.sh index c249b395..197ea0f1 100755 --- a/.assets/provision/fix_certifi_certs.sh +++ b/.assets/provision/fix_certifi_certs.sh @@ -29,7 +29,6 @@ esac # get list of installed certificates cert_paths=($(ls $CERT_PATH/*.crt 2>/dev/null)) if [ -z "$cert_paths" ]; then - printf '\nThere are no certificate(s) to install.\n' >&2 exit 0 fi diff --git a/.assets/provision/install_miniconda.sh b/.assets/provision/install_miniconda.sh index 29f180b2..6c7417ed 100755 --- a/.assets/provision/install_miniconda.sh +++ b/.assets/provision/install_miniconda.sh @@ -67,3 +67,10 @@ fi # *Update conda. conda update -n base -c defaults conda --yes conda clean --yes --all + +# *Fix certificates after update. +if $fix_certify; then + conda activate base + .assets/provision/fix_certifi_certs.sh + conda deactivate +fi diff --git a/.assets/provision/install_pwsh.sh b/.assets/provision/install_pwsh.sh index 20b8e5c0..7dce249c 100755 --- a/.assets/provision/install_pwsh.sh +++ b/.assets/provision/install_pwsh.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash : ' -sudo .assets/provision/install_pwsh.sh $(id -un) >/dev/null +sudo .assets/provision/install_pwsh.sh >/dev/null ' if [ $EUID -ne 0 ]; then printf '\e[31;1mRun the script as root.\e[0m\n' diff --git a/.assets/provision/setup_gh_repos.sh b/.assets/provision/setup_gh_repos.sh index 87ca44af..da3896cd 100755 --- a/.assets/provision/setup_gh_repos.sh +++ b/.assets/provision/setup_gh_repos.sh @@ -1,11 +1,10 @@ #!/usr/bin/env bash : ' -.assets/provision/setup_gh_repos.sh --repos "szymonos/linux-setup-scripts szymonos/ps-modules" --user "szymo" -.assets/provision/setup_gh_repos.sh --repos "szymonos/linux-setup-scripts szymonos/ps-modules" --user "szymo" --ws_suffix "scripts" +.assets/provision/setup_gh_repos.sh --repos "szymonos/linux-setup-scripts szymonos/ps-modules" +.assets/provision/setup_gh_repos.sh --repos "szymonos/linux-setup-scripts szymonos/ps-modules" --ws_suffix "scripts" ' # parse named parameters repos=${repos} -user=${user} ws_suffix=${ws_suffix:-devops} while [ $# -gt 0 ]; do if [[ $1 == *"--"* ]]; then diff --git a/.assets/scripts/linux_setup.sh b/.assets/scripts/linux_setup.sh index 4f0a23ba..c4b57a72 100755 --- a/.assets/scripts/linux_setup.sh +++ b/.assets/scripts/linux_setup.sh @@ -3,8 +3,8 @@ # :set up the system using default values .assets/scripts/linux_setup.sh # :set up the system using specified values -scope="shell" -scope="k8s_base python shell" +scope="pwsh" +scope="k8s_base python pwsh" scope="az distrobox docker k8s_base k8s_ext python rice shell" # :set up the system using the specified scope .assets/scripts/linux_setup.sh --scope "$scope" @@ -41,20 +41,24 @@ pushd "$(cd "${SCRIPT_ROOT}/../../" && pwd)" >/dev/null # *Calculate and show installation scopes # convert scope string to array array=($scope) -# determine scope for update if not provided -if [ -z "$array" ]; then - [ -f /usr/bin/kubectl ] && array+=(k8s_base) || true - [ -f /usr/bin/kustomize ] && array+=(k8s_ext) || true - [ -f /usr/bin/pwsh ] && array+=(shell) || true - [ -d "$HOME/miniconda3" ] && array+=(python) || true -fi +# determine additional scopes +[ -d "$HOME/.local/share/powershell/Modules/Az" ] && array+=(az) || true +[ -f /usr/bin/kubectl ] && array+=(k8s_base) || true +[ -f /usr/local/bin/k3d ] && array+=(k8s_ext) || true +[ -f /usr/bin/pwsh ] && array+=(pwsh) || true +[ -d "$HOME/miniconda3" ] && array+=(python) || true +[ -f /usr/bin/rg ] && array+=(shell) || true +# add corresponding scopes +grep -qw 'az' <<<${array[@]} && array+=(python) || true +grep -qw 'k8s_ext' <<<${array[@]} && array+=(docker) && array+=(k8s_base) || true +grep -qw 'pwsh' <<<${array[@]} && array+=(shell) || true # add oh_my_posh scope if necessary if [[ -n "$omp_theme" || -f /usr/bin/oh-my-posh ]]; then array+=(oh_my_posh) - grep -qw 'shell' <<<${array[@]} || array+=(shell) + array+=(shell) fi # sort array -IFS=$'\n' scope_arr=($(sort <<<${array[*]})) && unset IFS +IFS=$'\n' scope_arr=($(sort --unique <<<${array[*]})) && unset IFS # get distro name from os-release . /etc/os-release # display distro name and scopes to install @@ -86,7 +90,6 @@ for sc in ${scope_arr[@]}; do sudo .assets/provision/install_minikube.sh >/dev/null sudo .assets/provision/install_k3d.sh >/dev/null sudo .assets/provision/install_k9s.sh >/dev/null - sudo .assets/provision/install_yq.sh >/dev/null ;; k8s_ext) printf "\e[96minstalling kubernetes additional packages...\e[0m\n" @@ -102,6 +105,14 @@ for sc in ${scope_arr[@]}; do sudo .assets/provision/setup_omp.sh --theme $omp_theme --user $user fi ;; + pwsh) + printf "\e[96minstalling pwsh...\e[0m\n" + sudo .assets/provision/install_pwsh.sh >/dev/null + printf "\e[96msetting up profile for all users...\e[0m\n" + sudo .assets/provision/setup_profile_allusers.ps1 -UserName $user + printf "\e[96msetting up profile for current user...\e[0m\n" + .assets/provision/setup_profile_user.ps1 + ;; python) printf "\e[96minstalling python packages...\e[0m\n" .assets/provision/install_miniconda.sh --fix_certify true @@ -117,23 +128,22 @@ for sc in ${scope_arr[@]}; do ;; shell) printf "\e[96minstalling shell packages...\e[0m\n" - sudo .assets/provision/install_pwsh.sh >/dev/null sudo .assets/provision/install_eza.sh >/dev/null sudo .assets/provision/install_bat.sh >/dev/null sudo .assets/provision/install_ripgrep.sh >/dev/null + sudo .assets/provision/install_yq.sh >/dev/null printf "\e[96msetting up profile for all users...\e[0m\n" sudo .assets/provision/setup_profile_allusers.sh $user - sudo .assets/provision/setup_profile_allusers.ps1 -UserName $user printf "\e[96msetting up profile for current user...\e[0m\n" .assets/provision/setup_profile_user.sh - .assets/provision/setup_profile_user.ps1 ;; esac done # install powershell modules if [ -f /usr/bin/pwsh ]; then - cloned=$(.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') - if [ "$cloned" = "True" ]; then + cmd="Import-Module (Resolve-Path './modules/InstallUtils'); Invoke-GhRepoClone -OrgRepo 'szymonos/ps-modules'" + cloned=$(pwsh -nop -c $cmd) + if [ $cloned -gt 0 ]; then printf "\e[96minstalling ps-modules...\e[0m\n" # install do-common module for all users printf "\e[3;32mAllUsers\e[23m : do-common\e[0m\n" diff --git a/.assets/scripts/modules_update.ps1 b/.assets/scripts/modules_update.ps1 new file mode 100644 index 00000000..44dfd867 --- /dev/null +++ b/.assets/scripts/modules_update.ps1 @@ -0,0 +1,91 @@ +#Requires -PSEdition Core +<# +.SYNOPSIS +Update repository modules from ps-modules. + +.EXAMPLE +.assets/scripts/modules_update.ps1 +#> +[CmdletBinding()] +param () + +begin { + $ErrorActionPreference = 'Stop' + + # check if repository is up to date + Write-Host 'refreshing current repository...' -ForegroundColor Cyan + git fetch + $remote = "$(git remote)/$(git branch --show-current)" + if ((git rev-parse HEAD) -ne (git rev-parse $remote)) { + Write-Warning "Current branch is behind remote, performing hard reset.`n`t Run the script again!`n" + git reset --hard $remote + exit 0 + } + + # set location to workspace folder + Push-Location "$PSScriptRoot/../.." + # import InstallUtils for the Invoke-GhRepoClone function + Import-Module (Resolve-Path './modules/InstallUtils') + + # specify update functions structure + $import = @{ + 'do-common' = @{ + SetupUtils = @{ + certs = @( + 'ConvertTo-PEM' + 'Get-Certificate' + ) + common = @( + 'ConvertFrom-Cfg' + 'ConvertTo-Cfg' + 'Invoke-ExampleScriptSave' + ) + } + } + 'psm-windows' = @{ + InstallUtils = @{ + common = @( + 'Invoke-CommandRetry' + 'Test-IsAdmin' + 'Update-SessionEnvironmentPath' + ) + } + } + } +} + +process { + # *refresh ps-modules repository + Write-Host 'refreshing ps-modules repository...' -ForegroundColor Cyan + if ((Invoke-GhRepoClone -OrgRepo 'szymonos/ps-modules' -Path '..') -eq 0) { + Write-Error 'Cloning ps-modules repository failed.' + } + + # *perform update + Write-Host 'perform modules update..' -ForegroundColor Cyan + foreach ($srcModule in $import.GetEnumerator()) { + Import-Module (Resolve-Path "../ps-modules/modules/$($srcModule.Key)") + Write-Host "`n$($srcModule.Key)" -ForegroundColor Green + foreach ($dstModule in $srcModule.Value.GetEnumerator()) { + Write-Host $dstModule.Key + foreach ($destFile in $dstModule.Value.GetEnumerator()) { + Write-Host " - $($destFile.Key).ps1" + $filePath = "./modules/$($dstModule.Key)/Functions/$($destFile.Key).ps1" + Set-Content -Value $null -Path $filePath + $builder = [System.Text.StringBuilder]::new() + foreach ($function in $destFile.Value) { + Write-Host " • $function" + $def = " $((Get-Command $function -CommandType Function).Definition.Trim())" + $builder.AppendLine("function $function {") | Out-Null + $builder.AppendLine($def) | Out-Null + $builder.AppendLine("}`n") | Out-Null + } + Set-Content -Value $builder.ToString().Trim().Replace("`r`n", "`n") -Path $filePath -Encoding utf8 + } + } + } +} + +end { + Pop-Location +} diff --git a/.assets/tools/gh_repo_clone.ps1 b/.assets/tools/gh_repo_clone.ps1 deleted file mode 100755 index 9282c84e..00000000 --- a/.assets/tools/gh_repo_clone.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/pwsh -nop -<# -.SYNOPSIS -Clone specified GitHub repository name. - -.PARAMETER OrgRepo -GitHub repository in the Organization/RepoName format. - -.EXAMPLE -$OrgRepo = 'szymonos/ps-modules' -$result = .assets/tools/gh_repo_clone.ps1 -r $OrgRepo -#> -[CmdletBinding()] -[OutputType([bool])] -param ( - [Alias('r')] - [Parameter(Mandatory, Position = 0, ValueFromPipeline)] - [ValidateScript({ $_ -match '^[\w-\.]+/[\w-\.]+$' })] - [string]$OrgRepo -) - -begin { - $ErrorActionPreference = 'Stop' - - # determine organisation and repository name - $org, $repo = $OrgRepo.Split('/') - # command for getting the remote url - $getOrigin = { git config --get remote.origin.url } -} - -process { - try { - Push-Location "../$repo" - $cloned = if ($(Invoke-Command $getOrigin) -match "github\.com[:/]$org/$repo\b") { - # refresh target repository - git fetch --prune --quiet - git switch main --force --quiet 2>$null - git reset --hard --quiet origin/main - # ps-modules repo refreshed successfully - $true - } else { - Write-Warning "Another `"$targetRepo`" repository exists." - # repo remote not match - $false - } - Pop-Location - } catch { - # determine GitHub protocol used (https/ssl) - $gitProtocol = $(Invoke-Command $getOrigin) -replace '(^.+github\.com[:/]).*', '$1' - # clone target repository - git clone "${gitProtocol}$org/$repo" "../$repo" --quiet - # determine state of cloning the repository - $cloned = if ($?) { - $true - } else { - Write-Warning "Cloning of the `"$OrgRepo`" repository failed." - $false - } - } -} - -end { - # return status if repository has been cloned successfully - return $cloned -} diff --git a/modules/InstallUtils/Functions/common.ps1 b/modules/InstallUtils/Functions/common.ps1 new file mode 100644 index 00000000..5db541b1 --- /dev/null +++ b/modules/InstallUtils/Functions/common.ps1 @@ -0,0 +1,71 @@ +function Invoke-CommandRetry { + [CmdletBinding()] + param ( + [Parameter(Mandatory, Position = 0, HelpMessage = 'The command to be invoked.')] + [scriptblock]$Command, + + [Parameter(HelpMessage = 'The number of retries the command should be invoked.')] + [int]$MaxRetries = 10 + ) + + Set-Variable -Name retryCount -Value 0 + do { + try { + Invoke-Command -ScriptBlock $Command + $exit = $true + } catch [System.IO.IOException] { + if ($_.Exception.TargetSite.Name -eq 'MoveNext') { + if ($_.ErrorDetails) { + Write-Verbose $_.ErrorDetails.Message + } else { + Write-Verbose $_.Exception.Message + } + Write-Host "`nRetrying..." + } else { + Write-Verbose $_.Exception.GetType().FullName + Write-Error $_ + } + } catch [System.AggregateException] { + if ($_.Exception.InnerException.GetType().Name -eq 'HttpRequestException') { + Write-Verbose $_.Exception.InnerException.Message + Write-Host "`nRetrying..." + } else { + Write-Verbose $_.Exception.InnerException.GetType().FullName + Write-Error $_ + } + } catch { + Write-Verbose $_.Exception.GetType().FullName + Write-Error $_ + } + $retryCount++ + if ($retryCount -eq $MaxRetries) { + $exit = $true + } + } until ($exit) +} + +function Test-IsAdmin { + $currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]$currentIdentity + $admin = [System.Security.Principal.WindowsBuiltInRole]'Administrator' + + return $principal.IsInRole($admin) +} + +function Update-SessionEnvironmentPath { + # instantiate a HashSet to store unique paths + $auxHashSet = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + + # get Path env variable from all scopes and build unique set + foreach ($scope in @('Machine', 'User', 'Process')) { + [Environment]::GetEnvironmentVariable('Path', $scope).Split(';').Where({ $_ }).ForEach({ + $auxHashSet.Add($_) | Out-Null + } + ) + } + + # build a path string from the HashSet + $pathStr = [string]::Join([System.IO.Path]::PathSeparator, $auxHashSet) + # set the Path environment variable in the current process scope + [System.Environment]::SetEnvironmentVariable('Path', $pathStr, 'Process') +} diff --git a/modules/InstallUtils/Functions/git.ps1 b/modules/InstallUtils/Functions/git.ps1 new file mode 100644 index 00000000..72077856 --- /dev/null +++ b/modules/InstallUtils/Functions/git.ps1 @@ -0,0 +1,68 @@ +<# +.SYNOPSIS +Function for refreshing/cloning specified GitHub repository. + +.PARAMETER OrgRepo +GitHub repository provided as Org/Repo. +.PARAMETER Path +Destination path to clone/refresh repo to. +#> +function Invoke-GhRepoClone { + [CmdletBinding()] + [OutputType([int])] + param ( + [Alias('r')] + [Parameter(Mandatory, Position = 0, ValueFromPipeline)] + [ValidateScript({ $_ -match '^[\w-\.]+/[\w-\.]+$' })] + [string]$OrgRepo, + + [ValidateScript({ Test-Path $_ -PathType 'Container' })] + [string]$Path = '..' + ) + + begin { + $ErrorActionPreference = 'Stop' + + # determine organisation and repository name + $org, $repo = $OrgRepo.Split('/') + # command for getting the remote url + $getOrigin = { git config --get remote.origin.url } + # calculate destination path + $destPath = Join-Path $Path -ChildPath $repo + } + + process { + try { + Push-Location $destPath + $status = if ($(Invoke-Command $getOrigin) -match "github\.com[:/]$org/$repo\b") { + # refresh target repository + git fetch --prune --quiet + git switch main --force --quiet 2>$null + git reset --hard --quiet origin/main + Write-Verbose "Repository `"$OrgRepo`" refreshed successfully." + Write-Output 2 + } else { + Write-Warning "Another `"$repo`" repository exists not matching remote." + Write-Output 0 + } + Pop-Location + } catch { + # determine GitHub protocol used (https/ssl) + $gitProtocol = $(Invoke-Command $getOrigin) -replace '(^.+github\.com[:/]).*', '$1' + # clone target repository + git clone "${gitProtocol}$org/$repo" "$destPath" --quiet + # determine state of cloning the repository + $status = if ($?) { + Write-Verbose "Repository `"$OrgRepo`" cloned successfully." + Write-Output 1 + } else { + Write-Warning "Cloning of the `"$OrgRepo`" repository failed." + Write-Output 0 + } + } + } + + end { + return $status + } +} diff --git a/modules/InstallUtils/InstallUtils.psd1 b/modules/InstallUtils/InstallUtils.psd1 new file mode 100644 index 00000000..1a5b4a69 --- /dev/null +++ b/modules/InstallUtils/InstallUtils.psd1 @@ -0,0 +1,138 @@ +# +# Module manifest for module 'InstallUtils' +# +# Generated by: szymo +# +# Generated on: 2023-10-01 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'InstallUtils.psm1' + + # Version number of this module. + ModuleVersion = '0.1.0' + + # Supported PSEditions + CompatiblePSEditions = @('Core', 'Desk') + + # ID used to uniquely identify this module + GUID = '8c07c9b1-8cc3-4159-a2be-20d0dfb051f4' + + # Author of this module + Author = 'Szymon Osiecki' + + # Company or vendor of this module + # CompanyName = 'Unknown' + + # Copyright statement for this module + Copyright = '(c) Szymon Osiecki. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'Module providing generic utility functions for scripting operations.' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + # common + 'Invoke-CommandRetry' + 'Test-IsAdmin' + 'Update-SessionEnvironmentPath' + # git + 'Invoke-GhRepoClone' + ) + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = 'beta' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} diff --git a/modules/InstallUtils/InstallUtils.psm1 b/modules/InstallUtils/InstallUtils.psm1 new file mode 100644 index 00000000..a30da0b1 --- /dev/null +++ b/modules/InstallUtils/InstallUtils.psm1 @@ -0,0 +1,19 @@ +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot/Functions/common.ps1 +. $PSScriptRoot/Functions/git.ps1 + +$exportModuleMemberParams = @{ + Function = @( + # common + 'Invoke-CommandRetry' + 'Test-IsAdmin' + 'Update-SessionEnvironmentPath' + # git + 'Invoke-GhRepoClone' + ) + Variable = @() + Alias = @() +} + +Export-ModuleMember @exportModuleMemberParams diff --git a/modules/SetupUtils/Functions/certs.ps1 b/modules/SetupUtils/Functions/certs.ps1 new file mode 100644 index 00000000..905be7e4 --- /dev/null +++ b/modules/SetupUtils/Functions/certs.ps1 @@ -0,0 +1,91 @@ +function ConvertTo-PEM { + [CmdletBinding()] + [OutputType([System.Collections.Generic.List[string]])] + param ( + [Parameter(Mandatory, Position = 0, ValueFromPipeline)] + [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, + + [switch]$AddHeader + ) + + begin { + # instantiate list for storing PEM encoded certificates + $pems = [System.Collections.Generic.List[string]]::new() + } + + process { + # convert certificate to base64 + $base64 = [System.Convert]::ToBase64String($Certificate.RawData) + # build PEM encoded X.509 certificate + $builder = [System.Text.StringBuilder]::new() + if ($AddHeader) { + $builder.AppendLine("# Issuer: $($Certificate.Issuer)") | Out-Null + $builder.AppendLine("# Subject: $($Certificate.Subject)") | Out-Null + $builder.AppendLine("# Label: $([regex]::Match($Certificate.Subject, '(?<=CN=)(.)+?(?=,|$)').Value)") | Out-Null + $builder.AppendLine("# Serial: $($Certificate.SerialNumber)") | Out-Null + $builder.AppendLine("# SHA1 Fingerprint: $($Certificate.Thumbprint)") | Out-Null + } + $builder.AppendLine('-----BEGIN CERTIFICATE-----') | Out-Null + for ($i = 0; $i -lt $base64.Length; $i += 64) { + $length = [System.Math]::Min(64, $base64.Length - $i) + $builder.AppendLine($base64.Substring($i, $length)) | Out-Null + } + $builder.AppendLine('-----END CERTIFICATE-----') | Out-Null + # create object with parsed common name and PEM encoded certificate + $pems.Add($builder.ToString().Replace("`r`n", "`n")) + } + + end { + return $pems + } +} + +function Get-Certificate { + [CmdletBinding()] + [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2[]])] + param ( + [Parameter(Mandatory, Position = 0)] + [string]$Uri, + + [switch]$BuildChain, + + [switch]$IgnoreValidation + ) + + begin { + $tcpClient = [System.Net.Sockets.TcpClient]::new($Uri, 443) + if ($BuildChain) { + $chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new() + } + if ($IgnoreValidation) { + $sslStream = [System.Net.Security.SslStream]::new($tcpClient.GetStream(), $false, { $true }) + if ($BuildChain) { + $chain.ChainPolicy.VerificationFlags = [System.Security.Cryptography.X509Certificates.X509VerificationFlags]::AllFlags + } + } else { + $sslStream = [System.Net.Security.SslStream]::new($tcpClient.GetStream()) + } + } + + process { + try { + $sslStream.AuthenticateAsClient($Uri) + $certificate = $sslStream.RemoteCertificate + } finally { + $sslStream.Close() + } + + if ($BuildChain) { + $isChainValid = $chain.Build($certificate) + if ($isChainValid) { + $certificate = $chain.ChainElements.Certificate + } else { + Write-Warning 'SSL certificate chain validation failed.' + } + } + } + + end { + return $certificate + } +} diff --git a/modules/SetupUtils/Functions/common.ps1 b/modules/SetupUtils/Functions/common.ps1 new file mode 100644 index 00000000..b733214b --- /dev/null +++ b/modules/SetupUtils/Functions/common.ps1 @@ -0,0 +1,205 @@ +function ConvertFrom-Cfg { + [CmdletBinding()] + [OutputType([System.Collections.Specialized.OrderedDictionary])] + param ( + [Parameter(Mandatory, Position = 0, ParameterSetName = 'FromFile')] + [ValidateScript({ Test-Path $_ -PathType Leaf }, ErrorMessage = "'{0}' is not a valid file path.")] + [string]$Path, + + [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromPipeline')] + [object]$InputObject + ) + + begin { + # instantiate generic list for the configuration content + $cfgList = [System.Collections.Generic.List[string]]::new() + } + + process { + switch ($PsCmdlet.ParameterSetName) { + FromFile { + $content = [System.IO.File]::ReadAllLines((Resolve-Path $Path)) + $content.ForEach({ $cfgList.Add( $_ ) }) + } + FromPipeline { + if ($InputObject) { + $cfgList.Add( $InputObject ) + } + } + } + } + + end { + # build an ordered dictionary from cfg object + $cfg = [ordered]@{} + switch -Regex ($cfgList) { + '^\s*\[(.+)\]' { + # Section + $section = $matches[1] + $cfg[$section] = [ordered]@{} + $CommentCount = 0 + } + '^\s*([#;].*)' { + # Comment + $value = $matches[1] + $CommentCount = $CommentCount + 1 + $name = 'Comment' + $CommentCount + $cfg[$section][$name] = $value + } + '^\s*(\w+)\s*=(.*)' { + # Key + $name, $value = $matches[1..2] + $cfg[$section][$name] = $value.Trim() + } + } + # return configuration dictionary + return $cfg + } +} + +function ConvertTo-Cfg { + [CmdletBinding()] + param ( + [Parameter(Mandatory, Position = 0, ValueFromPipeline)] + [System.Collections.Specialized.OrderedDictionary]$OrderedDict, + + [string]$Path, + + [switch]$LineFeed, + + [switch]$Force + ) + + $ErrorActionPreference = 'Stop' + + if (-not $Force -and (Test-Path $Path)) { + Write-Error "$Path destination already exists." + } + + $builder = [System.Text.StringBuilder]::new() + foreach ($enum in $OrderedDict.GetEnumerator()) { + $section = $enum.Key + $builder.AppendLine("`n[$section]") | Out-Null + foreach ($cfg in $enum.Value.GetEnumerator()) { + if ($cfg.Key -like 'Comment*') { + $builder.AppendLine($cfg.Value) | Out-Null + } else { + $builder.AppendLine([string]::Join(' = ', $cfg.Key, $cfg.Value)) | Out-Null + } + } + } + + $content = $builder.ToString().Trim() + if ($LineFeed) { + $content = $content.Replace("`r`n", "`n") + } + if ($Path) { + Set-Content -Value $content -Path $Path + } else { + $content + } +} + +function Invoke-ExampleScriptSave { + [CmdletBinding()] + param ( + [Parameter(Position = 0)] + [ValidateScript({ Test-Path $_ }, ErrorMessage = "'{0}' is not a valid path.")] + [string]$Path = '.', + + [ValidateScript({ $_.ForEach({ $_ -in @('.ps1', '.py', '.sh') }) -notcontains $false }, + ErrorMessage = 'Wrong extensions provided. Valid values: .ps1, .py, .sh')] + [string[]]$ExtensionFilter = @('.ps1', '.py', '.sh'), + + [ValidateNotNullOrEmpty()] + [string[]]$Exclude = '.', + + [switch]$FolderFromBase + ) + + begin { + # get list of scripts in the specified directory + $scripts = Get-ChildItem $Path -File -Force | Where-Object { + $_.Extension -in $ExtensionFilter -and $_.FullName -notin (Resolve-Path $Exclude -ErrorAction SilentlyContinue).Path + } + # instantiate generic list to store example script(s) name(s) + $lst = [Collections.Generic.List[string]]::new() + if ($scripts) { + # get git root + $gitRoot = git rev-parse --show-toplevel + # add the console folder to .gitignored if necessary + if (-not (Select-String '\bconsole\b' "$gitRoot/.gitignore" -Quiet)) { + [IO.File]::AppendAllLines("$gitRoot/.gitignore", [string[]]'/console/') + } + # determine and create example folder to put example scripts in + $exampleDir = if ($FolderFromBase) { + [IO.Path]::Combine($gitRoot, 'console', $scripts[0].Directory.Name) + } else { + [IO.Path]::Combine($gitRoot, 'console') + } + # create example dir if not exists + if (-not (Test-Path $exampleDir -PathType Container)) { + New-Item $exampleDir -ItemType Directory | Out-Null + } + # script's initial comment regex pattern + $pattern = @{ + '.ps1' = '(?s)(?<=\n\.EXAMPLE\n).*?(?=(\n#>|\n\.[A-Z]))' + '.sh' = "(?s)(?<=\n: '\n).*?(?=\n'\n)" + '.py' = '(?<=^(#.*?\n)?"""\n)((?!""")[\s\S])*(?=\n""")' + } + } else { + return + } + } + + process { + foreach ($script in $scripts) { + $content = [IO.File]::ReadAllText($script) + # get script examples + $example = [regex]::Matches($content, $pattern[$script.Extension]).Value + if ($example) { + # get PowerShell parameters descriptions + if ($script.Extension -eq '.ps1') { + $synopsis = [regex]::Matches($content, '(?s)(?<=\n)\.SYNOPSIS\n(.*?)(?=(\n\.[A-Z]+( \w+)?\n|#>))').Value + $param = [regex]::Matches($content, '(?s)(?<=\n)\.PARAMETER \w+\n(.*?)(?=(\n\.[A-Z]+\n|#>))').Value + } else { + # quote sentences and links + $example = $example ` + -replace '(^|\n)([A-Z].*\.)(\n|$)', '$1# $2$3' ` + -replace '(^|\n)(http.*)(\n|$)', '$1# $2$3' + # clean param variable + $synopsis = $null + $param = $null + } + # calculate example file path + $fileName = $script.Extension -eq '.py' ? "$($script.BaseName)_py.ps1" : $script.Name + $exampleFile = [IO.Path]::Combine($exampleDir, $fileName) + # build content string + $builder = [System.Text.StringBuilder]::new() + if ($synopsis -or $param) { + $builder.AppendLine('<#') | Out-Null + if ($synopsis) { $builder.AppendLine($synopsis.Trim()) | Out-Null } + if ($param) { $builder.AppendLine($synopsis ? "`n$($param.Trim())" : $param.Trim()) | Out-Null } + $builder.AppendLine('#>') | Out-Null + if ($example) { $builder.AppendLine('') | Out-Null } + } + if ($example) { + $example.Trim().Split("`n") | Select-String -NotMatch 'ExampleScriptSave' | ForEach-Object { + $builder.AppendLine($_) | Out-Null + } + } + # save the example script + [IO.File]::WriteAllText($exampleFile, $builder.ToString()) + # add example script path to the list + $lst.Add([IO.Path]::GetRelativePath($gitRoot, $exampleFile)) + } + } + } + + end { + # print list of saved file paths + foreach ($example in $lst) { + Write-Host $example + } + } +} diff --git a/modules/SetupUtils/SetupUtils.psd1 b/modules/SetupUtils/SetupUtils.psd1 new file mode 100644 index 00000000..eb718792 --- /dev/null +++ b/modules/SetupUtils/SetupUtils.psd1 @@ -0,0 +1,139 @@ +# +# Module manifest for module 'SetupUtils' +# +# Generated by: szymo +# +# Generated on: 2023-10-01 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'SetupUtils.psm1' + + # Version number of this module. + ModuleVersion = '0.1.0' + + # Supported PSEditions + CompatiblePSEditions = @('Core') + + # ID used to uniquely identify this module + GUID = '18cfc3bf-fdbd-4343-a1a5-241df6265e70' + + # Author of this module + Author = 'Szymon Osiecki' + + # Company or vendor of this module + # CompanyName = 'Unknown' + + # Copyright statement for this module + Copyright = '(c) Szymon Osiecki. All rights reserved.' + + # Description of the functionality provided by this module + # Description = '' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '7.0' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + # certs + 'ConvertTo-PEM' + 'Get-Certificate' + # common + 'ConvertFrom-Cfg' + 'ConvertTo-Cfg' + 'Invoke-ExampleScriptSave' + ) + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = 'beta' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} diff --git a/modules/SetupUtils/SetupUtils.psm1 b/modules/SetupUtils/SetupUtils.psm1 new file mode 100644 index 00000000..485e35ac --- /dev/null +++ b/modules/SetupUtils/SetupUtils.psm1 @@ -0,0 +1,20 @@ +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot/Functions/certs.ps1 +. $PSScriptRoot/Functions/common.ps1 + +$exportModuleMemberParams = @{ + Function = @( + # certs + 'ConvertTo-PEM' + 'Get-Certificate' + # common + 'ConvertFrom-Cfg' + 'ConvertTo-Cfg' + 'Invoke-ExampleScriptSave' + ) + Variable = @() + Alias = @() +} + +Export-ModuleMember @exportModuleMemberParams diff --git a/scripts_egsave.ps1 b/scripts_egsave.ps1 index e85457ae..242c7493 100644 --- a/scripts_egsave.ps1 +++ b/scripts_egsave.ps1 @@ -17,12 +17,7 @@ begin { try { Get-Command Invoke-ExampleScriptSave -CommandType Function | Out-Null } catch { - # clone/refresh szymonos/ps-modules repository - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') { - Import-Module -Name (Resolve-Path '../ps-modules/modules/do-common') - } else { - Write-Error 'Cloning ps-modules repository failed.' - } + Import-Module (Resolve-Path './modules/SetupUtils') } } diff --git a/wsl/pwsh_setup.ps1 b/wsl/pwsh_setup.ps1 index 830213dd..e8b91077 100644 --- a/wsl/pwsh_setup.ps1 +++ b/wsl/pwsh_setup.ps1 @@ -13,9 +13,11 @@ begin { # set location to workspace folder Push-Location "$PSScriptRoot/.." + # import InstallUtils for the Invoke-GhRepoClone function + Import-Module (Resolve-Path './modules/InstallUtils') # clone/refresh szymonos/powershell-scripts repository - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/powershell-scripts') { + if (Invoke-GhRepoClone -OrgRepo 'szymonos/powershell-scripts' -Path '..') { Set-Location ../powershell-scripts } else { Write-Error 'Cloning ps-modules repository failed.' diff --git a/wsl/wsl_certs_add.ps1 b/wsl/wsl_certs_add.ps1 index a327079a..8f64f2dd 100644 --- a/wsl/wsl_certs_add.ps1 +++ b/wsl/wsl_certs_add.ps1 @@ -47,16 +47,12 @@ begin { # set location to workspace folder Push-Location "$PSScriptRoot/.." - # clone/refresh szymonos/ps-modules repository + # check if the required functions are available, otherwise import SetupUtils module try { - Import-Module do-common -MinimumVersion 0.27 + Get-Command Get-Certificate -CommandType Function | Out-Null + Get-Command ConvertTo-PEM -CommandType Function | Out-Null } catch { - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') { - # import the do-common module for certificate functions - Import-Module -Name (Resolve-Path '../ps-modules/modules/do-common') - } else { - Write-Error 'Cloning ps-modules repository failed.' - } + Import-Module (Resolve-Path './modules/SetupUtils') } # determine update ca parameters depending on distro @@ -95,25 +91,20 @@ process { Get-Certificate -Uri $Uri -BuildChain | Select-Object -Skip 1 } # check if root certificate from chain is in the cert store - $rootCrts = Get-RootCertificates - if ($chain[-1].Thumbprint -in $rootCrts.Thumbprint) { - Write-Host "`e[1mIntercepted certificates from TLS chain`e[0m" - for ($i = $chain.Count - 1; $i -ge 0; $i--) { - $cert = $chain[$i] - - # calculate certificate file name - $crtFile = "$($cert.Thumbprint).crt" - Write-Host "`e[32m$crtFile :`e[0m $([regex]::Match($cert.Subject, '(?<=CN=)(.)+?(?=,|$)').Value)" - $pem = $cert | ConvertTo-PEM -AddHeader - [IO.File]::WriteAllText([IO.Path]::Combine($tmpFolder, $crtFile), $pem) - } + Write-Host "`e[1mIntercepted certificates from TLS chain`e[0m" + for ($i = $chain.Count - 1; $i -ge 0; $i--) { + $cert = $chain[$i] - # copy certificates to the distro specific cert directory and install them - $cmd = "mkdir -p $($crt.path) && install -m 0644 ${tmpName}/*.crt $($crt.path) && $($crt.cmd)" - wsl -d $Distro -u root --exec bash -c $cmd - } else { - Write-Error "Root certificate from TLS chain is not trusted ($($chain[-1].Subject))." + # calculate certificate file name + $crtFile = "$($cert.Thumbprint).crt" + Write-Host "`e[32m$crtFile :`e[0m $([regex]::Match($cert.Subject, '(?<=CN=)(.)+?(?=,|$)').Value)" + $pem = $cert | ConvertTo-PEM -AddHeader + [IO.File]::WriteAllText([IO.Path]::Combine($tmpFolder, $crtFile), $pem) } + + # copy certificates to the distro specific cert directory and install them + $cmd = "mkdir -p $($crt.path) && install -m 0644 ${tmpName}/*.crt $($crt.path) && $($crt.cmd)" + wsl -d $Distro -u root --exec bash -c $cmd } end { diff --git a/wsl/wsl_install.ps1 b/wsl/wsl_install.ps1 new file mode 100644 index 00000000..ebcc7787 --- /dev/null +++ b/wsl/wsl_install.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Script synopsis. +.EXAMPLE +# :perform basic Ubuntu WSL setup +wsl/wsl_install.ps1 -Distro 'Ubuntu' +# :fix network in the Ubuntu WSL distro +wsl/wsl_install.ps1 -Distro 'Ubuntu' -FixNetwork +# :set up WSL distro with specified installation scopes +$Scope = @('python') +$Scope = @('az', 'docker') +wsl/wsl_install.ps1 -Distro 'Ubuntu' -s $Scope +# :set up WSL distro and clone specified GitHub repositories +$Repos = @('szymonos/linux-setup-scripts') +wsl/wsl_install.ps1 -Distro 'Ubuntu' -s $Scope -r $Repos +#> +[CmdletBinding(DefaultParameterSetName = 'Setup')] +param ( + [Parameter(Mandatory, Position = 0)] + [string]$Distro, + + [Parameter(ParameterSetName = 'Setup')] + [Parameter(ParameterSetName = 'GitHub')] + [ValidateScript({ $_.ForEach({ $_ -in @('az', 'docker', 'python') }) -notcontains $false })] + [string[]]$Scope, + + [Parameter(Mandatory, ParameterSetName = 'GitHub')] + [ValidateScript({ $_.ForEach({ $_ -match '^[\w-]+/[\w-]+$' }) -notcontains $false })] + [string[]]$Repos, + + [Parameter(ParameterSetName = 'Setup')] + [Parameter(ParameterSetName = 'GitHub')] + [switch]$FixNetwork +) + +begin { + $ErrorActionPreference = 'Stop' + + # set location to workspace folder + Push-Location "$PSScriptRoot/.." + # import InstallUtils for the Update-SessionEnvironmentPath function + Import-Module (Resolve-Path './modules/InstallUtils') + # update environment paths + Update-SessionEnvironmentPath +} + +process { + # *Install PowerShell + try { + Get-Command pwsh.exe -CommandType Application | Out-Null + } catch { + $scriptPath = Resolve-Path wsl/pwsh_setup.ps1 + if (Test-IsAdmin) { + & $scriptPath + # update environment paths + Update-SessionEnvironmentPath + } else { + Start-Process powershell.exe "-NoProfile -File `"$scriptPath`"" -Verb RunAs + Write-Host "`nInstalling PowerShell Core. Complete the installation and run the script again!`n" -ForegroundColor Yellow + exit 0 + } + } + + # *Set up WSL + $cmd = "wsl/wsl_setup.ps1 -Distro '$Distro'" + if ($Scope) { $cmd += " -Scope @($($Scope.ForEach({ "'$_'" }) -join ','),'shell')" } + if ($Repos) { $cmd += " -Repos @($($Repos.ForEach({ "'$_'" }) -join ','))" } + if ($FixNetwork) { $cmd += ' -FixNetwork' } + $cmd += '-OmpTheme base -AddCertificate' + pwsh.exe -NoProfile -Command $cmd +} + +end { + Pop-Location +} diff --git a/wsl/wsl_network_fix.ps1 b/wsl/wsl_network_fix.ps1 index 61a59985..53cfd83b 100644 --- a/wsl/wsl_network_fix.ps1 +++ b/wsl/wsl_network_fix.ps1 @@ -46,6 +46,13 @@ begin { # set location to workspace folder Push-Location "$PSScriptRoot/.." + # check if the required functions are available, otherwise import SetupUtils module + try { + Get-Command ConvertFrom-Cfg -CommandType Function | Out-Null + Get-Command ConvertTo-Cfg -CommandType Function | Out-Null + } catch { + Import-Module (Resolve-Path './modules/SetupUtils') + } # check if distro exist $distros = wsl/wsl_distro_get.ps1 -FromRegistry @@ -54,18 +61,6 @@ begin { exit 1 } - # clone/refresh szymonos/ps-modules repository - try { - Import-Module do-common -MinimumVersion 0.28.2 - } catch { - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') { - # import the do-common module for certificate functions - Import-Module -Name (Resolve-Path '../ps-modules/modules/do-common') - } else { - Write-Error 'Cloning ps-modules repository failed.' - } - } - # determine if resolv.conf should be automatically generated if ($Revert) { $genResolv = 'true' diff --git a/wsl/wsl_setup.ps1 b/wsl/wsl_setup.ps1 index f8bee6d7..16d1a701 100644 --- a/wsl/wsl_setup.ps1 +++ b/wsl/wsl_setup.ps1 @@ -114,6 +114,8 @@ begin { # set location to workspace folder Push-Location "$PSScriptRoot/.." + # import InstallUtils for the Invoke-GhRepoClone function + Import-Module (Resolve-Path './modules/InstallUtils') # check if repository is up to date git fetch @@ -141,13 +143,13 @@ begin { Invoke-Expression $cmd $lxss = wsl/wsl_distro_get.ps1 -FromRegistry | Where-Object Name -EQ $Distro } catch { - if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) { + if (Test-IsAdmin) { Invoke-Expression $cmd Write-Host 'WSL service installation finished.' - Write-Host "`nRestart the system and run the script again to install the specified WSL distro!" -ForegroundColor Yellow + Write-Host "`nRestart the system and run the script again to install the specified WSL distro!`n" -ForegroundColor Yellow } else { Start-Process pwsh.exe "-NoProfile -Command `"$cmd`"" -Verb RunAs - Write-Host "`nWSL service installing. Wait for the process to finish and restart the system!" -ForegroundColor Yellow + Write-Host "`nWSL service installing. Wait for the process to finish and restart the system!`n" -ForegroundColor Yellow } exit 0 } @@ -184,10 +186,10 @@ process { '[ -f /usr/bin/kubectl ] && k8s_base="true" || k8s_base="false";', '[ -f /usr/local/bin/k3d ] && k8s_ext="true" || k8s_ext="false";', '[ -f /usr/bin/oh-my-posh ] && omp="true" || omp="false";', - '[ -d ~/.local/share/powershell/Modules/Az ] && az="true" || az="false";', - '[ -d /mnt/wslg ] && wslg="true" || wslg="false";', + '[ -d "$HOME/.local/share/powershell/Modules/Az" ] && az="true" || az="false";', '[ -d "$HOME/miniconda3" ] && python="true" || python="false";', '[ -f "$HOME/.ssh/id_ed25519" ] && ssh_key="true" || ssh_key="false";', + '[ -d /mnt/wslg ] && wslg="true" || wslg="false";', 'git_user_name="$(git config --global --get user.name 2>/dev/null)";', '[ -n "$git_user_name" ] && git_user="true" || git_user="false";', 'git_user_email="$(git config --global --get user.email 2>/dev/null)";', @@ -203,16 +205,20 @@ process { # instantiate scope generic sorted set $scopes = [System.Collections.Generic.SortedSet[string]]::new() $Scope.ForEach({ $scopes.Add($_) | Out-Null }) - # *determine scope if not provided - if ($scopes.Count -eq 0) { - switch ($chk) { - { $_.az } { @('az', 'python').ForEach({ $scopes.Add($_) | Out-Null }) } - { $_.k8s_base } { $scopes.Add('k8s_base') | Out-Null } - { $_.k8s_ext } { @('docker', 'k8s_base', 'k8s_ext').ForEach({ $scopes.Add($_) | Out-Null }) } - { $_.pwsh } { @('pwsh', 'shell').ForEach({ $scopes.Add($_) | Out-Null }) } - { $_.python } { $scopes.Add('python') | Out-Null } - { $_.shell } { $scopes.Add('shell') | Out-Null } - } + # *determine additional scopes from distro check + switch ($chk) { + { $_.az } { $scopes.Add('az') | Out-Null } + { $_.k8s_base } { $scopes.Add('k8s_base') | Out-Null } + { $_.k8s_ext } { $scopes.Add('k8s_ext') | Out-Null } + { $_.pwsh } { $scopes.Add('pwsh') | Out-Null } + { $_.python } { $scopes.Add('python') | Out-Null } + { $_.shell } { $scopes.Add('shell') | Out-Null } + } + # add corresponding scopes + switch (@($scopes)) { + az { $scopes.Add('python') | Out-Null } + k8s_ext { @('docker', 'k8s_base').ForEach({ $scopes.Add($_) | Out-Null }) } + pwsh { $scopes.Add('shell') | Out-Null } } # determine 'oh_my_posh' scope if ($chk.omp -or $OmpTheme) { @@ -226,7 +232,7 @@ process { } # display distro name and installed scopes $follow = $Distro -eq $lxss[0].Name ? '' : "`n" - Write-Host "$follow`e[95;1m${Distro}$($scopes ? " :`e[0m $($scopes -join ', ')" : '')" + Write-Host "$follow`e[95;1m${Distro}$($scopes.Count ? " :`e[0;90m $($scopes -join ', ')`e[0m" : "`e[0m")" # *fix WSL networking if ($FixNetwork) { Write-Host 'fixing network...' -ForegroundColor Cyan @@ -309,13 +315,14 @@ process { } # *install PowerShell modules from ps-modules repository # clone/refresh szymonos/ps-modules repository - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') { - Write-Verbose 'ps-modules repository cloned/refreshed successfully.' + $repoClone = Invoke-GhRepoClone -OrgRepo 'szymonos/ps-modules' -Path '..' + if ($repoClone) { + Write-Verbose "Repository `"ps-modules`" $($repoClone -eq 1 ? 'cloned': 'refreshed') successfully." } else { Write-Error 'Cloning ps-modules repository failed.' } Write-Host 'installing ps-modules...' -ForegroundColor Cyan - Write-Host "`e[32mAllUsers :`e[0m do-common" + Write-Host "`e[32mAllUsers :`e[0;90m do-common`e[0m" wsl.exe --distribution $Distro --user root --exec ../ps-modules/module_manage.ps1 'do-common' -CleanUp # instantiate psmodules generic lists $modules = [System.Collections.Generic.SortedSet[String]]::new([string[]]@('aliases-git', 'do-linux')) @@ -328,7 +335,7 @@ process { $modules.Add('aliases-kubectl') | Out-Null Write-Verbose "Added `e[3maliases-kubectl`e[23m to be installed from ps-modules." } - Write-Host "`e[32mCurrentUser :`e[0m $($modules -join ', ')" + Write-Host "`e[32mCurrentUser :`e[0;90m $($modules -join ', ')`e[0m" $cmd = "@($($modules | Join-String -SingleQuote -Separator ',')) | ../ps-modules/module_manage.ps1 -CleanUp" wsl.exe --distribution $Distro --exec pwsh -nop -c $cmd continue @@ -364,6 +371,18 @@ process { continue } } + # *set gtk theme for wslg + if ($lx.Version -eq 2 -and $chk.wslg) { + $GTK_THEME = if ($GtkTheme -eq 'light') { + $chk.gtkd ? '"Adwaita"' : $null + } else { + $chk.gtkd ? $null : '"Adwaita:dark"' + } + if ($GTK_THEME) { + Write-Host "setting `e[3m$GtkTheme`e[23m gtk theme..." -ForegroundColor Cyan + wsl.exe --distribution $Distro --user root -- bash -c "echo 'export GTK_THEME=$GTK_THEME' >/etc/profile.d/gtk_theme.sh" + } + } # *setup git config $builder = [System.Text.StringBuilder]::new() # set up git author identity @@ -438,18 +457,6 @@ process { $cmd = "mkdir -p `"`$HOME/.ssh`" && install -m 0400 /mnt/c/Users/$env:USERNAME/.ssh/id_ed25519* `"`$HOME/.ssh`"" wsl.exe --distribution $Distro --exec bash -c $cmd } - # *set gtk theme for wslg - if ($lx.Version -eq 2 -and $chk.wslg) { - $GTK_THEME = if ($GtkTheme -eq 'light') { - $chk.gtkd ? '"Adwaita"' : $null - } else { - $chk.gtkd ? $null : '"Adwaita:dark"' - } - if ($GTK_THEME) { - Write-Host "setting `e[3m$GtkTheme`e[23m gtk theme..." -ForegroundColor Cyan - wsl.exe --distribution $Distro --user root -- bash -c "echo 'export GTK_THEME=$GTK_THEME' >/etc/profile.d/gtk_theme.sh" - } - } } if ($PsCmdlet.ParameterSetName -eq 'GitHub') { @@ -457,7 +464,7 @@ process { wsl.exe --distribution $Distro --user root --exec .assets/provision/install_gh.sh # *clone GitHub repositories Write-Host 'cloning GitHub repositories...' -ForegroundColor Cyan - wsl.exe --distribution $Distro --exec .assets/provision/setup_gh_repos.sh --repos "$Repos" --user $env:USERNAME + wsl.exe --distribution $Distro --exec .assets/provision/setup_gh_repos.sh --repos "$Repos" } } diff --git a/wsl/wsl_systemd.ps1 b/wsl/wsl_systemd.ps1 index ea83ebb4..0a2f11a1 100644 --- a/wsl/wsl_systemd.ps1 +++ b/wsl/wsl_systemd.ps1 @@ -37,6 +37,13 @@ begin { # set location to workspace folder Push-Location "$PSScriptRoot/.." + # check if the required functions are available, otherwise import SetupUtils module + try { + Get-Command ConvertFrom-Cfg -CommandType Function | Out-Null + Get-Command ConvertTo-Cfg -CommandType Function | Out-Null + } catch { + Import-Module (Resolve-Path './modules/SetupUtils') + } # check if distro exist $distros = wsl/wsl_distro_get.ps1 -FromRegistry @@ -44,18 +51,6 @@ begin { Write-Warning "The specified distro does not exist ($Distro)." exit 1 } - - # clone/refresh szymonos/ps-modules repository - try { - Import-Module do-common -MinimumVersion 0.28.2 - } catch { - if (.assets/tools/gh_repo_clone.ps1 -OrgRepo 'szymonos/ps-modules') { - # import the do-common module for certificate functions - Import-Module -Name (Resolve-Path '../ps-modules/modules/do-common') - } else { - Write-Error 'Cloning ps-modules repository failed.' - } - } } process {