From f0009101bbcaba4f56fb57476e447c7bc1d5d1e0 Mon Sep 17 00:00:00 2001 From: Matheus Castello Date: Mon, 25 Mar 2024 11:43:37 -0300 Subject: [PATCH] Initial Commit Signed-off-by: Matheus Castello --- .conf/.container | 1 + .conf/.depok | 0 .conf/.docok | 0 .conf/.template | 1 + .conf/checkCIEnv.ps1 | 70 + .conf/checkDeps.ps1 | 87 + .conf/createDockerComposeProduction.ps1 | 214 ++ .conf/deps.json | 7 + .conf/id_rsa | 27 + .conf/id_rsa.pub | 1 + .conf/projectUpdater.ps1 | 357 ++++ .conf/runContainerIfNotExists.ps1 | 58 + .conf/shareWSLPorts.ps1 | 63 + .conf/tcb-env-setup.sh | 308 +++ .conf/torizonIO.ps1 | 248 +++ .conf/torizonPackages.ps1 | 130 ++ .conf/update.json | 10 + .conf/validateDepsRunning.ps1 | 71 + .doc/README.md | 14 + .dockerignore | 1 + .github/workflows/build-application.yaml | 37 + .gitignore | 5 + .gitlab-ci.yml | 115 ++ .vscode/extensions.json | 5 + .vscode/launch.json | 111 ++ .vscode/settings.json | 19 + .vscode/tasks.json | 2259 ++++++++++++++++++++++ .vscode/tasks.ps1 | 696 +++++++ Dockerfile | 110 ++ Dockerfile.debug | 134 ++ Program.cs | 19 + Utils/Shell.cs | 39 + VERSION | 1 + docker-compose.yml | 87 + torizonPackages.json | 6 + torizonWSLWelcome.csproj | 29 + torizonWSLWelcome.sln | 25 + ui/AppWindow.slint | 202 ++ ui/assets/torizonLoves.png | Bin 0 -> 17126 bytes 39 files changed, 5567 insertions(+) create mode 100644 .conf/.container create mode 100644 .conf/.depok create mode 100644 .conf/.docok create mode 100644 .conf/.template create mode 100644 .conf/checkCIEnv.ps1 create mode 100644 .conf/checkDeps.ps1 create mode 100644 .conf/createDockerComposeProduction.ps1 create mode 100644 .conf/deps.json create mode 100644 .conf/id_rsa create mode 100644 .conf/id_rsa.pub create mode 100644 .conf/projectUpdater.ps1 create mode 100644 .conf/runContainerIfNotExists.ps1 create mode 100644 .conf/shareWSLPorts.ps1 create mode 100755 .conf/tcb-env-setup.sh create mode 100644 .conf/torizonIO.ps1 create mode 100644 .conf/torizonPackages.ps1 create mode 100644 .conf/update.json create mode 100644 .conf/validateDepsRunning.ps1 create mode 100644 .doc/README.md create mode 100644 .dockerignore create mode 100644 .github/workflows/build-application.yaml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 .vscode/tasks.ps1 create mode 100644 Dockerfile create mode 100644 Dockerfile.debug create mode 100644 Program.cs create mode 100644 Utils/Shell.cs create mode 100644 VERSION create mode 100644 docker-compose.yml create mode 100644 torizonPackages.json create mode 100644 torizonWSLWelcome.csproj create mode 100644 torizonWSLWelcome.sln create mode 100644 ui/AppWindow.slint create mode 100644 ui/assets/torizonLoves.png diff --git a/.conf/.container b/.conf/.container new file mode 100644 index 0000000..477403e --- /dev/null +++ b/.conf/.container @@ -0,0 +1 @@ +torizon-wsl-welcome diff --git a/.conf/.depok b/.conf/.depok new file mode 100644 index 0000000..e69de29 diff --git a/.conf/.docok b/.conf/.docok new file mode 100644 index 0000000..e69de29 diff --git a/.conf/.template b/.conf/.template new file mode 100644 index 0000000..3a1e101 --- /dev/null +++ b/.conf/.template @@ -0,0 +1 @@ +dotnetSlint diff --git a/.conf/checkCIEnv.ps1 b/.conf/checkCIEnv.ps1 new file mode 100644 index 0000000..eb83357 --- /dev/null +++ b/.conf/checkCIEnv.ps1 @@ -0,0 +1,70 @@ +# suppress warnings that we need to use +param() + +$_envVarsSettings = @( + "DOCKER_REGISTRY", + "DOCKER_LOGIN", + "DOCKER_TAG", + "TCB_CLIENTID", + "TCB_CLIENTSECRET", + "TCB_PACKAGE", + "TCB_FLEET", + "TORIZON_ARCH" +) + +$_envVarsSecrets = @( + # secrets + "DOCKER_PSSWD", + "PLATFORM_CLIENT_ID", + "PLATFORM_CLIENT_SECRET", + "PLATFORM_CREDENTIALS" +) + +function _gotoError { + Write-Host -ForegroundColor DarkYellow ` + "`n⚠️ THESE ENV VARIABLES NEED TO BE SET IN YOUR CI/CD ENVIRONMENT`n" + exit 69 +} + +$_missingEnvVarSettings = $false +$_missingEnvVarSecrets = $false + +# check if we are running in a GitLab CI or GitHub Actions environment +if ( + ($Env:GITLAB_CI -eq $true) -or + ($Env:CI -eq $true) +) { + # validate the environment variables + foreach ($var in $_envVarsSettings) { + if ((Test-Path "Env:$var") -eq $false) { + Write-Host -ForegroundColor DarkRed ` + "❌ $var is not set" + $_missingEnvVarSettings = $true + } + } + + if ($_missingEnvVarSettings) { + Write-Host -ForegroundColor DarkRed ` + "Missing settings.json properties, aborting`n" + } + + foreach ($var in $_envVarsSecrets) { + if ((Test-Path "Env:$var") -eq $false) { + Write-Host -ForegroundColor DarkRed ` + "❌ $var is not set" + $_missingEnvVarSecrets = $true + } + } + + if ($_missingEnvVarSecrets) { + Write-Host -ForegroundColor DarkRed ` + "Missing protected environment variables, aborting`n" + } + + if ( + $_missingEnvVarSettings -or + $_missingEnvVarSecrets + ) { + _gotoError + } +} diff --git a/.conf/checkDeps.ps1 b/.conf/checkDeps.ps1 new file mode 100644 index 0000000..a5e5585 --- /dev/null +++ b/.conf/checkDeps.ps1 @@ -0,0 +1,87 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidOverwritingBuiltInCmdlets', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingInvokeExpression', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingPositionalParameters', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidGlobalVars', "" +)] +param() + +$env:DOCKER_HOST = "" + +if ($Env:GITLAB_CI -eq $true) { + Write-Host "ℹ️ :: GITLAB_CI :: ℹ️" + $Env:DOCKER_HOST = "tcp://docker:2375" +} + +# tested on Ubuntu 22.04 +$_packages = Get-Content .conf/deps.json | ConvertFrom-Json + +# docker and docker-compose are special cases +# TODO: check also for podman or other runtime +if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Host -ForegroundColor DarkRed "❌ you need docker installed" + exit 69 +} + +$_dockerComposeV = (docker compose version) + +if ($? -eq $false || [string]::IsNullOrEmpty($_dockerComposeV)) { + Write-Host -ForegroundColor DarkRed "❌ you need docker compose plugin installed" + exit 69 +} + +# ok docker and docker-compose exist so let's check the packages +$_packagesToInstall = New-Object Collections.Generic.List[string] + +Write-Host -ForegroundColor Yellow "Checking dependencies ..." + +foreach ($package in $_packages.packages) { + dpkg -s $package > /dev/null 2>&1 + + if ($? -eq $false) { + $_packagesToInstall.Add($package) + Write-Host -ForegroundColor DarkRed "😵 $package not installed" + } else { + Write-Host -ForegroundColor DarkGreen "👍 $package installed" + } +} + +# ask if the user want to install the packages that are not installed +if ($_packagesToInstall.Count -gt 0) { + $_installConfirm = Read-Host ` + "Do you want to try to install the dependencies? " + + if ($_installConfirm -eq 'y') { + # make sure to update the list first + sudo apt-get update + + foreach ($item in $_packagesToInstall) { + sudo apt-get install -y $item + + if ($? -eq $false) { + Write-Host -ForegroundColor DarkRed "❌ error trying to install package $item" + exit 69 + } + } + + Write-Host -ForegroundColor DarkGreen "✅ All packages installed successfully" + + # all packages installed then dep ok + New-Item -Path .conf/ -Name .depok -ItemType File 2>&1 | out-null + } +} else { + Write-Host -ForegroundColor DarkGreen "✅ All packages already installed" + + # we need to ran the check deps only if it's not ran yet + New-Item -Path .conf/ -Name .depok -ItemType File 2>&1 | out-null +} diff --git a/.conf/createDockerComposeProduction.ps1 b/.conf/createDockerComposeProduction.ps1 new file mode 100644 index 0000000..942760a --- /dev/null +++ b/.conf/createDockerComposeProduction.ps1 @@ -0,0 +1,214 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidOverwritingBuiltInCmdlets', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingInvokeExpression', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingPositionalParameters', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidGlobalVars', "" +)] +param() + +$ErrorActionPreference = "Stop" +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', "Internal PS variable" +)] +$PSNativeCommandUseErrorActionPreference = $true + +$env:DOCKER_HOST = "" + +if ($Env:GITLAB_CI -eq $true) { + Write-Host "ℹ️ :: GITLAB_CI :: ℹ️" + $Env:DOCKER_HOST = "tcp://docker:2375" +} + +# args are needed +$compoFilePath = $args[0] +$dockerLogin = $args[1] +$tag = $args[2] +$registry = $args[3] +$imageName = $args[4] + +# can be null +$gpu = $args[5] + +$_iterative = $true + +if ($null -eq $gpu) { + $gpu = "" +} else { + $env:GPU = $gpu +} + +if ($null -eq $env:DOCKER_PSSWD) { + throw "❌ DOCKER_PSSWD not set" +} else { + $psswd = $env:DOCKER_PSSWD +} + +if ($null -eq $env:TORIZON_ARCH) { + throw "❌ TORIZON_ARCH not set" +} else { + $imageArch = $env:TORIZON_ARCH +} + +if ($null -eq $env:APP_ROOT) { + throw "❌ APP_ROOT not set" +} else { + $appRoot = $env:APP_ROOT +} + +if ($env:TASKS_ITERATIVE -eq $False) { + $_iterative = $False +} + +if ([string]::IsNullOrEmpty($compoFilePath)) { + if ($_iterative) { + $compoFilePath = Read-Host "docker-compose.yml root file path" + } + + if ([string]::IsNullOrEmpty($compoFilePath)) { + throw "❌ docker-compose.yml root file path cannot be empty" + } +} + +if ([string]::IsNullOrEmpty($dockerLogin)) { + if ($_iterative) { + $dockerLogin = Read-Host "Image repository" + } + + if ([string]::IsNullOrEmpty($dockerLogin)) { + throw "❌ Docker image repository cannot be empty" + } +} + +if ([string]::IsNullOrEmpty($psswd)) { + if ($_iterative) { + $psswd = Read-Host -MaskInput "Docker registry password" + } + + if ([string]::IsNullOrEmpty($psswd)) { + throw "❌ Docker registry password cannot be empty" + } +} + +if ([string]::IsNullOrEmpty($imageName)) { + if ($_iterative) { + $imageName = Read-Host "Image name" + } + + if ([string]::IsNullOrEmpty($imageName)) { + throw "❌ Docker image name cannot be empty" + } +} + +if ([string]::IsNullOrEmpty($tag)) { + if ($_iterative) { + $tag = Read-Host "Image tag" + } + + if ([string]::IsNullOrEmpty($tag)) { + throw "❌ Docker image tag cannot be empty" + } +} + +$objSettings = Get-Content ("$compoFilePath/.vscode/settings.json") | ` + Out-String | ConvertFrom-Json +$localRegistry = $objSettings.host_ip + +$env:LOCAL_REGISTRY="$($localRegistry):5002" +$env:TAG="$tag" +if ([string]::IsNullOrEmpty($registry)) { + $env:DOCKER_LOGIN="$dockerLogin" +} else { + $env:DOCKER_LOGIN="$registry/$dockerLogin" +} +Set-Location $compoFilePath + +# rebuild and tag +Write-Host "Rebuilding $env:DOCKER_LOGIN/${imageName}:$tag ..." + +docker compose build ` + --build-arg APP_ROOT=$appRoot ` + --build-arg IMAGE_ARCH=$imageArch ` + --build-arg GPU=$gpu ` + $imageName + +Set-Location - + +Write-Host -ForegroundColor DarkGreen "✅ Image rebuild and tagged" + +# push it +Write-Host "Pushing it $env:DOCKER_LOGIN/${imageName}:$tag ..." + +Write-Output "$psswd" | docker login $registry -u $dockerLogin --password-stdin +docker push $env:DOCKER_LOGIN/${imageName}:$tag + +Write-Host -ForegroundColor DarkGreen "✅ Image push OK" + +# check if the yaml module is installed +Write-Host "Importing powershell-yaml ..." +if (-not (Get-Module -ListAvailable -Name "powershell-yaml")) { + Write-Host -ForegroundColor Yellow "Installing powershell-yaml ..." + Install-Module -Name "powershell-yaml" -Confirm:$false -Force +} + +Import-Module -Name "powershell-yaml" +Write-Host -ForegroundColor DarkGreen "✅ powershell-yaml loaded" + +# read the yaml file +Write-Host "Reading docker-compose.yml file ..." +$composeContent = Get-Content ("$compoFilePath/docker-compose.yml") | Out-String +$composeLoad = ConvertFrom-Yaml $composeContent -AllDocuments +$composeServices = $composeLoad.Services +$removeKeys = New-Object Collections.Generic.List[String] +$prodKeys = New-Object Collections.Generic.List[String] + +Write-Host -ForegroundColor DarkGreen "✅ docker-compose.yml loaded" + + +# get the keys that need to be removed +Write-Host "Cleaning services ..." + +foreach ($key in $composeServices.Keys) { + if ($key.toString().contains("debug")) { + $removeKeys.Add($key) + } else { + $prodKeys.Add($key) + } +} + +# remove it +foreach ($key in $removeKeys) { + $composeServices.Remove($key) +} + +Write-Host -ForegroundColor DarkGreen "✅ services cleaned" + +# replace all the env variables +Write-Host "Replacing variables ..." + +foreach ($key in $prodKeys) { + $composeServices[$key].Remove("build") + $composeServices[$key].image = ` + $composeServices[$key].image.replace("`${DOCKER_LOGIN}", $dockerLogin) + $composeServices[$key].image = ` + $composeServices[$key].image.replace("`${TAG}", $tag) + $composeServices[$key].image = ` + $composeServices[$key].image.replace("`${GPU}", $gpu) +} + +Write-Host -ForegroundColor DarkGreen "✅ variables replaced" + +# write the torizon.io ready docker-compose +ConvertTo-Yaml $composeLoad ` + -OutFile "$compoFilePath/docker-compose.prod.yml" -Force + +Write-Host -ForegroundColor DarkGreen "✅ docker-compose.prod.yml created" diff --git a/.conf/deps.json b/.conf/deps.json new file mode 100644 index 0000000..2277ff1 --- /dev/null +++ b/.conf/deps.json @@ -0,0 +1,7 @@ +{ + "packages": [ + "openssh-client", + "sshpass", + "dotnet-sdk-8.0" + ] +} diff --git a/.conf/id_rsa b/.conf/id_rsa new file mode 100644 index 0000000..af98f44 --- /dev/null +++ b/.conf/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAqpUaOa6i/TaSKwpdsHzLfGK95dDcFYpmYH5hUj0cBhVEVdl8 +JAG0MEDcZsjQHiBlfUiHmyU2iOWBoKUytpMnzYF6ggdZBv5hBMv3RcTlnNijxzxg +KvbjTm3LqAY8uu4SF9I8oDXVOn7xwZYgiC7s3XRGOP6TowSVEHsLB+cehWs4Y5io +3BngOlcOYZtVrh+i3A7xSHWP7n2IetJEo5C425oaOCZXUJT2egau6ODmvBItZjpG +mNdwVwrEI2H3o+LLI9tieNfyFcUFPNDRXbku1V0mXU9VjZ7WFihZXob11XNCEkY7 +Bg3SpzTntSRn9hZD7i3xDU9MSJz90rnn56ID1wIDAQABAoIBACI239i7/3Mcc/rw +DFSu4z2irIYg+1PSu7AVCT4uhaVutJMnmS+7q8GV8N8o3h18z/5uAs0KjMKuje6D +1AUsxOoCU2krQh8V/K2yn3k+AfQQu+DlakCT3onHmfassZeo0rY2c/SKd6dQ+Pqk +Owg1qUrN3Jvn7ALhk2iH1XGTWNrXj44AxVVhK3zADd/688stubo+mhj1vLJvk3IJ +j/cb9UtCnWDZ6U6E4C+5tfXqzrqbtlrWnIBe0qAwYI6+f4bSQxnNa42wNUh0v0mX +tiY9wnHEmE7nCQNQ8sBeBLEJm3xmFRhABNA+iAglsjfeBIbPNpy7rXty412CmHp0 +mLG0RskCgYEA4t4Ca3EDq/scg7fiplLjhk2AtsZumkYT/N+42x4VlioKfoaivLKf +6mjWUSr+c7Aq0Ssx9GdOFU0epNGbuvymCGBhq6v27SADMJTP2Dmg83FpcV6opOUM +SBz1tnrL9j74Eo9jGdnBjgxHOl4biO+uCjCZxcLoVjsfPpGnCeNSpFUCgYEAwHzM +GfiBR9wl+7cVrtP4YQgmzyZ6YCnqkfFasVAe6cN/0if5sqQRn3Fg3YFrP9GlKkkn +EMvow56nTWUmpx4KG4yanrocEYIsA4uv4kwyFOC/YD+67SQpgTEOjD0seDJoOgpz +ekkxNjMro8RJCK+DrV17qk3/dGY4b3u2M66P03sCgYEAhHCcswJX5WeD/vUzkGtl +PfsywADZgqCUfJRSg0Bt06sZU0Hf8Q7KQnsPWnUh9IGMjW2NDSPdtpu93vQq2J8z +gkJZ4nFShp2gnxTQvWbvKg80QiXDh/fbEqItY2kOduPNyHACp42y6+0JC+7yXh+l +13cF3ihpoCWFmO5IuIIHtb0CgYEAk5acwEUEi17HWOuWmog4591iNG8iYd2B2Akh +Ktdt/HtD5JV2JX0bbg924CA9ZZ+Pbo7Mf1p5zJQ3X2Zxbq3fviPe4ll88AfJS6at +Y2xc8hkpY9k5sF6L3F0K9IhrMnYj6GzfAiJgs1gk29rCzQAWjLUi/v/zIQLHvuMy +Xwo3iUUCgYEAo/27Qkb5nOdL8sJwFggDrdB1pPrxXN20KmYCJF1P9wPDhnj1fJ7+ +0Z/56XYzPA2rQx0vf5idvoGQ3KZS7QkOClLtcyevH3b38fnmAqv+dPLRHmrSd+3G +BspdMlr3rwZK8RKwXjDzLYpwSvjmf7PTGTPO3C7WTzg+m0U9RbSJKi4= +-----END RSA PRIVATE KEY----- diff --git a/.conf/id_rsa.pub b/.conf/id_rsa.pub new file mode 100644 index 0000000..86f8fe6 --- /dev/null +++ b/.conf/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqlRo5rqL9NpIrCl2wfMt8Yr3l0NwVimZgfmFSPRwGFURV2XwkAbQwQNxmyNAeIGV9SIebJTaI5YGgpTK2kyfNgXqCB1kG/mEEy/dFxOWc2KPHPGAq9uNObcuoBjy67hIX0jygNdU6fvHBliCILuzddEY4/pOjBJUQewsH5x6FazhjmKjcGeA6Vw5hm1WuH6LcDvFIdY/ufYh60kSjkLjbmho4JldQlPZ6Bq7o4Oa8Ei1mOkaY13BXCsQjYfej4ssj22J41/IVxQU80NFduS7VXSZdT1WNntYWKFlehvXVc0ISRjsGDdKnNOe1JGf2FkPuLfENT0xInP3SuefnogPX \ No newline at end of file diff --git a/.conf/projectUpdater.ps1 b/.conf/projectUpdater.ps1 new file mode 100644 index 0000000..85e5f02 --- /dev/null +++ b/.conf/projectUpdater.ps1 @@ -0,0 +1,357 @@ + +param() + +# include +. "$env:HOME/.apollox/scripts/utils/formatJson.ps1" +. "$env:HOME/.apollox/scripts/utils/replaceTasksInput.ps1" + +$errorActionPreference = "Stop" + +$projectFolder = $args[0] +$projectName = $args[1] +$acceptAll = $args[2] + +function _checkArg ($_arg) { + if ([string]::IsNullOrEmpty($_arg)) { + throw "❌ arg is not defined" + } +} + +function _checkIfFileContentIsEqual ($_file1, $_file2) { + $file1 = Get-FileHash $_file1 + $file2 = Get-FileHash $_file2 + + if ($file1.Hash -eq $file2.Hash) { + return $true + } else { + return $false + } +} + +function _openMergeWindow ($_path1, $_path2) { + if ($acceptAll -eq $true) { + cp $_path1 $_path2 + return + } + + if ( + -not (_checkIfFileContentIsEqual $_path1 $_path2) + ) { + code --wait --diff $_path1 $_path2 + } +} + +# check if the args passed are not empty +_checkArg $projectFolder +_checkArg $projectName + +# the accept all is optional +if ([string]::IsNullOrEmpty($acceptAll)) { + $acceptAll = $false +} else { + if ($acceptAll -eq "1") { + # ask for confirmation + Write-Host -ForegroundColor Yellow "You are about to accept all incoming changes from the updated template" + Write-Host -ForegroundColor Yellow "If the project is not versioned there is no way back" + $_sure = Read-Host -Prompt "Accept all changes? [y/n]" + + if ($_sure -ne "y") { + exit 0 + } + + $acceptAll = $true + } else { + $acceptAll = $false + } +} + +# copy the new one and make the subs +$templateName = Get-Content $projectFolder/.conf/.template +$containerName = Get-Content $projectFolder/.conf/.container + +# check first if the folder already exists +if (-not (Test-Path $projectFolder/.conf/tmp)) { + mkdir $projectFolder/.conf/tmp +} + +# get the metadata +$_metadata = Get-Content "$Env:HOME/.apollox/templates.json" | ConvertFrom-Json +$_templateMetadata = + $_metadata.Templates | + Where-Object { $_.folder -eq $templateName } + +# ----------------------------------------------------------- ALWAYS ACCEPT NEW +# UPDATE.JSON: +Copy-Item ` + $Env:HOME/.apollox/$templateName/.conf/update.json ` + $projectFolder/.conf/update.json + +# DEPS.JSON: +Copy-Item ` + $Env:HOME/.apollox/$templateName/.conf/deps.json ` + $projectFolder/.conf/deps.json + +# PROJECT UPDATER: +if ( + -not (_checkIfFileContentIsEqual ` + $Env:HOME/.apollox/scripts/projectUpdater.ps1 ` + $projectFolder/.conf/projectUpdater.ps1) +) { + # in this case we need to update the project updater + # and then run it again + Copy-Item ` + $Env:HOME/.apollox/scripts/projectUpdater.ps1 ` + $projectFolder/.conf/projectUpdater.ps1 + + Write-Host ` + -ForegroundColor DarkYellow ` + "⚠️ project updater updated, running it again" + + # run the project updater again + & $projectFolder/.conf/projectUpdater.ps1 ` + $projectFolder ` + $projectName ` + $acceptAll + + exit $LASTEXITCODE +} + +# TASKS.PS1: +Copy-Item ` + $Env:HOME/.apollox/scripts/tasks.ps1 ` + $projectFolder/.vscode/tasks.ps1 + +# CHECK DEPS +Copy-Item ` + $Env:HOME/.apollox/scripts/checkDeps.ps1 ` + $projectFolder/.conf/checkDeps.ps1 + +# RUN CONTAINER IF NOT EXISTS +Copy-Item ` + $Env:HOME/.apollox/scripts/runContainerIfNotExists.ps1 ` + $projectFolder/.conf/runContainerIfNotExists.ps1 + +# SHARE WSL PORTS +Copy-Item ` + $Env:HOME/.apollox/scripts/shareWSLPorts.ps1 ` + $projectFolder/.conf/shareWSLPorts.ps1 + +# TORIZON PACKAGES +Copy-Item ` + $Env:HOME/.apollox/scripts/torizonPackages.ps1 ` + $projectFolder/.conf/torizonPackages.ps1 + +# TORIZON IO: +Copy-Item ` + $Env:HOME/.apollox/scripts/torizonIO.ps1 ` + $projectFolder/.conf/torizonIO.ps1 + +# CREATE DOCKER COMPOSE PRODUCTION: +Copy-Item ` + $Env:HOME/.apollox/scripts/createDockerComposeProduction.ps1 ` + $projectFolder/.conf/createDockerComposeProduction.ps1 + +# TCB ENV SETUP: +Copy-Item ` + $Env:HOME/.apollox/scripts/bash/tcb-env-setup.sh ` + $projectFolder/.conf/tcb-env-setup.sh + +# CHECK CI ENV: +Copy-Item ` + $Env:HOME/.apollox/scripts/checkCIEnv.ps1 ` + $projectFolder/.conf/checkCIEnv.ps1 + +# VALIDATE DEPS RUNNING ENV: +Copy-Item ` + $Env:HOME/.apollox/scripts/validateDepsRunning.ps1 ` + $projectFolder/.conf/validateDepsRunning.ps1 + +Write-Host -ForegroundColor DarkGreen "✅ always accept new" +# ----------------------------------------------------------- ALWAYS ACCEPT NEW + + +# now that we have an updated version we ca read it +$updateTable = Get-Content $projectFolder/.conf/update.json | ConvertFrom-Json + + +# ----------------------------------------------------------------------- TASKS +# TASKS.JSON: +Copy-Item $Env:HOME/.apollox/$templateName/.vscode/tasks.json ` + $projectFolder/.conf/tmp/tasks-next.json + +if ($_templateMetadata.mergeCommon -ne $False) { + Write-Host -ForegroundColor Yellow "Applying common tasks ..." + $commonTasks = + Get-Content "$env:HOME/.apollox/assets/tasks/common.json" | + ConvertFrom-Json + $commonInputs = + Get-Content "$env:HOME/.apollox/assets/tasks/inputs.json" | + ConvertFrom-Json + $projTasks = + Get-Content "$projectFolder/.conf/tmp/tasks-next.json" | + ConvertFrom-Json + + $projTasks.tasks += $commonTasks.tasks + $projTasks.inputs += $commonInputs.inputs + + ConvertTo-Json -Depth 100 -InputObject $projTasks | ` + Format-Json | ` + Out-File -FilePath "$projectFolder/.conf/tmp/tasks-next.json" +} + +# we need to create a tmp folder to the update files +Set-Location $projectFolder/.conf/tmp + +# tcb does not have the common Docker files +if ($templateName -ne "tcb") { + # The generic template doesn't have a Dockerfile.debug + if ($templateName -ne "genericTemplate") { + Copy-Item $Env:HOME/.apollox/$templateName/Dockerfile.debug . + } + Copy-Item $Env:HOME/.apollox/$templateName/Dockerfile . + Copy-Item $Env:HOME/.apollox/$templateName/docker-compose.yml . + Copy-Item $Env:HOME/.apollox/assets/github/workflows/build-application.yaml . + Copy-Item $Env:HOME/.apollox/assets/gitlab/.gitlab-ci.yml . + Copy-Item $Env:HOME/.apollox/$templateName/.doc/README.md . +} + +Copy-Item $Env:HOME/.apollox/$templateName/.gitignore . + +# read the update table: +for ($i = 0; $i -lt $updateTable.Count; $i++) { + $_source = $updateTable[$i].source + Copy-Item "$Env:HOME/.apollox/$templateName/$_source" . +} + +# change the contents +Write-Host -ForegroundColor Yellow "Renaming file contents ..." +Get-ChildItem -Force -File -Recurse * | ForEach-Object { + Write-Host $_ + $a = $_.fullname; + + # do not mess up with binary files + $mimeType = file --mime-encoding $a + + if (-not $mimeType.Contains("binary")) { + # id_rsa is a special case, is ascii but we do not have permissions + if (-not $a.Contains("id_rsa")) { + if ($_ -isnot [System.IO.DirectoryInfo]) { + ( Get-Content $a ) | + ForEach-Object { + $_ -replace "__change__",$projectName + } | Set-Content $a + + ( Get-Content $a ) | + ForEach-Object { + $_ -replace "__container__",$containerName + } | Set-Content $a + + ( Get-Content $a ) | + ForEach-Object { + $_ -replace "__home__",$env:HOME + } | Set-Content $a + + ( Get-Content $a ) | + ForEach-Object { + $_ -replace "__templateFolder__", $templateName + } | Set-Content $a + } + } elseif (-not $a.Contains("id_rsa.pub")) { + chmod 0400 $a + } + } +} + +# we need to also replace the inputs +Replace-Tasks-Input + +# and back to the project folder +Set-Location - + +# open the merge window +_openMergeWindow ` + $projectFolder/.conf/tmp/tasks-next.json ` + $projectFolder/.vscode/tasks.json + +Write-Host -ForegroundColor DarkGreen "✅ tasks.json" +# ----------------------------------------------------------------------- TASKS + + + +# ---------------------------------------------------------------------- COMMON + +# TCB does not have the common application Docker files +if ($templateName -ne "tcb") { + # DOCKERFILE.DEBUG: + # The generic template doesn't have a Dockerfile.debug + if ($templateName -ne "genericTemplate") { + _openMergeWindow ` + $projectFolder/.conf/tmp/Dockerfile.debug ` + $projectFolder/Dockerfile.debug + } + # DOCKERFILE: + _openMergeWindow ` + $projectFolder/.conf/tmp/Dockerfile ` + $projectFolder/Dockerfile + # DOCKER-COMPOSE: + _openMergeWindow ` + $projectFolder/.conf/tmp/docker-compose.yml ` + $projectFolder/docker-compose.yml + + # GITHUB ACTIONS: + _openMergeWindow ` + $projectFolder/.conf/tmp/build-application.yaml ` + $projectFolder/.github/workflows/build-application.yaml + + # GITLAB CI: + _openMergeWindow ` + $projectFolder/.conf/tmp/.gitlab-ci.yml ` + $projectFolder/.gitlab-ci.yml + + # TEMPLATE SPECIFIC DOCUMENTATION: + # check if the folder already exists, if not create it + # and always accept the new one + if (-not (Test-Path $projectFolder/.doc)) { + mkdir $projectFolder/.doc + } + + Copy-Item ` + $Env:HOME/.apollox/$templateName/.doc/README.md ` + $projectFolder/.doc/README.md +} + +# GITIGNORE: +_openMergeWindow ` + $projectFolder/.conf/tmp/.gitignore ` + $projectFolder/.gitignore + +Write-Host -ForegroundColor DarkGreen "✅ common" +# ---------------------------------------------------------------------- COMMON + + + +# -------------------------------------------------------------------- SPECIFIC +for ($i = 0; $i -lt $updateTable.Count; $i++) { + $_source = Split-Path $updateTable[$i].source -Leaf + $_target = $updateTable[$i].target + $_target = (Invoke-Expression "echo `"$_target`"") + + # check if the file exists, if not simple copy it + if ( + (Test-Path $projectFolder/$_target) -eq $True + ) { + _openMergeWindow ` + $projectFolder/.conf/tmp/$_source ` + $projectFolder/$_target + } else { + cp $projectFolder/.conf/tmp/$_source $projectFolder/$_target + } +} + +Write-Host -ForegroundColor DarkGreen "✅ specific" +# -------------------------------------------------------------------- SPECIFIC + +# clean up tmp +Remove-Item -Recurse -Force $projectFolder/.conf/tmp + +Write-Host -ForegroundColor DarkGreen "✅ Update done" diff --git a/.conf/runContainerIfNotExists.ps1 b/.conf/runContainerIfNotExists.ps1 new file mode 100644 index 0000000..6e47377 --- /dev/null +++ b/.conf/runContainerIfNotExists.ps1 @@ -0,0 +1,58 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidOverwritingBuiltInCmdlets', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingInvokeExpression', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingPositionalParameters', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidGlobalVars', "" +)] +param( + [string] $ContainerRuntime, + [string] $RunArguments, + [string] $ContainerName +) + +$env:DOCKER_HOST = "" + +if ($Env:GITLAB_CI -eq $true) { + Write-Host "ℹ️ :: GITLAB_CI :: ℹ️" + $Env:DOCKER_HOST = "tcp://docker:2375" +} + +$_containerRuntime = $ContainerRuntime +$_runArguments = $RunArguments.Trim("'").Trim('"'); +$_containerName = $ContainerName + +# debug +Write-Host "Container Runtime: $_containerRuntime" +Write-Host "Run Arguments: $_runArguments" +Write-Host "Container Name: $_containerName" + +$_containerInfo = + Invoke-Expression "$_containerRuntime container inspect $_containerName" | ` + ConvertFrom-Json + +if ($null -ne $_containerInfo) { + $_containerInfo = $_containerInfo[0] +} + +if ($null -ne $_containerInfo) { + Write-Host "Container Exists" + + if ($_containerInfo.State.Running -eq $false) { + Invoke-Expression "$_containerRuntime start $_containerName" + } else { + Write-Host "Container is running" + } +} else { + Write-Host "Container does not exist. Starting ..." + Invoke-Expression "$_containerRuntime run --name $_containerName $_runArguments" +} diff --git a/.conf/shareWSLPorts.ps1 b/.conf/shareWSLPorts.ps1 new file mode 100644 index 0000000..9abb943 --- /dev/null +++ b/.conf/shareWSLPorts.ps1 @@ -0,0 +1,63 @@ +# DO NOT ADD THE suppress HERE +# THIS NEEDS TO BE SUPPORTED ON THE WINDOWS POWERSHELL +# this only makes sense for WSL +if (! [string]::IsNullOrEmpty($env:WSL_DISTRO_NAME)) { + $_workspace = $args[0] + $remoteport = bash -c "ifconfig eth0 | grep 'inet '" + $found = $remoteport -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; + + if( $found ){ + $remoteport = $matches[0]; + } else{ + Write-Output "The Script Exited, the ip address of WSL 2 cannot be found"; + exit; + } + + #[Ports] + + #All the ports you want to forward separated by coma + $ports=@(8090, 5002); + $superScript = ""; + + #[Static ip] + #You can change the addr to your ip config to listen to a specific address + $addr='0.0.0.0'; + $ports_a = $ports -join ","; + + #Remove Firewall Exception Rules + $superScript = "(Remove-NetFireWallRule -DisplayName ApolloX) -or `$true ; "; + + #adding Exception Rules for inbound and outbound Rules + $superScript = "$($superScript) New-NetFireWallRule -DisplayName ApolloX -Direction Outbound -LocalPort $ports_a -Action Allow -Protocol TCP ; "; + $superScript = "$($superScript) New-NetFireWallRule -DisplayName ApolloX -Direction Inbound -LocalPort $ports_a -Action Allow -Protocol TCP ; "; + + for( $i = 0; $i -lt $ports.length; $i++ ){ + $port = $ports[$i]; + # cspell:disable-next-line + $superScript = "$($superScript) (netsh interface portproxy delete v4tov4 listenport=$port listenaddress=$addr) -or `$true ; "; + } + + $superScript = "$($superScript) wsl -e pwsh -nop -File $_workspace/.vscode/tasks.ps1 run run-docker-registry-wsl ; " + + for( $i = 0; $i -lt $ports.length; $i++ ){ + $port = $ports[$i]; + # cspell:disable-next-line + $superScript = "$($superScript) (netsh interface portproxy add v4tov4 listenport=$port listenaddress=$addr connectport=$port connectaddress=$remoteport) -or `$true ; "; + } + + $superScript = $superScript.Trim(); + + # in case of issues break the glass + # cspell:disable-next-line + #Write-Host "start-process powershell -verb runas -ArgumentList '-NoProfile -C `"$($superScript) echo done`"'" + + if ($env:DEBUG_SHARED_PORTS -eq "true") { + # this command has a Read-Host so it will wait for the user to press enter + # this way we can see the output of the script before window closes + # cspell:disable-next-line + powershell.exe -NoProfile -C "start-process powershell -verb runas -ArgumentList '-NoProfile -C `"$superScript echo done; Read-Host ; `"'" + } else { + # cspell:disable-next-line + powershell.exe -NoProfile -C "start-process powershell -verb runas -ArgumentList '-NoProfile -C `"$superScript echo done`"'" + } +} diff --git a/.conf/tcb-env-setup.sh b/.conf/tcb-env-setup.sh new file mode 100755 index 0000000..5d319fa --- /dev/null +++ b/.conf/tcb-env-setup.sh @@ -0,0 +1,308 @@ +#!/usr/bin/env bash + +# we need this to expand during the source of the file +shopt -s expand_aliases + +# For DockerHub the variable can be empty, by tcb platform push command +# requires a value, so passing the default DockerHub registry to it if it is +# empty +if [$DOCKER_REGISTRY = ""]; then + export DOCKER_REGISTRY="registry-1.docker.io" +fi + +# Check to make sure script is being sourced otherwise exit +SOURCED=0 + +# zsh +if [ -n "$ZSH_EVAL_CONTEXT" ]; then + case $ZSH_EVAL_CONTEXT in *:file) SOURCED=1;; esac + +# ksh +elif [ -n "$KSH_VERSION" ]; then + [ "$(cd $(dirname -- "$0") && pwd -P)/$(basename -- "$0")" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && SOURCED=1 + +# bash +elif [ -n "$BASH_VERSION" ]; then + (return 0 2>/dev/null) && SOURCED=1 + +# All other shells: examine $0 for known shell binary filenames +else + # Detects `sh` and `dash`; add additional shell filenames as needed. + case ${0##*/} in sh|dash) SOURCED=1;; esac +fi + +# check if it was sourced +if [ "$SOURCED" = "0" ]; then + echo "Error: don't run $0, source it." + exit 1 +fi + +# cleanup variables and functions used in script since script is meant to be sourced +tcb_env_setup_cleanup () { + unset source + unset under_windows + unset user_tag + unset storage + unset volumes + unset network + unset remote_tags + unset local_tags + unset tag + unset latest + unset latest_remote + unset latest_local + unset pull_remote + unset chosen_tag + unset -f tcb_env_setup_usage 2>/dev/null + unset -f get_latest_tag 2>/dev/null + unset -f tcb_env_setup_check_updated 2>/dev/null +} + +tcb_env_setup_cleanup + +# Usage help message +tcb_env_setup_usage () { + echo "Usage: source tcb-env-setup.sh [OPTIONS] [-- ]" + echo "" + echo "optional arguments:" + echo " -a : select auto mode" + echo " With this flag enabled the script will automatically run with no need" + echo " for user input. Valid values for are either remote or local." + echo " When \"-a remote\" is passed, the script will automatically use the" + echo " latest version of TorizonCore Builder online, with no consideration" + echo " for any local versions that may exist. When \"-a local\" is passed" + echo " the script will automatically use the latest version of TorizonCore" + echo " Builder found locally, with no consideration to what may be online." + echo " This flag is mutually exclusive with the -t flag." + echo "" + echo " -t : select tag mode" + echo " With this flag enabled the script will automatically run with no need" + echo " for user input. Valid values for can be found online:" + echo " https://registry.hub.docker.com/r/torizon/torizoncore-builder/tags?page=1&ordering=last_updated." + echo " Whatever is provided will then be pulled from online." + echo " This flag is mutually exclusive with the -a flag." + echo "" + echo " -d: disable volumes" + echo " With this flag enabled the script will setup torizoncore-builder " + echo " without Docker volumes meaning some torizoncore-builder commands will" + echo " require additional directories to be passed as arguments. By default" + echo " with this flag excluded torizoncore-builder is setup with Docker" + echo " volumes." + echo "" + echo " -s: select storage directory or Docker volume" + echo " Internal storage directory or Docker volume that TorizonCore Builder" + echo " should use to keep its state information and image customizations." + echo " It must be an absolute directory or a Docker volume name. If this" + echo " flag is not set, the \"storage\" Docker volume will be used." + echo "" + echo " -n: do not enable \"host\" network mode." + echo " Under Linux the tool runs in \"host\" network mode by default allowing" + echo " it to operate as a server without explicit port publishing. Under" + echo " Windows this mode of operation is always disabled requiring port" + echo " publishing to be set up if the tool is to act as a server. This flag" + echo " disables the default behavior (which is relevant under Linux)." + echo "" + echo " -- : extra options to be passed to \"docker run\"." + echo " Parameters after -- are simply forwarded to the \"docker run\"" + echo " invocation in the alias that the script creates." + echo "" + echo " -h: help" + echo " Prints usage information." + echo "" +} + +tcb_env_setup_check_updated() { + # Check if md5sum on git matches the md5sum on this file. + [ ! -f "$1" ] && return + + local target_url="https://raw.githubusercontent.com/toradex/tcb-env-setup/master/tcb-env-setup.sh" + + local status_code=$(curl -sL -o tcb-env-setup.sh.tmp -w '%{http_code}' "$target_url") + local remote_md5sum=$(md5sum tcb-env-setup.sh.tmp | cut -d ' ' -f 1) + local local_md5sum=$(md5sum "$1" | cut -d ' ' -f 1) + rm tcb-env-setup.sh.tmp + +} + +# Are we running under Windows? +under_windows=0 +if uname -r | grep -i "microsoft" > /dev/null; then + under_windows=1 +fi + +# Parse flags +volumes=" -v /deploy " +storage="storage" +network=" --network=host " +if [ $under_windows = "1" ]; then + # Do not use "host" network mode under Windows/WSL + network=" " +fi +while [[ $# -gt 0 ]] +do + case "$1" in + -a) source=$2;[ "$2" ]||source="empty"; shift; shift;; + -t) user_tag="$2";[ "$2" ]||user_tag="empty"; shift; shift;; + -s) storage="$2";[ "$2" ]||storage="empty"; shift; shift;; + -d) volumes=" "; shift;; + -n) network=" "; shift;; + --) shift; break;; + -h|*) tcb_env_setup_usage; tcb_env_setup_cleanup; return;; + esac +done + +if [[ $source != "local" ]] +then + if [ -z "${ZSH_VERSION-}" ]; then + SCRIPT_PATH="$PWD/${BASH_SOURCE[0]}" + else + SCRIPT_PATH="${(%):-%x}" + fi + + tcb_env_setup_check_updated $SCRIPT_PATH +fi + +if [[ $source = "empty" ]] || [[ $user_tag = "empty" ]] || [[ $storage = "empty" ]] +then + tcb_env_setup_usage + tcb_env_setup_cleanup + return +fi + +# Check that only one flag is used at a time +if [[ -n $source && -n $user_tag ]] +then + echo "Error: -a and -t are mutually exclusive. Please only use one flag at a time." + tcb_env_setup_cleanup + return +fi +# Check that only valid values are passed for -a flag +if [[ -n $source && $source != "local" && $source != "remote" ]] +then + echo "Error: unrecognized value $source for -a" + tcb_env_setup_cleanup + return +fi +# Check that storage is an absolute directory or a valid Docker volume name +if [[ $storage != /* && ! $storage =~ ^[a-zA-Z][a-zA-Z0-9_.-]*$ ]] +then + echo "Error: \"$storage\" storage must be an absolute directory or a valid Docker volume name." + tcb_env_setup_cleanup + return +fi +if [ $under_windows = "1" -a $# -eq 0 ]; then + echo "Warning: If you intend to use torizoncore-builder as a server (listening to ports), then you should pass extra parameters to \"docker run\" (via the -- switch)." +fi + +# Get list of image tags from docker hub +remote_tags=$(curl -L -s 'https://registry.hub.docker.com/v2/namespaces/torizon/repositories/torizoncore-builder/tags' | sed -n -e 's/\("name"\) *: *\("[^"]\+"\)/\n\1:\2\n/gp' | \ + sed -n -e 's/"name":"\([^"]\+\)"/\1/p') +# Get list of image tags locally +# TODO RegEx Fails on MacOS. This one works: sed -En 's/^.*torizoncore-builder[[:space:]]+([0-9]+).*$/\1/p' +local_tags=$(docker images --format "{{.Tag}}" torizon/torizoncore-builder) + +# Determine the tag with the greatest numerical major revision +get_latest_tag () { + latest=0 + for tag in $(echo $@) + do + if [[ $tag != *"."* ]] + then + if [[ $tag -gt $latest ]] + then + latest=$tag + fi + fi + done + return "$latest" +} + +get_latest_tag "$remote_tags" +latest_remote=$? + +# Figure out whether to use latest local or latest remote version of Tcore-builder based on either flags or user response +if [[ -z $local_tags && -z $source && -z $user_tag ]] +then + echo "TorizonCore Builder is not installed. Pulling the latest version from Docker Hub..." + pull_remote=true + chosen_tag=$latest_remote +elif [[ -n $local_tags && -z $source && -z $user_tag ]] +then + get_latest_tag "$local_tags" + latest_local=$? + echo -n "You may have an outdated version installed. Would you like to check for updates online? [y/n] " + read -r yn + case $yn in + [Yy]* ) pull_remote=true + chosen_tag=$latest_remote;; + [Nn]* ) pull_remote=false + chosen_tag=$latest_local;; + * ) echo "Please answer yes or no." + tcb_env_setup_cleanup + return;; + esac +elif [[ $source == "local" ]] +then + get_latest_tag "$local_tags" + latest_local=$? + if [[ $latest_local == "0" ]] + then + echo "Error: no local versions found!" + tcb_env_setup_cleanup + return + fi + pull_remote=false + chosen_tag=$latest_local +elif [[ $source == "remote" ]] +then + pull_remote=true + chosen_tag=$latest_remote +elif [[ -n $user_tag ]] +then + pull_remote=false + chosen_tag=$user_tag +fi + +# Sets up chosen version of Tcore-builder based on result from above +echo -e "Setting up TorizonCore Builder with version $chosen_tag.\n" + +if [[ $pull_remote == true ]] +then + echo -e "Pulling TorizonCore Builder..." + if docker pull torizon/torizoncore-builder:"$chosen_tag"; then + echo -e "Done!\n" + else + echo "Error: could not pull TorizonCore Builder from Docker Hub!" + tcb_env_setup_cleanup + return + fi +fi + +# if installing latest version, download and source the bash completion script +if [[ "$chosen_tag" == "$latest_remote" ]] +then + if wget -q https://raw.githubusercontent.com/toradex/tcb-env-setup/master/torizoncore-builder-completion.bash -O ./torizoncore-builder-completion.bash.tmp 2>/dev/null; then + source ./torizoncore-builder-completion.bash.tmp 2>/dev/null && rm -rf torizoncore-builder-completion.bash.tmp + fi +fi + +function tcb_dynamic_params() { + local cont_name="tcb_$(date +%s)" + echo "-e TCB_CONTAINER_NAME=$cont_name --name $cont_name" +} +# TODO Not compatible with ZSH +export -f tcb_dynamic_params + +alias torizoncore-builder='docker run --rm'"$volumes"'-v "$(pwd)":/workdir -v '"$storage"':/storage -v /var/run/docker.sock:/var/run/docker.sock'"$network"'$(tcb_dynamic_params) '"$*"' torizon/torizoncore-builder:'"$chosen_tag" + +[[ $storage =~ ^[a-zA-Z][a-zA-Z0-9_.-]*$ ]] && storage="Docker volume named '$storage'" + +tcb_env_setup_cleanup +unset -f tcb_env_setup_cleanup 2>/dev/null + +if [[ ! -z "${VSCODE_CMD}" ]]; then + # solve any environment variable + VSCODE_CMD=$(eval echo $VSCODE_CMD) + # execute it + torizoncore-builder $VSCODE_CMD +fi diff --git a/.conf/torizonIO.ps1 b/.conf/torizonIO.ps1 new file mode 100644 index 0000000..e4baa63 --- /dev/null +++ b/.conf/torizonIO.ps1 @@ -0,0 +1,248 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseApprovedVerbs', "" +)] +param() + +# TODO: we need to work with the offsets and limits + +$_VERSION = "0.1.0" + +$ErrorActionPreference = "Stop" + +# fail fast +if ($null -eq $env:PLATFORM_CLIENT_ID) { + Write-Host -ForegroundColor Red "PLATFORM_CLIENT_ID not set" + throw "PLATFORM_CLIENT_ID not set" +} + +if ($null -eq $env:PLATFORM_CLIENT_SECRET) { + Write-Host -ForegroundColor Red "PLATFORM_CLIENT_SECRET not set" + throw "PLATFORM_CLIENT_SECRET not set" +} + +# check if the TorizonPlatformAPI module is installed +$_mod = Get-Module -ListAvailable -Name "TorizonPlatformAPI" + +if ( + -not ($_mod) -or + ($_mod.Version[0].ToString().Contains($_VERSION) -eq $false) +) { + Install-Module ` + -Name "TorizonPlatformAPI" ` + -RequiredVersion $_VERSION ` + -Confirm:$false ` + -Force | Out-Null +} + +Import-Module -Name "TorizonPlatformAPI" -RequiredVersion $_VERSION | Out-Null +#Write-Host -ForegroundColor DarkGreen "✅ TorizonPlatformAPI loaded" + +# get the bearer token +function _getJonOsterToken () { + $_headers = @{ + "Content-Type" = "application/x-www-form-urlencoded" + } + + $_payload = @{ + "grant_type" = "client_credentials" + "client_id" = "$env:PLATFORM_CLIENT_ID" + "client_secret" = "$env:PLATFORM_CLIENT_SECRET" + } + + $_ret = Invoke-RestMethod ` + -Method Post ` + -Uri "https://kc.torizon.io/auth/realms/ota-users/protocol/openid-connect/token" ` + -Headers $_headers ` + -Body $_payload ` + -ContentType "application/x-www-form-urlencoded" ` + -ErrorAction Stop + + # and we have the AWESOME JonOster Token 🦪 + return $_ret.access_token +} + +# configure the API client +$_token = _getJonOsterToken +Set-TorizonPlatformAPIConfiguration ` + -BaseUrl "https://app.torizon.io/api/v2beta" ` + -DefaultHeaders @{ "Authorization" = "Bearer $_token" } ` + -ErrorAction Stop + +function _getTargetByHash ([string] $_hash) { + $_packages = Get-TorizonPlatformAPIPackages + + foreach ($_package in $_packages.values) { + if ($_hash -eq $_package.hashes.sha256) { + return $_package + } + } + + return $null +} + +function _getFleetDevices ($_fleetName) { + $_fleets = Get-TorizonPlatformAPIFleets + + $_fleetId = ( + $_fleets.values | + Where-Object { $_.name -eq "$_fleetName" } + ).id + + if ($null -eq $_fleetId) { + throw "Fleet '$_fleetName' not found" + } + + $_devices = + Get-TorizonPlatformAPIFleetsFleetidDevices ` + -FleetId $_fleetId + + if ($_devices.total -eq 0) { + throw "Fleet '$_fleetName' has no devices" + } + + return $_devices.values +} + +function _getFleetId ($_fleetName) { + $_fleets = Get-TorizonPlatformAPIFleets + + $_fleetId = ( + $_fleets.values | + Where-Object { $_.name -eq "$_fleetName" } + ).id + + if ($null -eq $_fleetId) { + throw "Fleet '$_fleetName' not found" + } + + return $_fleetId +} + +function _resolvePlatformMetadata ([object] $targets, [string] $targetName) { + $_packages = $targets + $_packageName = $targetName + $_latestV = 0 + $_hash = $null + + $_ret = [PSCustomObject]@{ + "hash" = $null + "version" = $null + } + + foreach ($_package in $_packages.values) { + if ($_package.name -eq $_packageName) { + $_actualV = $_package.version + + if (([int]$_latestV) -lt ([int]$_actualV)) { + $_latestV = $_actualV + $_hash = $_package.hashes.sha256 + } + } + } + + $_ret.hash = $_hash + $_ret.version = $_latestV + + return $_ret +} + +function package-latest-hash ([string] $packageName) { + $_targetName = $packageName + $_targets = Get-TorizonPlatformAPIPackages + $_hash = $null + + $_ret = _resolvePlatformMetadata $_targets $_targetName + $_hash = $_ret.hash + + if ($null -eq $_hash) { + Write-Host -ForegroundColor Red "package not found" + exit 404 + } + + return $_hash +} + +function package-latest-version ([string] $packageName) { + $_packageName = $packageName + $_packages = Get-TorizonPlatformAPIPackages + + $_ret = _resolvePlatformMetadata $_packages $_packageName + + # it's return 0 if not found (we can publish the version 1) + return $_ret.version +} + +function update-fleet-latest ([string] $targetName, [string] $fleetName) { + $_targetName = $targetName + $_fleetName = $fleetName + + $_targetHash = package-latest-hash $_targetName + $_target = _getTargetByHash($_targetHash) + + if ($null -eq $_target) { + throw "package $_targetName not found" + } + + $_targetId = $_target.packageId + $_fleetId = _getFleetId($_fleetName) + + $_updateRequest = Initialize-TorizonPlatformAPIUpdateRequest ` + -PackageIds @($_targetId) ` + -Fleets @($_fleetId) + + $Result = + Submit-TorizonPlatformAPIUpdates ` + -UpdateRequest $_updateRequest + + return $Result +} + +# check if the command exists +$_cmd = $args[0] +$_sub = $args[1] +$_third = $args[2] + +try { + # is duple + if (Get-Command "$_cmd-$_sub" -ErrorAction SilentlyContinue) { + $_args = '"' + ($args[3..$args.Length] -join '" "') + '"' + + (Invoke-Expression "$_cmd-$_sub $_args") + # is triple + } elseif ( + Get-Command "$_cmd-$_sub-$_third" -ErrorAction SilentlyContinue + ) { + $_args = "`"" + ($args[3..$args.Length] -join "`" `"") + "`"" + + (Invoke-Expression "$_cmd-$_sub-$_third $_args") + } else { + Write-Host "" + Write-Host "usage:" + Write-Host "" + Write-Host " Get the latest hash pushed by package name:" + Write-Host " package latest hash " + Write-Host " Get the latest version pushed by package name:" + Write-Host " package latest version " + Write-Host "" + Write-Host " Update a fleet with a defined package:" + Write-Host " update fleet latest " + Write-Host "" + + exit 69 + } +} catch { + Write-Host $_.Exception.Message -Foreground "Red" + Write-Host "" + $lines = $_.ScriptStackTrace.Split("`n") + + foreach ($line in $lines) { + Write-Host "`t$line" -Foreground "DarkGray" + } + + Write-Host "" + exit 500 +} diff --git a/.conf/torizonPackages.ps1 b/.conf/torizonPackages.ps1 new file mode 100644 index 0000000..cfe4456 --- /dev/null +++ b/.conf/torizonPackages.ps1 @@ -0,0 +1,130 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidOverwritingBuiltInCmdlets', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingInvokeExpression', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingPositionalParameters', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidGlobalVars', "" +)] +param() + +$TORIZON_ARCH = $args[0] + +if ($TORIZON_ARCH -eq "aarch64") { + $TORIZON_ARCH = "arm64" +} + +if ($TORIZON_ARCH -eq "armv7l") { + $TORIZON_ARCH = "armhf" +} + +if ($TORIZON_ARCH -eq "x86_64") { + $TORIZON_ARCH = "amd64" +} + +# get the files content +function _getFileLines ($file) { + [string[]] $lines = Get-Content -Path $file + + return $lines +} + +# replace the dev and prod sections +function _ReplaceSection ([string[]]$fileLines, [string]$section) { + $_startIx = $null + $_endIx = $null + $_ix = 0 + $_newFileContent = New-Object System.Collections.Generic.List[string] + + foreach ($line in $fileLines) { + if ($line.Contains("__${section}_start__")) { + $_startIx = $_ix + } + + if ($line.Contains("__${section}_end__")) { + $_endIx = $_ix + } + + $_ix++ + } + + $_ix = 0; + $_stopAdd = $false + + foreach ($line in $fileLines) { + if ($_ix -eq $_startIx) { + $_newFileContent.Add($line) + $_stopAdd = $true + + $_json = Get-Content -Path "torizonPackages.json" | ConvertFrom-Json + $_devPacks = $_json.devDeps + $_prodPacks = $_json.deps + + if ($section.Contains("dev")) { + foreach ($pack in $_devPacks) { + $_newFileContent.Add("`t${pack}:${TORIZON_ARCH} \") + } + } elseif ($section.Contains("prod")) { + foreach ($pack in $_prodPacks) { + $_newFileContent.Add("`t${pack}:${TORIZON_ARCH} \") + } + } + } + + if ($_ix -eq $_endIx) { + $_stopAdd = $false + } + + if (-not $_stopAdd) { + $_newFileContent.Add($line) + } + + $_ix++ + } + + return $_newFileContent +} + +Write-Host "Applying torizonPackages.json ..." + +# Dockerfile.debug +# The generic project doesn't have a Dockerfile.debug, so check if it exists +# before applying it +if (Test-Path -Path "Dockerfile.debug") { + Write-Host "Applying to Dockerfile.debug ..." + $debugDockerfile = _getFileLines "Dockerfile.debug" + + _ReplaceSection $debugDockerfile "torizon_packages_dev" ` + | Out-File -FilePath "Dockerfile.debug" + + Write-Host -ForegroundColor DarkGreen "✅ Dockerfile.debug" +} +# Dockerfile.sdk +# is not all templates that's need the Dockerfile.sdk +if (Test-Path -Path "Dockerfile.sdk") { + Write-Host "Applying to Dockerfile.sdk ..." + $debugDockerfileSDK = _getFileLines "Dockerfile.sdk" + $debugDockerfileSDK = ` + _ReplaceSection $debugDockerfileSDK "torizon_packages_prod" + _ReplaceSection $debugDockerfileSDK "torizon_packages_dev" ` + | Out-File -FilePath "Dockerfile.sdk" + Write-Host -ForegroundColor DarkGreen "✅ Dockerfile.sdk" +} + +# Dockerfile +Write-Host "Applying to Dockerfile ..." +$Dockerfile = _getFileLines "Dockerfile" +$Dockerfile = _ReplaceSection $Dockerfile "torizon_packages_prod" +_ReplaceSection $Dockerfile "torizon_packages_dev" ` + | Out-File -FilePath "Dockerfile" +Write-Host -ForegroundColor DarkGreen "✅ Dockerfile" + +Write-Host "torizonPackages.json applied" diff --git a/.conf/update.json b/.conf/update.json new file mode 100644 index 0000000..bcb9d63 --- /dev/null +++ b/.conf/update.json @@ -0,0 +1,10 @@ +[ + { + "source": "torizonWSLWelcome.csproj", + "target": "$projectName.csproj" + }, + { + "source": ".vscode/launch.json", + "target": ".vscode/launch.json" + } +] diff --git a/.conf/validateDepsRunning.ps1 b/.conf/validateDepsRunning.ps1 new file mode 100644 index 0000000..099006a --- /dev/null +++ b/.conf/validateDepsRunning.ps1 @@ -0,0 +1,71 @@ +# suppress warnings that we need to use +param() + +$env:DOCKER_HOST = "" + +if ($Env:GITLAB_CI -eq $true) { + Write-Host "ℹ️ :: GITLAB_CI :: ℹ️" + $Env:DOCKER_HOST = "tcp://docker:2375" +} + +$_envVarsSettings = @( + "TORIZON_PSSWD", + "TORIZON_LOGIN", + "HOST_IP", + "TORIZON_IP", + "TORIZON_ARCH" +) + +Write-Host -ForegroundColor DarkYellow ` + "`n⚠️ VALIDATING ENVIRONMENT`n" + +$_missingEnvVarSettings = $false + +# validate the environment variables +foreach ($var in $_envVarsSettings) { + if ((Test-Path "Env:$var") -eq $false -or (Get-Item "Env:$var").Value -eq "") { + # let's maintain this here to debug purposes + # but show to users will make more confusing + # the message must be that the default device is not set + # Write-Host -ForegroundColor DarkRed ` + # "❌ $var is not set" + $_missingEnvVarSettings = $true + } +} + +if ($_missingEnvVarSettings) { + Write-Host -ForegroundColor DarkRed ` + "❌ Missing settings.json properties, aborting`n" + Write-Host -ForegroundColor DarkYellow ` + "⚠️ Did you forget to set default device?" + Write-Host -ForegroundColor DarkYellow ` + "If you are facing issues even after setting default device, please remove the registered device and connect it again.`n" + + exit 69 +} + +# check if docker is running +docker info 2> $null | Out-Null +if (-not $?) { + Write-Host -ForegroundColor DarkRed ` + "❌ Docker is not running!`n" + Write-Host -ForegroundColor DarkRed ` + "⚠️ Please start Docker" + Write-Host -ForegroundColor DarkRed ` + "⚠️ Please make sure to reload the VS Code window after starting Docker" + + exit 69 +} + +# check if the docker container with name registry is running +if ($null -eq (docker ps -q -f name=registry)) { + Write-Host -ForegroundColor DarkRed ` + "❌ Docker container registry is not running!`n" + Write-Host -ForegroundColor DarkRed ` + "⚠️ Please make sure to reload the VS Code Window if you had initialization errors" + + exit 69 +} + +Write-Host -ForegroundColor DarkGreen ` + "`n✅ Environment is valid!`n" diff --git a/.doc/README.md b/.doc/README.md new file mode 100644 index 0000000..9e9ce9a --- /dev/null +++ b/.doc/README.md @@ -0,0 +1,14 @@ +# .NET 8 C# SlintDotnet Template Specific Documentation + + +> ⚠️ **WARNING:** This is just the documentation part specific of this template. **For the complete and general Torizon IDE documentation, check the [developer website documentation](https://developer.toradex.com/torizon/application-development/ide-extension/)** ⚠️ + +All projects follow the pipeline of tasks described in the [common contributing documentation](https://github.com/toradex/vscode-torizon-templates/blob/bookworm/CONTRIBUTING.md#contributing-templates). However, each project has its own specificities in terms of technologies and methods used to compile, deploy, and debug the code. Therefore, each of them has their own specific tasks in the **tasks.json** file. + +# Slint UI .NET + +> ⚠️ This is experimental and not ready for production use! + +Slint is a Rust based UI toolkit to build native user interfaces on desktop platforms and for embedded devices. This nuget package provides the .NET APIs to interact with the user interface implemented in Slint. + +The complete Rust documentation for Slint can be viewed online at https://slint.rs/docs/rust/slint/. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..91ac1b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +credentials.zip diff --git a/.github/workflows/build-application.yaml b/.github/workflows/build-application.yaml new file mode 100644 index 0000000..539968b --- /dev/null +++ b/.github/workflows/build-application.yaml @@ -0,0 +1,37 @@ +name: Build & Deploy TorizonCore +on: + - push + +jobs: + build-release: + runs-on: ubuntu-latest + name: Build & Release + + build-deploy: + runs-on: ubuntu-latest + name: Build & Deploy + steps: + - uses: actions/checkout@v3 + + - name: Build + shell: pwsh + run: | + ./.vscode/tasks.ps1 run build-debug-local + + - name: Build Release + shell: pwsh + run: | + ./.vscode/tasks.ps1 run publish-release-amd64 + + - name: Release + shell: pwsh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + $_tag = Get-Content -Path ./VERSION + zip -r "Torizon-WSL2-Welcome-$_tag.zip" ./bin/Release/net8.0/linux-x64/publish/ + gh release create ` + --target main $_tag ` + -t "Torizon WSL2 Welcome $_tag" ` + -n "Torizon WSL2 Welcome $_tag" ` + "Torizon-WSL2-Welcome-$_tag.zip" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1878440 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin +obj + +credentials.zip +*.lock.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..f5e10a1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,115 @@ +image: docker:latest + +services: + - docker:dind + +variables: + # This should be set by docker image already, just to be sure... + DOCKER_HOST: tcp://docker:2375 + # Use overlayfs driver for better performance + DOCKER_TLS_CERTDIR: "" + DOCKER_DRIVER: overlay2 + DOCKER_BUILDKIT: 1 + +stages: + - setup + - build + - platform + +initial-setup: + stage: setup + image: + name: torizonextras/pwsh-gitlab:latest + + variables: + TORIZON_ARCH: "" + + script: | + echo ' + + $_settings = (Get-Content ./.vscode/settings.json | ConvertFrom-Json) + $_torizonArch = $_settings.torizon_arch + if ($_torizonArch -eq "aarch64") { $_torizonArch = "arm64" } + if ($_torizonArch -eq "armhf") { $_torizonArch = "arm" } + "TORIZON_ARCH=$_torizonArch " | Set-Content -Path "setup.env" + + ./.vscode/tasks.ps1 run validate-pipeline-settings + + ' | gitlabWrapper.ps1 + + artifacts: + reports: + dotenv: setup.env + +build-docker-image: + needs: + - initial-setup + stage: build + image: + name: torizonextras/pwsh-gitlab:latest + + variables: + TASKS_ITERATIVE: "False" + TASKS_OVERRIDE_ENV: "False" + + script: | + echo ' + + ./.vscode/tasks.ps1 run run-torizon-binfmt + ./.vscode/tasks.ps1 run create-production-image + + ' | gitlabWrapper.ps1 + + artifacts: + paths: + - docker-compose.prod.yml + +push-package-to-platform: + needs: + - initial-setup + - build-docker-image + stage: platform + image: + name: torizonextras/pwsh-gitlab:latest + + variables: + TASKS_ITERATIVE: "False" + TASKS_OVERRIDE_ENV: "False" + + script: | + echo $PLATFORM_CREDENTIALS | base64 -d > credentials.zip + echo ' + + ./.vscode/tasks.ps1 run run-torizon-binfmt + ./.vscode/tasks.ps1 run tcb-platform-publish + + ' | gitlabWrapper.ps1 + + artifacts: + paths: + - docker-compose.prod.yml + - docker-compose.prod.lock.yml + when: on_success + +# trigger a deployment to the fleet only when pushing to main branch +update-fleet: + needs: + - initial-setup + - push-package-to-platform + stage: platform + rules: + - if: '$CI_COMMIT_BRANCH == "main"' + image: + name: torizonextras/pwsh-gitlab:latest + + variables: + TASKS_ITERATIVE: "False" + TASKS_OVERRIDE_ENV: "False" + + script: | + echo $PLATFORM_CREDENTIALS | base64 -d > credentials.zip + echo ' + + ./.vscode/tasks.ps1 run platform-update-fleet + + ' | gitlabWrapper.ps1 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..5b7a98f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-dotnettools.csharp" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..64ce113 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,111 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Slint Local", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-debug-local", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net8.0/linux-x64/torizonWSLWelcome.dll", + "args": [], + "env": { + "WAYLAND_DISPLAY": "" + }, + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Torizon AMD64", + "type": "coreclr", + "request": "launch", + "program": "${config:torizon_app_root}/torizonWSLWelcome", + "cwd": "${config:torizon_app_root}", + "stopAtEntry": false, + "console": "internalConsole", + "args": [], + "pipeTransport": { + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "ssh", + "pipeArgs": [ + "-T", + "-q", + "-p", + "${config:torizon_debug_ssh_port}", + "-i", + "${workspaceFolder}/.conf/id_rsa", // ssh key path + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile /dev/null", + "${config:torizon_run_as}@${config:torizon_ip}" // user@device + ], + "debuggerPath": "/vsdbg/vsdbg" + }, + "preLaunchTask": "deploy-torizon-amd64" + }, + { + "name": "Torizon ARMv7", + "type": "coreclr", + "request": "launch", + "program": "${config:torizon_app_root}/torizonWSLWelcome", + "cwd": "${config:torizon_app_root}", + "stopAtEntry": false, + "console": "internalConsole", + "args": [], + "pipeTransport": { + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "ssh", + "pipeArgs": [ + "-T", + "-q", + "-p", + "${config:torizon_debug_ssh_port}", + "-i", + "${workspaceFolder}/.conf/id_rsa", // ssh key path + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile /dev/null", + "${config:torizon_run_as}@${config:torizon_ip}" // user@device + ], + "debuggerPath": "/vsdbg/vsdbg" + }, + "preLaunchTask": "deploy-torizon-arm" + }, + { + "name": "Torizon ARMv8", + "type": "coreclr", + "request": "launch", + "program": "${config:torizon_app_root}/torizonWSLWelcome", + "cwd": "${config:torizon_app_root}", + "stopAtEntry": false, + "console": "internalConsole", + "args": [], + "pipeTransport": { + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "ssh", + "pipeArgs": [ + "-T", + "-q", + "-p", + "${config:torizon_debug_ssh_port}", + "-i", + "${workspaceFolder}/.conf/id_rsa", // ssh key path + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile /dev/null", + "${config:torizon_run_as}@${config:torizon_ip}" // user@device + ], + "debuggerPath": "/vsdbg/vsdbg" + }, + "preLaunchTask": "deploy-torizon-arm64" + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..928e247 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "torizon_psswd": "", + "torizon_login": "", + "torizon_ip": "", + "host_ip": "", + "torizon_workspace": "${workspaceFolder}", + "torizon_debug_port": "", + "torizon_debug_ssh_port": "2222", + "torizon_debug_port2": "", + "torizon_debug_port3": "", + "torizon_gpu": "", + "torizon_arch": "amd64", + "wait_sync": "1", + "torizon_run_as": "torizon", + "torizon_app_root": "/home/torizon/app", + "tcb.packageName": "torizonWSLWelcome", + "tcb.version": "3.8.1", + "torizon.gpuPrefixRC": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..687dc34 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,2259 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build-debug-local", + "detail": "dotnet build command for debugging the application locally", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-c", + "Debug" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "clean-debug-local", + "detail": "dotnet clean command for cleaning the local application debug build", + "command": "dotnet", + "type": "process", + "args": [ + "clean", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "-c", + "Debug" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "trash", + "color": "terminal.ansiYellow" + } + }, + { + "label": "clean-debug-arm", + "detail": "dotnet clean command for cleaning the application build for the armhf target", + "command": "dotnet", + "type": "process", + "args": [ + "clean", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "-c", + "Debug", + "-r", + "linux-arm" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "trash", + "color": "terminal.ansiYellow" + } + }, + { + "label": "clean-debug-arm64", + "detail": "dotnet clean command for cleaning the application build for the arm64 target", + "command": "dotnet", + "type": "process", + "args": [ + "clean", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "-c", + "Debug", + "-r", + "linux-arm64" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "trash", + "color": "terminal.ansiYellow" + } + }, + { + "label": "clean-debug-amd64", + "detail": "dotnet clean command for cleaning the application build for the amd64 target", + "command": "dotnet", + "type": "process", + "args": [ + "clean", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "-c", + "Debug", + "-r", + "linux-x64" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "trash", + "color": "terminal.ansiYellow" + } + }, + { + "label": "watch", + "hide": true, + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/torizonWSLWelcome.csproj" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "publish-debug-arm", + "detail": "", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "-c", + "Debug", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-r", + "linux-arm" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "deploy-torizon-arm", + "detail": "", + "hide": true, + "command": "sleep", + "type": "process", + "args": [ + "1" + ], + "dependsOn": [ + "validate-settings", + "validate-arch-arm", + "copy-docker-compose", + "pre-cleanup", + "publish-debug-arm", + "build-container-torizon-debug-arm", + "push-container-torizon-debug-arm", + "pull-container-torizon-debug-arm", + "run-container-torizon-debug-arm" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "publish-debug-amd64", + "detail": "", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "-c", + "Debug", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-r", + "linux-amd64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "publish-release-amd64", + "detail": "", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "-r", + "linux-amd64", + "--self-contained", + "true", + "-p:PublishSingleFile=true" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "deploy-torizon-amd64", + "detail": "", + "hide": true, + "command": "sleep", + "type": "process", + "args": [ + "1" + ], + "dependsOn": [ + "validate-settings", + "validate-arch-amd64", + "copy-docker-compose", + "pre-cleanup", + "publish-debug-amd64", + "build-container-torizon-debug-amd64", + "push-container-torizon-debug-amd64", + "pull-container-torizon-debug-amd64", + "run-container-torizon-debug-amd64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "publish-debug-arm64", + "detail": "", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "-c", + "Debug", + "${workspaceFolder}/torizonWSLWelcome.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-r", + "linux-arm64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "deploy-torizon-arm64", + "detail": "", + "hide": true, + "command": "sleep", + "type": "process", + "args": [ + "1" + ], + "dependsOn": [ + "validate-settings", + "validate-arch-arm64", + "copy-docker-compose", + "pre-cleanup", + "publish-debug-arm64", + "build-container-torizon-debug-arm64", + "push-container-torizon-debug-arm64", + "pull-container-torizon-debug-arm64", + "run-container-torizon-debug-arm64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "fetch-templates-repo", + "detail": "", + "hide": true, + "command": "echo", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "echo", + "${command:torizon.fetchTemplates}" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "git-branch", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + }, + { + "label": "try-update-template", + "detail": "This task has automation steps to update the project\nbased on the template used to create it.", + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/projectUpdater.ps1", + "${workspaceFolder}", + "${workspaceFolderBasename}", + "0" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "fetch-templates-repo" + ], + "icon": { + "id": "repo-sync", + "color": "terminal.ansiCyan" + } + }, + { + "label": "try-update-template-accepting-all", + "detail": "This task has automation steps to update the project\nbased on the template used to create it.", + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/projectUpdater.ps1", + "${workspaceFolder}", + "${workspaceFolderBasename}", + "1" + ], + "dependsOrder": "sequence", + "icon": { + "id": "repo-sync", + "color": "terminal.ansiCyan" + } + }, + { + "label": "create-production-image", + "detail": "This task has automation steps to build the production\nDocker image. The production Docker image\nis based in the Dockerfile file.", + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": false, + "group": "create-prod-image" + }, + "options": { + "env": { + "DOCKER_PSSWD": "${command:docker_password}", + "TORIZON_ARCH": "${input:archList}", + "APP_ROOT": "${config:torizon_app_root}" + } + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/createDockerComposeProduction.ps1", + "${workspaceFolder}", + "${command:docker_login}", + "${command:docker_tag}", + "${command:docker_registry}", + "torizon-wsl-welcome", + "${config:torizon_gpu}" + ], + "dependsOrder": "sequence", + "icon": { + "id": "package", + "color": "terminal.ansiCyan" + } + }, + { + "label": "set-platform-fleet-name", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "echo", + "\"setting", + "${command:tcb.fleetName}\"" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + }, + { + "label": "fill-pipeline-settings", + "detail": "This task will fill the properties that are needed to be\nset in the .vscode/settings.json to correctly\nrun the GitHub Actions or GitLab CI pipelines.", + "command": "echo", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "DOCKER_REGISTRY": "${command:docker_registry}", + "DOCKER_LOGIN": "${command:docker_login}", + "DOCKER_PSSWD": "${command:docker_password}", + "DOCKER_TAG": "${command:docker_tag}", + "TCB_CLIENTID": "${command:tcb.clientId}", + "TCB_CLIENTSECRET": "${command:tcb.clientSecret}", + "TCB_PACKAGE": "torizonWSLWelcome", + "TCB_FLEET": "${command:tcb.fleetName}", + "TORIZON_ARCH": "${command:torizon_arch}" + } + }, + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "args": [ + "-e", + "\"✅ github-actions-settings done\n", + "\n", + "'⚠️ ENV VARIABLES NEED TO BE SET IN CI/CD PLATFORM'\n", + "\tDOCKER_PSSWD\n", + "\tPLATFORM_CLIENT_ID\n", + "\tPLATFORM_CLIENT_SECRET\n", + "\tPLATFORM_CREDENTIALS\n\"" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "notebook", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + }, + { + "label": "platform-update-fleet", + "detail": "This task will trigger the update of the fleet in the\nTorizon Platform with the latest version\nof the application published to the platform.", + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PLATFORM_CLIENT_ID": "${command:tcb.clientId}", + "PLATFORM_CLIENT_SECRET": "${command:tcb.clientSecret}" + } + }, + "args": [ + "pwsh", + "-nop", + "./.conf/torizonIO.ps1", + "update", + "fleet", + "latest", + "torizonWSLWelcome", + "${command:tcb.fleetName}" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "cloud-upload", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + }, + { + "label": "tcb-platform-publish", + "detail": "This task will build the production image based on the\nDockerfile and push it to the Torizon\nPlatform creating a new docker-compose package.", + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "DOCKER_PSSWD": "${command:docker_password}", + "DOCKER_LOGIN": "${command:docker_login}", + "DOCKER_REGISTRY": "${command:docker_registry}", + "VSCODE_CMD": "--verbose platform push --credentials credentials.zip --package-name torizonWSLWelcome --package-version ${command:tcb.getNextPackageVersion} --login-to $DOCKER_REGISTRY $DOCKER_LOGIN $DOCKER_PSSWD --canonicalize docker-compose.prod.yml" + } + }, + "args": [ + "source", + "./.conf/tcb-env-setup.sh", + "-s", + "${workspaceFolder}/storage", + "-t", + "${config:tcb.version}" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "cloud-upload", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [ + "create-production-image" + ] + }, + { + "label": "docker-login", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "${command:docker_login}", + "DOCKER_PSSWD": "${command:docker_password}" + } + }, + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "args": [ + "docker", + "login", + "--username", + "${command:docker_login}", + "--password", + "${command:docker_password}" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "run-docker-registry", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "args": [ + "-nop", + "-file", + "${workspaceFolder}/.conf/runContainerIfNotExists.ps1", + "-ContainerRuntime", + "docker", + "-RunArguments", + "\"-d -p 5002:5000 --restart=always registry:2\"", + "-ContainerName", + "registry" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "run-docker-registry-wsl", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "options": { + "env": { + "TASKS_USE_PWSH_INSTEAD_BASH": "true" + } + }, + "args": [ + "-nop", + "-file", + "${workspaceFolder}/.vscode/tasks.ps1", + "run", + "run-docker-registry" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "run-torizon-binfmt", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "args": [ + "${workspaceFolder}/.conf/runContainerIfNotExists.ps1", + "-ContainerRuntime", + "docker", + "-RunArguments", + "--rm --privileged torizon/binfmt", + "-ContainerName", + "binfmt" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "run-share-wsl-ports", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/shareWSLPorts.ps1", + "${workspaceFolder}" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "run-share-wsl-ports-debug", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "options": { + "env": { + "DEBUG_SHARED_PORTS": "true" + } + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/shareWSLPorts.ps1", + "${workspaceFolder}" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "check-deps", + "detail": "This task will check if the dependencies needed to run\nthe project are installed in the host machine.", + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/checkDeps.ps1" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "apply-torizon-packages", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "args": [ + "-nop", + "${workspaceFolder}/.conf/torizonPackages.ps1", + "${config:torizon_arch}" + ], + "dependsOrder": "sequence", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "copy-docker-compose", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "scp", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "${command:torizon_workspace}/docker-compose.yml", + "torizon@${config:torizon_ip}:~/" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "wait-a-bit", + "detail": "", + "hide": true, + "command": "sleep", + "type": "process", + "args": [ + "${config:wait_sync}" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "pos-cleanup", + "detail": "", + "hide": true, + "command": "ssh", + "type": "process", + "args": [ + "-i", + "${workspaceFolder}/.conf/id_rsa", + "-p", + "${config:torizon_debug_ssh_port}", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "${config:torizon_run_as}@${config:torizon_ip}", + "rm -rf ~/app" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "validate-settings", + "detail": "", + "hide": true, + "command": "bash", + "type": "process", + "args": [ + "-c", + "[[ ! -z \"${config:torizon_ip}\" ]] && true || false" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + }, + "dependsOn": [ + "validate-deps-running" + ] + }, + { + "label": "validate-arch-amd64", + "detail": "", + "hide": true, + "command": "bash", + "type": "process", + "args": [ + "-c", + "[[ \"${config:torizon_arch}\" == \"x86_64\" ]] && true || false" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "validate-arch-arm", + "detail": "", + "hide": true, + "command": "bash", + "type": "process", + "args": [ + "-c", + "[[ \"${config:torizon_arch}\" == \"armv7l\" ]] && true || false" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "validate-arch-arm64", + "detail": "", + "hide": true, + "command": "bash", + "type": "process", + "args": [ + "-c", + "[[ \"${config:torizon_arch}\" == \"aarch64\" ]] && true || false" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "validate-arch-riscv64", + "detail": "", + "hide": true, + "command": "bash", + "type": "process", + "args": [ + "-c", + "[[ \"${config:torizon_arch}\" == \"riscv64\" ]] && true || false" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "layers", + "color": "terminal.ansiCyan" + } + }, + { + "label": "pre-cleanup", + "detail": "", + "hide": true, + "command": "if", + "type": "shell", + "args": [ + "[", + "${command:cleanLock}", + "==", + "false", + "];", + "then", + "DOCKER_HOST=${config:torizon_ip}:2375", + "docker", + "compose", + "-p", + "torizon", + "down", + "--remove-orphans", + ";", + "fi" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "remove-dangling-images", + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "remove-dangling-images", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "docker", + "image", + "prune", + "-f", + "--filter=dangling=true" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-debug-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "arm64", + "GPU": "${config:torizon_gpu}", + "SSH_DEBUG_PORT": "${config:torizon_debug_ssh_port}", + "DEBUG_PORT": "${config:torizon_debug_port}", + "DEBUG_PORT2": "${config:torizon_debug_port2}", + "DEBUG_PORT3": "${config:torizon_debug_port3}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=arm64", + "--build-arg", + "SSH_DEBUG_PORT=${config:torizon_debug_ssh_port}", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-debug-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "arm", + "GPU": "${config:torizon_gpu}", + "SSH_DEBUG_PORT": "${config:torizon_debug_ssh_port}", + "DEBUG_PORT": "${config:torizon_debug_port}", + "DEBUG_PORT2": "${config:torizon_debug_port2}", + "DEBUG_PORT3": "${config:torizon_debug_port3}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=arm", + "--build-arg", + "SSH_DEBUG_PORT=${config:torizon_debug_ssh_port}", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-debug-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "amd64", + "GPU": "${config:torizon_gpu}", + "SSH_DEBUG_PORT": "${config:torizon_debug_ssh_port}", + "DEBUG_PORT": "${config:torizon_debug_port}", + "DEBUG_PORT2": "${config:torizon_debug_port2}", + "DEBUG_PORT3": "${config:torizon_debug_port3}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=amd64", + "--build-arg", + "SSH_DEBUG_PORT=${config:torizon_debug_ssh_port}", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-debug-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "riscv64", + "GPU": "${config:torizon_gpu}", + "SSH_DEBUG_PORT": "${config:torizon_debug_ssh_port}", + "DEBUG_PORT": "${config:torizon_debug_port}", + "DEBUG_PORT2": "${config:torizon_debug_port2}", + "DEBUG_PORT3": "${config:torizon_debug_port3}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=riscv64", + "--build-arg", + "SSH_DEBUG_PORT=${config:torizon_debug_ssh_port}", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-image-sdk-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "args": [ + "docker", + "build", + "--pull", + "-f", + "${workspaceFolder}/Dockerfile.sdk", + "${workspaceFolder}", + "-t", + "cross-toolchain-arm64-torizon-wsl-welcome", + "--build-arg", + "IMAGE_ARCH=arm64", + "--build-arg", + "GPU=${config:torizon_gpu}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": [ + "$gcc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-image-sdk-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "args": [ + "docker", + "build", + "--pull", + "-f", + "${workspaceFolder}/Dockerfile.sdk", + "${workspaceFolder}", + "-t", + "cross-toolchain-arm-torizon-wsl-welcome", + "--build-arg", + "IMAGE_ARCH=arm", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": [ + "$gcc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-image-sdk-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "args": [ + "docker", + "build", + "--pull", + "-f", + "${workspaceFolder}/Dockerfile.sdk", + "${workspaceFolder}", + "-t", + "cross-toolchain-amd64-torizon-wsl-welcome", + "--build-arg", + "IMAGE_ARCH=amd64", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": [ + "$gcc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-image-sdk-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "args": [ + "docker", + "build", + "--pull", + "-f", + "${workspaceFolder}/Dockerfile.sdk", + "${workspaceFolder}", + "-t", + "cross-toolchain-riscv64-torizon-wsl-welcome", + "--build-arg", + "IMAGE_ARCH=riscv64", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": [ + "$gcc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-debug-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "arm" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-debug-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "arm64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-debug-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "amd64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-debug-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "LOCAL_REGISTRY": "localhost", + "TAG": "riscv64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-debug-arm64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "LOCAL_REGISTRY=${config:host_ip} TAG=arm64 docker compose pull torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-debug-arm", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "LOCAL_REGISTRY=${config:host_ip} TAG=arm docker compose pull torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-debug-amd64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "LOCAL_REGISTRY=${config:host_ip} TAG=amd64 docker compose pull torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-debug-riscv64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "LOCAL_REGISTRY=${config:host_ip} TAG=riscv64 docker compose pull torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-debug-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "LOCAL_REGISTRY=${config:host_ip}", + "TAG=arm64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "-d", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-debug-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "LOCAL_REGISTRY=${config:host_ip}", + "TAG=arm", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "-d", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-debug-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "LOCAL_REGISTRY=${config:host_ip}", + "TAG=amd64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "-d", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-debug-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "LOCAL_REGISTRY=${config:host_ip}", + "TAG=riscv64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "-d", + "torizon-wsl-welcome-debug" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-release-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "arm64", + "GPU": "${config:torizon_gpu}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=arm64", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-release-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "arm", + "GPU": "${config:torizon_gpu}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=arm", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-release-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "amd64", + "GPU": "${config:torizon_gpu}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=amd64", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "build-container-torizon-release-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "riscv64", + "GPU": "${config:torizon_gpu}" + } + }, + "args": [ + "docker", + "compose", + "build", + "--pull", + "--build-arg", + "SSHUSERNAME=${config:torizon_run_as}", + "--build-arg", + "APP_ROOT=${config:torizon_app_root}", + "--build-arg", + "IMAGE_ARCH=riscv64", + "--build-arg", + "GPU=${config:torizon_gpu}", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "apply-torizon-packages" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-release-arm64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "arm64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome" + ], + "dependsOn": [ + "build-container-torizon-release-arm64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "repo-push", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-release-arm", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "arm" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome" + ], + "dependsOn": [ + "build-container-torizon-release-arm" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "repo-push", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-release-amd64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "amd64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome" + ], + "dependsOn": [ + "build-container-torizon-release-amd64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "repo-push", + "color": "terminal.ansiYellow" + } + }, + { + "label": "push-container-torizon-release-riscv64", + "detail": "", + "hide": true, + "command": "DOCKER_HOST=", + "type": "shell", + "options": { + "env": { + "DOCKER_LOGIN": "localhost:5002", + "TAG": "riscv64" + } + }, + "args": [ + "docker", + "compose", + "push", + "torizon-wsl-welcome" + ], + "dependsOn": [ + "build-container-torizon-release-riscv64" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "repo-push", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-release-arm64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "DOCKER_LOGIN=${config:host_ip}:5002 TAG=arm64 docker compose pull torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "push-container-torizon-release-arm64" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-release-arm", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "DOCKER_LOGIN=${config:host_ip}:5002 TAG=arm docker compose pull torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "push-container-torizon-release-arm" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-release-amd64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "DOCKER_LOGIN=${config:host_ip}:5002 TAG=amd64 docker compose pull torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "push-container-torizon-release-amd64" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "pull-container-torizon-release-riscv64", + "detail": "", + "hide": true, + "command": "sshpass", + "type": "process", + "args": [ + "-p", + "${config:torizon_psswd}", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + "torizon@${config:torizon_ip}", + "DOCKER_LOGIN=${config:host_ip}:5002 TAG=riscv64 docker compose pull torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "push-container-torizon-release-riscv64" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-release-arm64", + "detail": "This task will build the application based in the production Dockerfile, push it to the local registry (localhost:5002), pull it on the board and run it on the board, showing the result in the VSCode terminal", + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "DOCKER_LOGIN=${config:host_ip}:5002", + "TAG=arm64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "validate-settings", + "validate-arch-arm64", + "copy-docker-compose", + "pre-cleanup", + "build-container-torizon-release-arm64", + "push-container-torizon-release-arm64", + "pull-container-torizon-release-arm64", + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "run", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-release-arm", + "detail": "This task will build the application based in the production Dockerfile, push it to the local registry (localhost:5002), pull it on the board and run it on the board, showing the result in the VSCode terminal", + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "DOCKER_LOGIN=${config:host_ip}:5002", + "TAG=arm", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "validate-settings", + "validate-arch-arm", + "copy-docker-compose", + "pre-cleanup", + "build-container-torizon-release-arm", + "push-container-torizon-release-arm", + "pull-container-torizon-release-arm", + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "run", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-release-amd64", + "detail": "This task will build the application based in the production Dockerfile, push it to the local registry (localhost:5002), pull it on the board and run it on the board, showing the result in the VSCode terminal", + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "DOCKER_LOGIN=${config:host_ip}:5002", + "TAG=amd64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "validate-settings", + "validate-arch-amd64", + "copy-docker-compose", + "pre-cleanup", + "build-container-torizon-release-amd64", + "push-container-torizon-release-amd64", + "pull-container-torizon-release-amd64", + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "run", + "color": "terminal.ansiYellow" + } + }, + { + "label": "run-container-torizon-release-riscv64", + "detail": "This task will build the application based in the production Dockerfile, push it to the local registry (localhost:5002), pull it on the board and run it on the board, showing the result in the VSCode terminal", + "command": "DOCKER_HOST=${config:torizon_ip}:2375", + "type": "shell", + "args": [ + "DOCKER_LOGIN=${config:host_ip}:5002", + "TAG=riscv64", + "GPU=${config:torizon_gpu}", + "docker", + "compose", + "-p", + "torizon", + "up", + "torizon-wsl-welcome" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "validate-settings", + "validate-arch-riscv64", + "copy-docker-compose", + "pre-cleanup", + "build-container-torizon-release-riscv64", + "push-container-torizon-release-riscv64", + "pull-container-torizon-release-riscv64", + "wait-a-bit" + ], + "problemMatcher": "$msCompile", + "icon": { + "id": "run", + "color": "terminal.ansiYellow" + } + }, + { + "label": "show-project-documentation", + "detail": "This task will open the project specific documentation\nin a new tab.", + "command": "${command:markdown.showPreview}", + "type": "process", + "presentation": { + "echo": false, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "dependsOn": [ + "open-project-documentation-file" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "book", + "color": "terminal.ansiYellow" + } + }, + { + "label": "open-project-documentation-file", + "command": "code", + "detail": "", + "hide": true, + "type": "process", + "presentation": { + "echo": false, + "reveal": "never", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + "args": [ + ".doc/README.md" + ], + "dependsOrder": "sequence", + "problemMatcher": "$msCompile", + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + } + }, + { + "label": "validate-pipeline-settings", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "DOCKER_REGISTRY": "${command:docker_registry}", + "DOCKER_LOGIN": "${command:docker_login}", + "DOCKER_PSSWD": "${command:docker_password}", + "DOCKER_TAG": "${command:docker_tag}", + "TCB_CLIENTID": "${command:tcb.clientId}", + "TCB_CLIENTSECRET": "${command:tcb.clientSecret}", + "TCB_PACKAGE": "torizonWSLWelcome", + "TCB_FLEET": "${command:tcb.fleetName}", + "TORIZON_ARCH": "${command:torizon_arch}" + } + }, + "args": [ + "-nop", + ".conf/checkCIEnv.ps1" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + }, + { + "label": "validate-deps-running", + "detail": "", + "hide": true, + "command": "pwsh", + "type": "process", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "TORIZON_PSSWD": "${config:torizon_psswd}", + "TORIZON_LOGIN": "${config:torizon_login}", + "HOST_IP": "${config:host_ip}", + "TORIZON_IP": "${config:torizon_ip}", + "TORIZON_ARCH": "${config:torizon_arch}", + "TORIZON_GPU": "${config:torizon_gpu}" + } + }, + "args": [ + "-nop", + ".conf/validateDepsRunning.ps1" + ], + "problemMatcher": [ + "$tsc" + ], + "icon": { + "id": "flame", + "color": "terminal.ansiYellow" + }, + "dependsOrder": "sequence", + "dependsOn": [] + } + ], + "inputs": [ + { + "id": "dockerLogin", + "type": "promptString", + "description": "Container Registry Login" + }, + { + "id": "dockerImageRegistry", + "type": "promptString", + "description": "Image Registry" + }, + { + "id": "dockerImageTag", + "type": "promptString", + "description": "Image Tag" + }, + { + "id": "dockerImageGpu", + "type": "promptString", + "description": "Image GPU" + }, + { + "id": "dockerPsswd", + "type": "promptString", + "password": true, + "description": "Container Registry Password" + }, + { + "id": "archList", + "type": "pickString", + "description": "Container architecture", + "options": [ + "arm", + "arm64", + "amd64", + "riscv64" + ] + } + ] +} diff --git a/.vscode/tasks.ps1 b/.vscode/tasks.ps1 new file mode 100644 index 0000000..213cd88 --- /dev/null +++ b/.vscode/tasks.ps1 @@ -0,0 +1,696 @@ +# suppress warnings that we need to use +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidOverwritingBuiltInCmdlets', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingInvokeExpression', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingPositionalParameters', "" +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidGlobalVars', "" +)] +param() + +$ErrorActionPreference = "Stop" +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', "Internal PS variable" +)] +$PSNativeCommandUseErrorActionPreference = $true + +function _usage ($_fdp = 1) { + Write-Host "usage:" + Write-Host " list : list the tasks.json labels defined" + Write-Host " desc : describe the task " + Write-Host " desc : describe the task " + Write-Host " run : run the task " + Write-Host " run : run the task " + Write-Host " run-nodeps : run the tasks without dependencies " + + if ($_fdp -eq 0) { + Write-Host -ForegroundColor Yellow "" + Write-Host -ForegroundColor Yellow "⚠️ :: WARNING :: ⚠️" + Write-Host -ForegroundColor Yellow "This script depends on tasks.json and settings.json" + Write-Host -ForegroundColor Yellow "These files need to be in the same directory as this script." + Write-Host -ForegroundColor Yellow "" + } + + exit 0 +} + +# settings +$_overrideEnv = $true; +$_debug = $false; +$_gitlab_ci = $false + +if ($Env:GITLAB_CI -eq $true) { + Write-Host "ℹ️ :: GITLAB_CI :: ℹ️" + # for gitlab-ci we need to set the docker host + $Env:DOCKER_HOST = "tcp://docker:2375" + $_gitlab_ci = $true +} + +if ($env:TASKS_DEBUG -eq $true) { + $_debug = $true; +} + +if ($env:TASKS_OVERRIDE_ENV -eq $false) { + $_overrideEnv = $false; +} + +if ($env:TASKS_USE_PWSH_INSTEAD_BASH -eq $true) { + $_usePwshInsteadBash = $true; +} else { + $_usePwshInsteadBash = $false; +} + +try { + $tasksFileContent = Get-Content $PSScriptRoot/tasks.json + $settingsFileContent = Get-Content $PSScriptRoot/settings.json + $json = $tasksFileContent | ConvertFrom-Json + $settings = $settingsFileContent | ConvertFrom-Json + $inputs = $json.inputs + $inputValues = @{} + $cliInputs = [System.Collections.ArrayList]@() + $runDeps = $true; +} catch { + _usage 0 +} + +function settingsToGlobal () { + foreach ($set in $settings | Get-Member -MemberType Properties) { + if (-not (Test-Path "variable:Global:$($set.Name)")) { + New-Variable ` + -Scope Global ` + -Name "config:$($set.Name)" -Value $settings.($set.Name) + } else { + Set-Variable ` + -Scope Global ` + -Name "config:$($set.Name)" -Value $settings.($set.Name) + } + } +} + +function write-error () { + Write-Host -ForegroundColor Red ` + $args[0] + exit $args[1] +} + +function getTasksLabels () { + $labels = [System.Collections.ArrayList]@() + + for ($i = 0; $i -le $json.tasks.length; $i++) { + [void]$labels.Add($json.tasks[$i].label) + } + + return $labels +} + +function listTasksLabel () { + $_noIndex = $false + + if ($null -ne $args[0]) { + if ($args[0] -eq "--no-index") { + $_noIndex = $true + } + } + + for ($i = 0; $i -le $json.tasks.length; $i++) { + if ($null -ne $json.tasks[$i].label) { + if ($_noIndex -eq $false) { + Write-Host -NoNewline "$($i + 1).`t" + } + Write-Host $json.tasks[$i].label + } + } +} + +function checkInput () { + $ret = [System.Collections.ArrayList]@() + + foreach ($arg in $args[0]) { + if (-not $arg.Contains("`${input:")) { + [void]$ret.Add($arg) + } else { + $maches = ($arg | + Select-String ` + -Pattern "(?<=\`${input:).*?(?=\s*})" ` + -AllMatches + ).Matches + + foreach ($matchValue in $maches) { + $inputObj = $null + + foreach ($inp in $inputs) { + if ($inp.id -eq $matchValue.Value) { + $inputObj = $inp + } + } + + $fromUser = $null + if ($inputValues.ContainsKey($matchValue.Value)) { + $fromUser = $inputValues[$matchValue.Value] + } else { + $desc = $inputObj.description + $default = $inputObj.default + + if ($cliInputs.Count -gt 0) { + $fromUser = $cliInputs[0] + $cliInputs.RemoveAt(0) + } + + # cli input is nothing + if ($null -eq $fromUser) { + if ($inputObj.password -eq $true) { + $fromUser = Read-Host ` + -AsSecureString ` + -Prompt "$desc [***]" + + # TODO: so much security wow + $fromUser = ConvertFrom-SecureString ` + -SecureString $fromUser ` + -AsPlainText + } else { + $fromUser = Read-Host ` + -Prompt "$desc [$default]" + } + } + + if ($fromUser -eq [String]::Empty) { + $fromUser = $default + } + + $inputValues.Add($matchValue.Value, $fromUser) + } + + $matchValue = $matchValue.Value + $arg = $arg.Replace("`${input:${matchValue}}", $fromUser) + } + + [void]$ret.Add($arg) + } + } + + return $ret +} + +# TODO: refactor to be an generic prefix check +function checkTorizonInputs ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + if ($item.Contains("`${command:torizon_")) { + + $maches = ($item | + Select-String ` + -Pattern "(?<=\`${command:torizon_).*?(?=\s*})" ` + -AllMatches + ).Matches + + foreach ($matchValue in $maches) { + $matchValue = $matchValue.Value + $item = $item.Replace( + "`${command:torizon_${matchValue}}", + "`${config:torizon_${matchValue}}" + ) + } + } + + [void]$ret.Add($item) + } + + return $ret +} + +function checkDockerInputs ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + if ($item.Contains("`${command:docker_")) { + + $maches = ($item | + Select-String ` + -Pattern "(?<=\`${command:docker_).*?(?=\s*})" ` + -AllMatches + ).Matches + + foreach ($matchValue in $maches) { + $matchValue = $matchValue.Value + $item = $item.Replace( + "`${command:docker_${matchValue}}", + "`${config:docker_${matchValue}}" + ) + } + } + + [void]$ret.Add($item) + } + + return $ret +} + +function checkTCBInputs ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + if ($item.Contains("`${command:tcb")) { + + if ($item.Contains("tcb.getNextPackageVersion")) { + $_ret = ( + ./.conf/torizonIO.ps1 ` + package latest version ${global:config:tcb.packageName} + ) + $_next = [System.Int32]::Parse($_ret) +1 + + if ($_debug) { + Write-Host -ForegroundColor Green ` + "Next package version: $_next" + } + + $item = $item.Replace( + "`${command:tcb.getNextPackageVersion}", + "$_next" + ) + } + + $maches = ($item | + Select-String ` + -Pattern "(?<=\`${command:tcb.).*?(?=\s*})" ` + -AllMatches + ).Matches + + foreach ($matchValue in $maches) { + $matchValue = $matchValue.Value + $item = $item.Replace( + "`${command:tcb.${matchValue}}", + "`${config:tcb.${matchValue}}" + ) + } + } + + [void]$ret.Add($item) + } + + return $ret +} + +# check if the string contains special characters +function _containsSpecialChars ([String] $str) { + $ret = $false + + if ( + $str -match "[^a-zA-Z0-9\.\-_]" + ) { + $ret = $true + } + + return $ret +} + +function scapeArgs ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + # for now only scaping double quotes + foreach ($item in $list) { + if ($item.Contains("`"")) { + $item = $item.Replace("`"", "```"") + } + + [void]$ret.Add($item) + } + + return $ret +} + +function checkConfig ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + # TODO: Add variable expand recursive + if ($item.Contains("config:")) { + $item = $item.Replace("config:", "global:config:") + + $value = Invoke-Expression "echo `"$item`"" + + if ($null -ne $value -and $value.Contains("`${workspaceFolder")) { + $item = $value + } + } + + [void]$ret.Add($item) + } + + return $ret +} + +function checkLongArgs ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + if ($item.Contains(" ")) { + $item = "'$item'" + } + + [void]$ret.Add($item) + } + + return $ret +} + +## +# If the user is using bash as default shell, we need to scape the $ special +# characters, because powershell will try to expand the variables before it +# reach the bash shell +## +function bashVariables ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + if ($item.Contains("$")) { + + # if $env + # if ${} + # if $global + # then we continue because these are meant to be expanded + if ( + $item.Contains("`$global:") -or + $item.Contains("`$env:") -or + $item.Contains("`${") + ) { + [void]$ret.Add($item) + continue + } + + # ok, we can scape it + $item = $item.Replace("`$", "``$") + [void]$ret.Add($item) + } else { + [void]$ret.Add($item) + } + } + + return $ret +} + +function quotingSpecialChars ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + $_specialChar = _containsSpecialChars($item) + $_space = $item.Contains(" ") + + if ($_specialChar -and -not $_space) { + $item = "'$item'" + } + + [void]$ret.Add($item) + } + + return $ret +} + +function checkWorkspaceFolder ([System.Collections.ArrayList] $list) { + $ret = [System.Collections.ArrayList]@() + + foreach ($item in $list) { + # TODO: Add variable expand recursive + if ($item.Contains("workspaceFolder")) { + $item = $item.Replace("workspaceFolder", "global:workspaceFolder") + + $value = Invoke-Expression "echo $item" + + if ($value.Contains("`${workspaceFolder")) { + $item = $value + } + } + + [void]$ret.Add($item) + } + + return $ret +} + +function taskArgumentExecute ($label, [ScriptBlock]$fnExec, $message) { + if ($null -eq $label -or $label -eq [String]::Empty) { + write-error $message 10 + } else { + $taskLabel = $label + + if ($taskLabel -match "^\d+$") { + Invoke-Command -ScriptBlock $fnExec ` + -ArgumentList $json.tasks[[int]::Parse($taskLabel) -1].label + } elseif ((getTasksLabels).Contains($taskLabel)) { + Invoke-Command -ScriptBlock $fnExec ` + -ArgumentList $taskLabel + } else { + write-error "Undefined task <$taskLabel>" 10 + } + } +} + +function descTask () { + for ($i = 0; $i -le $json.tasks.length; $i++) { + if ($json.tasks[$i].label -eq $args[0]) { + $task = $json.tasks[$i] + $task | ConvertTo-Json + } + } +} + +function _parseEnvs () { + $env = $args[0] + $task = $args[1] + + $value = $task.options.env + | Select-Object -ExpandProperty $env + + $expValue = checkWorkspaceFolder($value) + $expValue = checkTorizonInputs($expValue) + $expValue = checkDockerInputs($expValue) + $expValue = checkTCBInputs($expValue) + $expValue = checkInput($expValue) + $expValue = checkConfig($expValue) + $expValue = bashVariables($expValue) + $expValue = $expValue.ToString() + $_env = Invoke-Expression "echo `"$expValue`"" + + if ($_debug -eq $true) { + Write-Host -ForegroundColor Yellow ` + "Env: $env=$expValue" + Write-Host -ForegroundColor Yellow ` + "Parsed Env: $env=$_env" + } + + return $_env +} + +function _replaceDockerHost () { + $value = $args[0] + + if ($value -match "DOCKER_HOST=") { + $value = $value.Replace("DOCKER_HOST=", "DOCKER_HOST=tcp://docker:2375") + } + + return $value +} + +function runTask () { + for ($i = 0; $i -le $json.tasks.length; $i++) { + if ($json.tasks[$i].label -eq $args[0]) { + $task = $json.tasks[$i] + $taskCmd = $task.command + $taskArgs = scapeArgs($task.args) + $taskArgs = checkWorkspaceFolder($taskArgs) + $taskArgs = checkTorizonInputs($taskArgs) + $taskArgs = checkDockerInputs($taskArgs) + $taskArgs = checkTCBInputs($taskArgs) + $taskArgs = checkInput($taskArgs) + $taskArgs = checkConfig($taskArgs) + $taskArgs = checkLongArgs($taskArgs) + $taskArgs = bashVariables($taskArgs) + $taskArgs = quotingSpecialChars($taskArgs) + $taskDepends = $task.dependsOn + $taskEnv = $task.options.env + $taskCwd = $task.options.cwd + + $isBackground = "" + if ($task.isBackground -eq $true) { + $isBackground = " &" + } + + # FIXME: if using powershell instead bash the background will start + # a new job, this is was not been well tested + # is gitlab ci + if ($_gitlab_ci -eq $true) { + $taskCmd = _replaceDockerHost($taskCmd) + } + + # inject env + if ($null -ne $taskEnv) { + $envs = $taskEnv + | get-member -MemberType NoteProperty + | Select-Object -ExpandProperty Name + + foreach ($env in $envs) { + if ($_overrideEnv) { + $_env = _parseEnvs $env $task + [System.Environment]::SetEnvironmentVariable( + $env, $_env + ) + } else { + if ( + $null -eq + [System.Environment]::GetEnvironmentVariable($env) + ) { + $_env = _parseEnvs $env $task + [System.Environment]::SetEnvironmentVariable( + $env, $_env + ) + } + } + } + } + + # run dependencies + if ($runDeps -eq $true) { + for ($j = 0; $j -lt $taskDepends.Count; $j++) { + runTask $taskDepends[$j] + } + } + + Write-Host -ForegroundColor Green ` + "> Executing task: $($json.tasks[$i].label) <" + + # we need to change dir if we are setting cwd + if ($null -ne $taskCwd) { + # store the current location + $_cwd = Get-Location + + # we use invoke-expression because this way it expand the + # variables automatically + Invoke-Expression "Set-Location $taskCwd" + } + + # parse the variables + $_cmd = Invoke-Expression "echo `"$taskCmd $taskArgs $isBackground`"" + + if ($env:TASKS_DEBUG -eq $true) { + Write-Host -ForegroundColor Yellow ` + "Command: $taskCmd" + Write-Host -ForegroundColor Yellow ` + "Args: $taskArgs" + Write-Host -ForegroundColor Yellow ` + "Parsed Command: $_cmd" + } + + # all to global + # we are spawning a new process, so we need to set all the envs + # as global, so the new process can see it + # this is useful when the user set bash variables + $_ALLENV = $(Get-ChildItem env:) + foreach ($_env in $_ALLENV) { + try { + # set it as a $Global: + Set-Variable ` + -Scope Global ` + -Name $_env.Name -Value $_env.Value + } catch { + # ignore + # some variables are not overwrite + } + } + + # execute the task + if ($task.type -eq "shell") { + if ($_usePwshInsteadBash -eq $false) { + # use bash as default + # TODO: be explicit about bash as default on documentation + Invoke-Expression "bash -c `"$_cmd`"" + } else { + Invoke-Expression "pwsh -nop -c `"$_cmd`"" + } + } else { + Invoke-Expression $_cmd + } + + $exitCode = $LASTEXITCODE + + # go back to the origin location + if ($null -ne $taskCwd) { + # restore the current location + Set-Location $_cwd + } + + # abort we had an error + if ($exitCode -ne 0) { + Write-Host -ForegroundColor Red ` + "> TASK $($json.tasks[$i].label) exited with error code $($exitCode) <" + exit $exitCode + } + } + } +} + +function getCliInputs () { + $argsS = $args[0] + # args[0] command / args[1] task name + for ($i = 2; $i -lt $argsS.Length; $i++) { + # inputs + [void]$cliInputs.Add($argsS[$i]) + } +} + +# main() +# set the relative workspaceFolder (following the pattern that VS Code expects) +if ( + ($null -eq $env:APOLLOX_WORKSPACE) -and + ($env:APOLLOX_CONTAINER -ne 1) +) { + $Global:workspaceFolder = Join-Path $PSScriptRoot .. +} else { + $Global:workspaceFolder = $env:APOLLOX_WORKSPACE +} + +settingsToGlobal + +try { + switch ($args[0]) { + "list" { + listTasksLabel $args[1] + } + "desc" { + taskArgumentExecute ` + $args[1] ${function:descTask} "Argument expected desc " + } + "run" { + getCliInputs $args + taskArgumentExecute ` + $args[1] ${function:runTask} "Argument expected run " + } + "run-nodeps" { + $runDeps = $false; + getCliInputs $args + taskArgumentExecute ` + $args[1] ${function:runTask} "Argument expected run " + } + Default { + _usage + } + } +} catch { + Write-Host $_.Exception.Message -Foreground "Red" + Write-Host "" + $lines = $_.ScriptStackTrace.Split("`n") + + foreach ($line in $lines) { + Write-Host "`t$line" -Foreground "DarkGray" + } + + Write-Host "" + exit 500 +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4831d9b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,110 @@ +# ARGUMENTS -------------------------------------------------------------------- +## +# Board architecture +## +ARG IMAGE_ARCH= + +## +# Base container version +## +ARG SDK_BASE_VERSION=4-8.0-rc +ARG BASE_VERSION=4-rc + +## +# Directory of the application inside container +## +ARG APP_ROOT= + +## +# Board GPU vendor prefix +## +ARG GPU= + +# ARGUMENTS -------------------------------------------------------------------- + + + +# BUILD ------------------------------------------------------------------------ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS Build + +ARG IMAGE_ARCH +ARG APP_ROOT + +# this is needed for the build to work witht he libslint +RUN apt-get -q -y update && \ + apt-get -q -y install \ + libfontconfig1 + +COPY . ${APP_ROOT} +WORKDIR ${APP_ROOT} + +# build +RUN dotnet restore && \ + dotnet publish -c Release -r linux-${IMAGE_ARCH} +# BUILD ------------------------------------------------------------------------ + + + +# DEPLOY ------------------------------------------------------------------------ +FROM --platform=linux/${IMAGE_ARCH} \ + torizon/wayland-base${GPU}:${BASE_VERSION} AS Deploy + +ARG IMAGE_ARCH +ARG GPU +ARG APP_ROOT + +# for vivante GPU we need some "special" sauce +RUN apt-get -q -y update && \ + if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \ + apt-get -q -y install \ + imx-gpu-viv-wayland-dev \ + ; else \ + apt-get -q -y install \ + libgl1 \ + ; fi \ + && \ + apt-get clean && apt-get autoremove && \ + rm -rf /var/lib/apt/lists/* + +# Install Slint dependencies +# Install Slint and .net dependencies +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive \ + apt-get install \ + libfontconfig1 \ + libxkbcommon0 \ + fonts-noto-core \ + fonts-noto-cjk \ + fonts-noto-cjk-extra \ + fonts-noto-color-emoji \ + fonts-noto-ui-core \ + fonts-noto-ui-extra \ + libicu72 \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get -y update && apt-get install -y --no-install-recommends \ +# DO NOT REMOVE THIS LABEL: this is used for VS Code automation + # __torizon_packages_prod_start__ + # __torizon_packages_prod_end__ +# DO NOT REMOVE THIS LABEL: this is used for VS Code automation + && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/* + +# Default to the Skia backend for best performance +ENV SLINT_BACKEND=winit-skia +# Default to Slint running in fullscreen +ENV SLINT_FULLSCREEN=1 +# Default style to fluent +ENV SLINT_STYLE=fluent + +# Copy the application compiled in the build step to the $APP_ROOT directory +# path inside the container, where $APP_ROOT is the torizon_app_root +# configuration defined in settings.json +COPY --from=Build ${APP_ROOT}/bin/Release/net8.0/linux-${IMAGE_ARCH}/publish ${APP_ROOT} + +# "cd" (enter) into the APP_ROOT directory +WORKDIR ${APP_ROOT} + +# Command executed in runtime when the container starts +CMD ["./torizonWSLWelcome"] + +# DEPLOY ------------------------------------------------------------------------ diff --git a/Dockerfile.debug b/Dockerfile.debug new file mode 100644 index 0000000..44989d5 --- /dev/null +++ b/Dockerfile.debug @@ -0,0 +1,134 @@ +# ARGUMENTS -------------------------------------------------------------------- +## +# Board architecture +## +ARG IMAGE_ARCH= + +## +# Base container version +## +ARG SDK_BASE_VERSION=rc-8.0 +ARG BASE_VERSION=4-bookworm-rc-1.2.2 + +## +# Directory of the application inside container +## +ARG APP_ROOT= + + +## +# Debug port +## +ARG SSH_DEBUG_PORT= + +## +# Run as +## +ARG SSHUSERNAME= + +## +# Board GPU vendor prefix +## +ARG GPU= + +## +# Get the debugger +## +FROM --platform=linux/${IMAGE_ARCH} \ + torizon/dotnet-debug:${SDK_BASE_VERSION} AS dotnet + +# BUILD ------------------------------------------------------------------------ +## +# Deploy Step +## +FROM commontorizon/slint-base-${IMAGE_ARCH}${GPU}:${BASE_VERSION} AS Debug + +ARG IMAGE_ARCH +ARG GPU +ARG SSH_DEBUG_PORT +ARG APP_ROOT +ARG SSHUSERNAME + +# SSH for remote debug +EXPOSE ${SSH_DEBUG_PORT} + +# Make sure we don't get notifications we can't answer during building. +ENV DEBIAN_FRONTEND="noninteractive" + +# for vivante GPU we need some "special" sauce +RUN apt-get -q -y update && \ + if [ "${GPU}" = "-vivante" ] || [ "${GPU}" = "-imx8" ]; then \ + apt-get -q -y install \ + imx-gpu-viv-wayland-dev \ + ; else \ + apt-get -q -y install \ + libgl1 \ + ; fi \ + && \ + apt-get clean && apt-get autoremove && \ + rm -rf /var/lib/apt/lists/* + +# your regular RUN statements here +# Install required Debug packages +RUN apt-get -q -y update && \ + apt-get -q -y install \ + openssl \ + openssh-server \ + rsync \ + file \ + curl \ + gdb \ + rust-gdb && \ + apt-get clean && apt-get autoremove && \ + rm -rf /var/lib/apt/lists/* + +# get the .NET debugger +COPY --from=dotnet /vsdbg /vsdbg + +# automate for torizonPackages.json +RUN apt-get -q -y update && \ + apt-get -q -y install \ +# DO NOT REMOVE THIS LABEL: this is used for VS Code automation + # __torizon_packages_dev_start__ + # __torizon_packages_dev_end__ +# DO NOT REMOVE THIS LABEL: this is used for VS Code automation + && \ + apt-get clean && apt-get autoremove && \ + rm -rf /var/lib/apt/lists/* + +# Default to the Skia backend for best performance +ENV SLINT_BACKEND=winit-skia +# Default to Slint running in fullscreen +ENV SLINT_FULLSCREEN=1 +# Default style to fluent +ENV SLINT_STYLE=fluent + +# ⚠️ DEBUG PURPOSES ONLY!! +# copies RSA key to enable SSH login for user +COPY .conf/id_rsa.pub /id_rsa.pub + +# create folders needed for the different components +# configures SSH access to the container and sets environment by default +RUN mkdir /var/run/sshd && \ + sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' \ + -i /etc/pam.d/sshd && \ + if test $SSHUSERNAME != root ; \ + then mkdir -p /home/$SSHUSERNAME/.ssh ; \ + else mkdir -p /root/.ssh ; fi && \ + if test $SSHUSERNAME != root ; \ + then cp /id_rsa.pub /home/$SSHUSERNAME/.ssh/authorized_keys ; \ + else cp /id_rsa.pub /root/.ssh/authorized_keys ; fi && \ + echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \ + echo "Port ${SSH_DEBUG_PORT}" >> /etc/ssh/sshd_config && \ + su -c "env" $SSHUSERNAME > /etc/environment + +RUN rm -r /etc/ssh/ssh*key && \ + dpkg-reconfigure openssh-server + + +# Copy the compiled application to the $APP_ROOT directory path inside the +# container, where $APP_ROOT is the torizon_app_root configuration defined +# in settings.json. +COPY --chown=$SSHUSERNAME:$SSHUSERNAME ./bin/Debug/net8.0/linux-${IMAGE_ARCH}/publish ${APP_ROOT} + +CMD [ "/usr/sbin/sshd", "-D" ] diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..e39b468 --- /dev/null +++ b/Program.cs @@ -0,0 +1,19 @@ +using Slint; +using Torizon.Shell; +using AppWindow; + +var win = new Window(); + +// create the login +win.CreateLogin = () => +{ + // create the login + var _ret = Exec.Bash( + $"useradd -m {win.loginName} -p $(openssl passwd -1 {win.loginRepPsswd})" + ); + + // close the process + Environment.Exit(0); +}; + +win.Run(); diff --git a/Utils/Shell.cs b/Utils/Shell.cs new file mode 100644 index 0000000..96a35ec --- /dev/null +++ b/Utils/Shell.cs @@ -0,0 +1,39 @@ +using System.Diagnostics; + +namespace Torizon.Shell +{ + public static class Exec + { + public class Result + { + public string? output; + public int exitCode; + } + + public static Result Bash(this string cmd) + { + var escapedArgs = cmd.Replace("\"", "\\\""); + + var process = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + process.Start(); + string result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return new Result + { + output = result, + exitCode = process.ExitCode + }; + } + } +} diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6fec8a4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,87 @@ +version: "3.9" +services: + torizon-wsl-welcome-debug: + build: + context: . + dockerfile: Dockerfile.debug + image: ${LOCAL_REGISTRY}:5002/torizon-wsl-welcome-debug:${TAG} + ports: + - 2222:2222 + volumes: + - type: bind + source: /tmp + target: /tmp + - type: bind + source: /dev + target: /dev + device_cgroup_rules: + # ... for tty0 + - "c 4:0 rmw" + # ... for tty7 + - "c 4:7 rmw" + # ... for /dev/input devices + - "c 13:* rmw" + - "c 199:* rmw" + # ... for /dev/dri devices + - "c 226:* rmw" + depends_on: [ + weston + ] + + torizon-wsl-welcome: + build: + context: . + dockerfile: Dockerfile + image: ${DOCKER_LOGIN}/torizon-wsl-welcome:${TAG} + volumes: + - type: bind + source: /tmp + target: /tmp + - type: bind + source: /dev + target: /dev + device_cgroup_rules: + # ... for tty0 + - "c 4:0 rmw" + # ... for tty7 + - "c 4:7 rmw" + # ... for /dev/input devices + - "c 13:* rmw" + - "c 199:* rmw" + # ... for /dev/dri devices + - "c 226:* rmw" + depends_on: [ + weston + ] + + weston: + image: commontorizon/weston${GPU}:4-rc + environment: + - ACCEPT_FSL_EULA=1 + # Required to get udev events from host udevd via netlink + network_mode: host + volumes: + - type: bind + source: /tmp + target: /tmp + - type: bind + source: /dev + target: /dev + - type: bind + source: /run/udev + target: /run/udev + cap_add: + - CAP_SYS_TTY_CONFIG + # Add device access rights through cgroup... + device_cgroup_rules: + # ... for tty0 + - "c 4:0 rmw" + # ... for tty1 + - "c 4:1 rmw" + # ... for tty7 + - "c 4:7 rmw" + # ... for /dev/input devices + - "c 13:* rmw" + - "c 199:* rmw" + # ... for /dev/dri devices + - "c 226:* rmw" diff --git a/torizonPackages.json b/torizonPackages.json new file mode 100644 index 0000000..efcb815 --- /dev/null +++ b/torizonPackages.json @@ -0,0 +1,6 @@ +{ + "deps": [ + ], + "devDeps": [ + ] +} diff --git a/torizonWSLWelcome.csproj b/torizonWSLWelcome.csproj new file mode 100644 index 0000000..978404a --- /dev/null +++ b/torizonWSLWelcome.csproj @@ -0,0 +1,29 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + PreserveNewest + + + + + + + PreserveNewest + + + + + + + + diff --git a/torizonWSLWelcome.sln b/torizonWSLWelcome.sln new file mode 100644 index 0000000..34dd92a --- /dev/null +++ b/torizonWSLWelcome.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "torizonWSLWelcome", "torizonWSLWelcome.csproj", "{48F0516A-C2F9-4FC7-B3B7-8815A0B4D302}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48F0516A-C2F9-4FC7-B3B7-8815A0B4D302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48F0516A-C2F9-4FC7-B3B7-8815A0B4D302}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48F0516A-C2F9-4FC7-B3B7-8815A0B4D302}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48F0516A-C2F9-4FC7-B3B7-8815A0B4D302}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {801A2EC2-86B6-4F8F-B55A-9D405FA32E98} + EndGlobalSection +EndGlobal diff --git a/ui/AppWindow.slint b/ui/AppWindow.slint new file mode 100644 index 0000000..bc566ef --- /dev/null +++ b/ui/AppWindow.slint @@ -0,0 +1,202 @@ +import { + Button, + HorizontalBox, + VerticalBox, + AboutSlint, + TextEdit, + LineEdit +} from "std-widgets.slint"; + +export component AppWindow inherits Window { + in-out property createLoginShow : false; + in-out property labelDoNotMatch : "Repeat password:"; + in-out property loginName; + in-out property loginPsswd; + in-out property loginRepPsswd; + callback createLogin(); + + max-height: 700px; + max-width: 800px; + min-width: 800px; + min-height: 700px; + + background: @linear-gradient(30deg, #F2F2F2, #0164b1); + //background: #F2F2F2; + title: "Torizon on WSL 2"; + + VerticalBox { + alignment: LayoutAlignment.center; + spacing: 10px; + + HorizontalLayout { + alignment: LayoutAlignment.center; + + Image { + source: @image-url("./assets/torizonLoves.png"); + width: 263px; + height: 143px; + } + } + + HorizontalBox { + alignment: LayoutAlignment.center; + padding-bottom: 40px; + + Text { + text: "Welcome to Torizon on WSL 2!"; + font-size: 30px; + font-weight: 600; + color: #004377; + } + } + + HorizontalBox { + alignment: LayoutAlignment.start; + padding-left: 20px; + + Text { + horizontal-alignment: TextHorizontalAlignment.left; + text: "Let's get things ready for you start developing!"; + font-size: 20px; + } + } + + if (!createLoginShow) : + Rectangle { + //background: #f2f2f2; + border-radius: 20px; + height: 200px; + + VerticalLayout { + alignment: LayoutAlignment.center; + spacing: 50px; + padding: 20px; + + Text { + horizontal-alignment: TextHorizontalAlignment.left; + font-size: 20px; + wrap: TextWrap.word-wrap; + text: "First we need to create a new user name and password to you use with your brand new Torizon on WSL 2!*"; + } + + Button { + height: 40px; + text: "Let's Go!"; + clicked => { + createLoginShow = true; + } + } + + Text { + text: "*https://learn.microsoft.com/en-us/windows/wsl/setup/environment#set-up-your-linux-username-and-password"; + } + } + } + + if (createLoginShow) : + VerticalLayout { + alignment: LayoutAlignment.center; + padding-top: 30px; + spacing: 10px; + + HorizontalLayout { + x: userInput.x; + alignment: LayoutAlignment.start; + + Text { + text: "New user name:"; + font-size: 15px; + } + } + + HorizontalLayout { + alignment: LayoutAlignment.center; + + userInput := LineEdit { + width: 400px; + height: 40px; + text <=> loginName; + } + } + + HorizontalLayout { + x: userPsswd.x; + alignment: LayoutAlignment.start; + + Text { + text: "New Password:"; + font-size: 15px; + } + } + + HorizontalLayout { + alignment: LayoutAlignment.center; + + userPsswd := LineEdit { + input-type: InputType.password; + width: 400px; + height: 40px; + text <=> loginPsswd; + edited => { + if (loginPsswd != loginRepPsswd) { + labelDoNotMatch = "Repeat password (do not match):"; + labelRepead.color = #aa0e0e; + } else { + labelDoNotMatch = "Repeat password (ok):"; + labelRepead.color = #145c12; + } + } + } + } + + HorizontalLayout { + x: userRepPsswd.x; + alignment: LayoutAlignment.start; + + labelRepead := Text { + text: labelDoNotMatch; + font-size: 15px; + } + } + + HorizontalLayout { + alignment: LayoutAlignment.center; + + userRepPsswd := LineEdit { + input-type: InputType.password; + width: 400px; + height: 40px; + text <=> loginRepPsswd; + edited => { + if (loginPsswd != loginRepPsswd) { + labelDoNotMatch = "Repeat password (do not match):"; + labelRepead.color = #aa0e0e; + } else { + labelDoNotMatch = "Repeat password (ok):"; + labelRepead.color = #145c12; + } + } + } + } + + HorizontalLayout { + alignment: LayoutAlignment.center; + + Button { + text: "Create User"; + primary: true; + width: 300px; + height: 40px; + enabled: loginPsswd != "" && loginRepPsswd != "" && loginPsswd == loginRepPsswd; + clicked => { + if ( + loginPsswd != "" && loginRepPsswd != "" && loginPsswd == loginRepPsswd + ) { + root.createLogin(); + } + } + } + } + } + } +} diff --git a/ui/assets/torizonLoves.png b/ui/assets/torizonLoves.png new file mode 100644 index 0000000000000000000000000000000000000000..592a9f0da371a326e54f2a234c1bbcc7ad511f08 GIT binary patch literal 17126 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