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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ bld/
[Oo]bj/
[Ll]og/
.vs/
.vscode/

# NuGet Packages
*.nupkg
Expand Down
Binary file modified docs/ScreenShot-MenuItems.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/ScreenShot-ToolsOptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
82 changes: 25 additions & 57 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,41 @@ Suess Labs consulting is sponsored by _Xeno Innovations, Inc._

## Overview

Now developers can build, deploy and debug projects on their remote Linux (Ubuntu, Raspberry PI, etc) devices! Customize your SSH connection to use either a _password_ or a _private key_.
Now developers can build, deploy and debug projects on their remote Linux (Ubuntu, Raspberry PI, etc) devices! Customize your SSH connection to use either a _password_, a _private key_, or an SSH CA-signed certificate.

If you enjoy using the extension, please give it a ★★★★★ rating on the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=SuessLabs.VSLinuxDebugger).

### What's new

* **SSH CA certificate authentication** — supports a private key with an accompanying `<key>-cert.pub` certificate (auto-detected next to the key, or set explicitly), in addition to password and plain private-key auth.
* **Sudo-elevated debugger launch** — an opt-in setting launches `vsdbg` via a configurable `sudo` command, for debuggees running with elevated or ambient capabilities that the debugger must match to attach.
* **Deploys via `dotnet publish`** — always produces a real native executable (self-contained or framework-dependent, your choice), with the executable bit restored after transfer (lost by default over tar/scp from Windows).
* **Environment variables for the debuggee** — pass `KEY=VALUE` pairs through to the remote process, for programs that read required configuration from the environment.
* **Configurable pre/post-deploy commands and attach-to-running-process** — run arbitrary shell commands before/after each deploy (i.e. stopping/restarting a systemd service), and optionally attach the debugger to that already-running process (via a configurable PID-lookup command) instead of launching a new one.
* **Attach Only** — reattach to a running/deployed process without rebuilding or redeploying.
* **Stop button** — cancels an in-progress build/deploy/debug between steps.
* **Modernized Options UI** — settings are split across focused Tools > Options pages (Remote Host, Remote Credentials, Remote Debugger, Remote Launch, Local) built with a real WPF UI (checkboxes, dynamic show/hide for dependent fields) instead of a single page with a WinForms PropertyGrid.
* Support for Visual Studio 2026 and newer .NET target frameworks (net8.0, net10.0).

### Supported Remote OS

The following Linux distrobutions have been validated and are supported.
The following Linux distributions have been validated and are supported.

* Ubuntu (20.04 LTS, 22.04 LTS, 24.x LTS)
* Raspberry PI OS
* Raspberry Pi OS
* Debian-based embedded Linux images (i.e. Yocto/OpenEmbedded targets), where `curl` and an SSH server are available

### Usage

![VS Menu](docs/ScreenShot-MenuItems.png)

* Build and upload to remote devices
* Remote debugging*
* _This is still in the experimental stages. Please use VS' Attach to Process if you have issues_
* **Build and Deploy** — build, upload to the remote device, and (re)start it if pre/post-deploy commands are configured.
* **Build, Deploy and Debug** — the above, then attach the debugger (or launch it, for a fresh non-supervised process).
* **Attach Only (no build/deploy)** — reattach to whatever's already running/deployed, without rebuilding or redeploying.
* **Stop** — cancels an in-progress build/deploy/debug between steps.
* VS Linux Debugger will automatically detect and install `vsdbg` for you!

For GUI app debugging, you can use the _Build and Deploy_ feature, however, you must manually _Attach to Process_ via SSH using Visual Studio at this time.
For GUI app debugging, you must manually _Attach to Process_ via SSH using Visual Studio at this time (see below).

### Getting Started

Expand All @@ -46,9 +60,9 @@ sudo apt install curl
**Windows**:

1. Open Visual Studio (VS) > Tools > Options > **Linux Debugger**
2. **Input:** Remote Host IP address
3. **Input:** Remote's User Name and Password
4. VS > Extensions > Linux Debugger > **Build, Deploy, Debug**
2. Configure the **Remote Host** page (IP address) and **Remote Credentials** page (user name, and either a password, a private key, or an SSH CA certificate)
3. Optionally, configure **Remote Debugger** (deploy folder, self-contained/RID) and **Remote Launch** (env vars, pre/post-deploy commands, attach-to-running-process, sudo, X11)
4. VS > Extensions > Linux Debugger > **Build, Deploy and Debug**

![Tools Options](docs/ScreenShot-ToolsOptions.png)

Expand All @@ -70,53 +84,7 @@ For GUI projects, you can use **Build and Deploy** and then manually attach to t

This will save you 1.5 minutes on every build of manual uploading and updating rights via `chown -R`.

### Manually Attaching (for Command line apps)

For CLI projects, you can use **Build and Deploy** and then manually attach to the process via SSH by using Visual Studio's built-in tool (similar to above).

You may have to manually interrupt your app via `Console.ReadLine();` high-up in your entry-point (i.e. `main()`).

1. Deploy to remote machine via
1. Extensions > Linux Debugger > **"Build and Deploy"**
2. Run your CLI app on remote machine
1. `dotnet MyCliApp.dll`
3. Debug > **"Attach to Process.."**
1. Connection Type: **SSH**
2. Connection Target: **(Remote machine's IP)**
3. (Select process)
4. Click, **Attach**
5. Check, **"Managed (.NET Core for Unix)"**
6. Click, **OK**
4. Continue your application, if using a manual interrupt (i.e. `Console.ReadLine();`)

This will save you 1.5 minutes on every build of manual uploading and updating rights via `chown -R`.

## How To Generate Private Key (optional)

The following steps are options if you wish to use an SSH Private Key. These steps were written for Windows 10, however, on Linux the steps are similar.

1. Open PowerShell:
2. **Generate key** (_with old PEM format_)
1. `ssh-keygen -m PEM -t rsa -b 4096`
2. In the future, we'll be able to use `ssh-keygen`.. just not yet.
3. Set output name (_default is okay for basic setups_)
4. Input a passphrase for the key _(OPTIONAL)_
5. Windows will now generate your RSA public/private key pair.
1. Default location: `%UserProfile%\.ssh` (WINOWS)
2. The public key will be stored as `id_rsa.pub` in the directory
6. **Upload the public key** to your remote machine
1. Navigate to folder, `~/.ssh/` on Linux device
2. If `~/.ssh/authorized_keys` exists, append the contents of `id_rsa.pub` to the next line.
3. If it does not exist, simply upload `id_rsa.pub` and rename it to, `authorized_keys`
7. DONE!

## Used By

* [SuessLabs](https://suesslabs.com) and [Xeno Innovations](https://xenoinc.com)
* [Wilderness Labs](https://github.com/WildernessLabs)
* [Omnicell, Inc.](https://omnicell.com)

_Want your name added? Reach out to us_
If your remote device uses **SSH CA-signed certificates** instead of `authorized_keys` (i.e. `TrustedUserCAKeys` configured in `sshd_config`), point "SSH Private Key File" at your CA-issued private key; the matching `<key>-cert.pub` certificate is picked up automatically if it sits next to the key, or can be set explicitly via "SSH Certificate File" on the Remote Credentials options page.

## Future Features

Expand Down
11 changes: 11 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

This document contains the release information for the project.

### 3.0.0

* Added: SSH CA certificate authentication (a private key with an accompanying `<key>-cert.pub` certificate, auto-detected or set explicitly), in addition to password and plain private-key auth.
* Added: Sudo-elevated debugger launch, for debuggees running with elevated or ambient capabilities that the debugger must match to attach.
* Added: Configurable pre/post-deploy shell commands, and an "Attach to Already-Running Process" mode (with a configurable PID-lookup command) for debuggees managed by an external supervisor (i.e. a systemd service).
* Added: "Attach Only" menu item to reattach to a running/deployed process without rebuilding or redeploying.
* Added: A "Stop" button to cancel an in-progress build/deploy/debug between steps.
* Update: Deployment now always goes through `dotnet publish` (self-contained or framework-dependent), producing a real native executable either way, instead of uploading a plain build's output folder.
* Update: Options window split across focused Tools > Options pages (Remote Host, Remote Credentials, Remote Debugger, Remote Launch, Local) with a real WPF UI (checkboxes, dynamic show/hide for dependent fields) instead of a single page with a WinForms PropertyGrid.
* Update: Support for Visual Studio 2026 and newer .NET target frameworks (net8.0, net10.0).

### 2.3.0 - 2024-12-02

Early patch release before Christmas break. Effort moving forward will be for the upcoming 3.0 release.
Expand Down
2 changes: 1 addition & 1 deletion sandbox/ConsoleNet6.sln → sandbox/Console.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32317.152
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleNet6", "ConsoleNet6\ConsoleNet6.csproj", "{F181E39A-CCE1-45C1-B196-0CE759BB86AB}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "Console\Console.csproj", "{F181E39A-CCE1-45C1-B196-0CE759BB86AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>

<!-- TESTING BELOW -->
<DebugType>portable</DebugType>
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion sandbox/GuiNet6.sln → sandbox/Gui.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32804.467
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GuiNet6", "GuiNet6\GuiNet6.csproj", "{98F164D1-51CF-4BC1-8F9D-8DFB541E57F5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gui", "Gui\Gui.csproj", "{98F164D1-51CF-4BC1-8F9D-8DFB541E57F5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
21 changes: 21 additions & 0 deletions sandbox/Gui/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Gui"
xmlns:vm="using:Gui.ViewModels"
xmlns:views="using:Gui.Views"
x:Class="Gui.App"
RequestedThemeVariant="Light">
<Application.DataTemplates>
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
<views:DashboardView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<views:SettingsView />
</DataTemplate>
</Application.DataTemplates>

<Application.Styles>
<FluentTheme />
<!--<StyleInclude Source="avares://Gui/Styles/Icons.axaml" />-->
</Application.Styles>
</Application>
37 changes: 37 additions & 0 deletions sandbox/Gui/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Gui.ViewModels;
using Gui.Views;

namespace Gui;

public class App : Application
{
/// <summary>App entry point.</summary>
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
#if DEBUG
this.AttachDeveloperTools();
#endif
}

/// <summary>Called once the Avalonia framework has finished initializing.</summary>
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Compose views/view-models directly - no DI container, no region manager.
var shellViewModel = new ShellWindowViewModel();
var shellWindow = new ShellWindow
{
DataContext = shellViewModel,
};

desktop.MainWindow = shellWindow;
}

base.OnFrameworkInitializationCompleted();
}
}
22 changes: 22 additions & 0 deletions sandbox/Gui/Gui.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.2" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
<PackageReference Include="ReactiveUI.Avalonia" Version="12.1.2" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\ShellWindow.axaml.cs">
<DependentUpon>ShellWindow.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>
9 changes: 4 additions & 5 deletions sandbox/GuiNet6/Program.cs → sandbox/Gui/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using Avalonia;
using Avalonia.ReactiveUI;
using ReactiveUI.Avalonia;

namespace GuiNet6;
namespace Gui;

internal class Program
{
Expand All @@ -17,11 +17,10 @@ public static AppBuilder BuildAvaloniaApp() => AppBuilder
})
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
AllowEglInitialization = true,
RenderingMode = [Win32RenderingMode.AngleEgl, Win32RenderingMode.Software],
})
.UseSkia()
.UseReactiveUI()
.UseReactiveUI(rxui => { })
.LogToTrace();

// Initialization code. Don't use any Avalonia, third-party APIs or any
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"profiles": {
"GuiNet6": {
"Gui": {
"commandName": "Project"
},
"WSL": {
Expand Down
21 changes: 21 additions & 0 deletions sandbox/Gui/ViewModels/DashboardViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Windows.Input;
using ReactiveUI;

namespace Gui.ViewModels;

public class DashboardViewModel : ViewModelBase
{
public DashboardViewModel()
{
Title = "Dashboard View!";

CmdBreakPoint = ReactiveCommand.Create(() =>
{
// Force a breakpoint
System.Diagnostics.Debug.WriteLine("Breakpoint triggering");
System.Diagnostics.Debugger.Break();
});
}

public ICommand CmdBreakPoint { get; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace GuiNet6.ViewModels;
namespace Gui.ViewModels;

public class SettingsViewModel : ViewModelBase
{
Expand Down
35 changes: 35 additions & 0 deletions sandbox/Gui/ViewModels/ShellWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using ReactiveUI;

namespace Gui.ViewModels;

public class ShellWindowViewModel : ViewModelBase
{
private ViewModelBase _currentPage;

public ShellWindowViewModel()
{
Title = "Sample Avalonia - Navigation";

Sidebar = new SidebarViewModel(NavigateTo);

var dashboard = new DashboardViewModel();
_currentPage = dashboard;
}

/// <summary>Gets the sidebar view model hosted in this shell.</summary>
public SidebarViewModel Sidebar { get; }

/// <summary>Gets or sets the view model currently displayed in the shell's content area.</summary>
public ViewModelBase CurrentPage
{
get => _currentPage;
set => this.RaiseAndSetIfChanged(ref _currentPage, value);
}

/// <summary>Switches the content area to the given view model.</summary>
/// <param name="viewModel">View model to display.</param>
private void NavigateTo(ViewModelBase viewModel)
{
CurrentPage = viewModel;
}
}
24 changes: 24 additions & 0 deletions sandbox/Gui/ViewModels/SidebarViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Windows.Input;
using ReactiveUI;

namespace Gui.ViewModels;

public class SidebarViewModel : ViewModelBase
{
private readonly Action<ViewModelBase> _navigate;

public SidebarViewModel(Action<ViewModelBase> navigate)
{
_navigate = navigate;

Title = "Navigation";

CmdDashboard = ReactiveCommand.Create(() => _navigate(new DashboardViewModel()));
CmdSettings = ReactiveCommand.Create(() => _navigate(new SettingsViewModel()));
}

public ICommand CmdDashboard { get; }

public ICommand CmdSettings { get; }
}
Loading