Chrison.SynoSharp
0.1.1-preview.4
dotnet add package Chrison.SynoSharp --version 0.1.1-preview.4
NuGet\Install-Package Chrison.SynoSharp -Version 0.1.1-preview.4
<PackageReference Include="Chrison.SynoSharp" Version="0.1.1-preview.4" />
<PackageVersion Include="Chrison.SynoSharp" Version="0.1.1-preview.4" />
<PackageReference Include="Chrison.SynoSharp" />
paket add Chrison.SynoSharp --version 0.1.1-preview.4
#r "nuget: Chrison.SynoSharp, 0.1.1-preview.4"
#:package Chrison.SynoSharp@0.1.1-preview.4
#addin nuget:?package=Chrison.SynoSharp&version=0.1.1-preview.4&prerelease
#tool nuget:?package=Chrison.SynoSharp&version=0.1.1-preview.4&prerelease
SynoSharp
A C# client for Synology DSM IaC. Sibling to ProxmoxSharp/UnifiSharp β but not code-generated: Synology publishes no settings/deploy API schema, so per ADR-0002 this is a hand-written read-API client (now) + an SSH-runner for mutations (later).
dotnet add package Chrison.SynoSharp
flowchart LR
APP["π§© homelab engine / CLI"] -->|"read / discover"| WEB["π DSM Web API<br/>SYNO.API.Auth β entry.cgi β SYNO.Core.*"]
APP -. "mutations (write phase)" .-> SSH["π SSH-runner<br/>syno* CLI + synowebapi"]
WEB --> SNAP["π SynologySnapshot"]
SSH --> NAS["ποΈ DSM 7.1 Β· DS1813+"]
WEB --- NAS
classDef future fill:#f3f4f6,stroke:#9ca3af,color:#6b7280;
class SSH future;
Approach
- Read / discover β the DSM Web API (
SYNO.API.Authβentry.cgiβSYNO.Core.*), returning a structuredSynologySnapshot. (This repo.) - Mutations (shares, NFS, users, network) β an SSH-runner over the on-box
syno*CLI +synowebapi(added with the write phase). TheSYNO.Core.*endpoints are undocumented/version-fragile β pinned to DSM 7.1 (the DS1813+ is EOL there).
Projects
| Project | What |
|---|---|
src/SynoSharp/ |
The client β SynologyApiClient (Web-API read), SynologyDiscovery β SynologySnapshot, Ssh/ (the SSH-runner), Tools/ (typed synoshare/synouser/synogroup wrappers), Provisioning/ (specs + reconciler). SemVer. |
src/SynoSharp.Cli/ |
synosharp dotnet tool β discover, ssh-check, plan, apply. |
tests/SynoSharp.Tests/ |
Unit (quoting + reconciler) + skippable live tests (Web-API + SSH). |
Build / use
Built with Fallout (Chris's C#/.NET
build system, a NUKE successor). Requires the .NET 10 SDK and GITHUB_PACKAGES_PAT
(a PAT with read:packages on the Fallout-build org β restores Fallout.*, see nuget.config).
./build.sh # default: Test (Compile + Test); ./build.sh Pack β Chrison.* nupkgs
export SYNOLOGY_BASE_URL=https://nas:5001 SYNOLOGY_USER=β¦ SYNOLOGY_PASSWORD=β¦ SYNOLOGY_VERIFY_TLS=false
synosharp discover # JSON snapshot: model, serial, DSM version, shares, users
synosharp ssh-check # prove the SSH-runner: login + sudo-to-root + read-only `synoshare --enum`
The SSH-runner reuses SYNOLOGY_USER/SYNOLOGY_PASSWORD (the account also supplies
the sudo password β syno* need root) and derives the host from SYNOLOGY_BASE_URL;
override with SYNOLOGY_SSH_HOST/SYNOLOGY_SSH_PORT/SYNOLOGY_SSH_KEY. DSM 7 disables
direct root SSH, so the runner logs in as an admin user and sudo -S (password over
stdin, never in the command line), running tools through env PATH=/usr/syno/sbin:β¦
since sudo's secure_path excludes the syno dirs.
Packages publish to nuget.org (public) under the Chrison.* prefix
(Chrison.SynoSharp, Chrison.SynoSharp.Cli) via Trusted Publishing (OIDC β no
stored key), like the siblings: prerelease on push to main, stable on v* tag.
Assembly name/namespace stay SynoSharp, so using SynoSharp; is unchanged.
Status
Read/discover β verified against the live NAS (2026-05-31, DS1813+ / DSM
7.1.1-42962). discover returns model, serial, DSM version, share names and
user names (SYNO.Core.System / Share / User). The SYNO.Core.* reads are
defensive (degrade to empty on shape mismatch).
SSH-runner transport β verified against the live NAS (2026-05-31). ssh-check
proves the full stack end-to-end (SSH login β sudo-to-root β on-box syno*):
synoshare --enum ALL lists the live shares with zero mutation. The runner
(ISshRunner/SshRunner over SSH.NET) + structured SynologyCommand (shell-quoted
argv) are the transport for the write path.
Write path (Phase A) β reconciler for shares/users/groups, dry-run by default.
Desired-state specs (ShareSpec/UserSpec/GroupSpec) are diffed against live
state by SynologyReconciler β a SynologyPlan of create/delete/skip actions; each
carries the exact synoshare/synouser/synogroup command. ApplyAsync(apply:false)
is a dry-run (the default). Verified against the live NAS (2026-05-31): plan
correctly skipped existing resources, planned creates for new ones, and blocked a
passwordless user create β zero mutation (reads only).
synosharp plan spec.json # diff vs live β dry-run plan (read-only)
synosharp apply spec.json # still a dry-runβ¦
synosharp apply spec.json --confirm # β¦only this mutates
The reconciler does existence + field reconciliation: create-if-missing /
delete-if-present:false / modify-on-drift / skip-if-in-sync, and never
prunes unmanaged resources. Field drift is checked for share/group Description
and user FullName/Email (read via --get/--descget, set via
--setdesc/--descset/--modify); empty/null spec fields are unmanaged (never
clobbered), and expired is preserved. Share deletes keep data by default (DSM
shares are btrfs subvolumes); set ShareSpec.DeleteData = true for a destructive
synoshare --del TRUE. Next: list-valued fields (share ACLs, group membership),
then NFS exports via synowebapi β last, highest-risk, prove on Virtual DSM
(needs an x86/KVM host).
See the write-path plan.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- SSH.NET (>= 2025.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.1-preview.4 | 28 | 7/24/2026 |
| 0.1.0-preview.3 | 34 | 7/23/2026 |
| 0.1.0-preview.2 | 34 | 7/23/2026 |
| 0.1.0-preview.1 | 34 | 7/23/2026 |