case 4706 - remove the unused RunProgram.Run and its shell helpers

Run, RunWindows (cmd.exe /C), RunLinuxShell (/bin/sh -c) and RunOSX have had
no callers since the backup moved to RunNoShell. Backup through the API and
the integration suite unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 08:02:40 -07:00
parent b3b7fa6a9d
commit 1c97678498
2 changed files with 2 additions and 121 deletions

View File

@@ -214,7 +214,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s
| 4700 | Restore note for customers with a dedicated database role | 1, docs |
| 4703 | Report JS console error logging | 1 |
| 4705 | License.DoFetchAsync never tries the fifth licence server | 1. **Done** (rethrow after the last server, `x == 4`; the trial request (`rvr`, rockfish only) now uses the same fallback loop, John's decision. Sandbox with rockfish, io, europa and callisto pointed at 127.0.0.1: the old build failed the trial request with a connection error, the new build reached sockeye.ayanova.com. No changelog: only noticeable if the licence server names fail) |
| 4706 | Remove the unused RunProgram.Run | 1 |
| 4706 | Remove the unused RunProgram.Run | 1. **Done** (`Run` and its `RunWindows`/`RunLinuxShell`/`RunOSX` removed, no callers; `RunNoShell` stays. Backup through the API wrote the database dump and attachments zip; full suite = baseline) |
| 4707 | Stray cypress.json in raven-client | 1, investigate |
| 4687 | Generator polls every second (raven and sockeye) | 1 |
| 4682 | Time-only picker keeps the old date's DST offset | 1 |
@@ -270,7 +270,7 @@ Found during the port, out of scope. One line each.
- Sockeye 8.1.0 has the NLog 6 archive naming problem fixed here for AyaNova: archives are `log-sockeye_NN.txt`, old `-0` to `-8` archives are never deleted, and its log viewer still sorts by name (it also filters on `ayanova.txt`, not `sockeye.txt`, so the current file is listed twice). Same fix applies: `ArchiveSuffixFormat = "-{0}"` and the date sort
- **Security review: customer contact isolation (John, 2026-10-23: urgent, fixed with the "next release" cases before 8.3.0; case numbers to come).** Findings F13 to F17 in `devdocs/modernization/SECURITY-REVIEW.md`, confirmed on the local server as a seeded customer contact: any customer's work order by id (and, by code, its customer report and attachments); other customers' service requests; the full customer list with phones and emails through pick lists; the whole service schedule; notification subscriptions created for another user. Hosted and self-hosted perpetual installations are equally important. Fix direction in the review: block customer-type users by default except the routes the customer UI uses, then add a "belongs to my customer" check to each allowed route that takes an id. F5 (2FA without password), F1 (placeholder JWT secret) and F18 (pick-list search text in SQL) are fixed in 8.3.0 too (John, 2026-10-23). **8.3.0 is not released until every security review finding is fixed or explicitly accepted by John** ("all holes must be plugged"); the review's remaining passes therefore come before the release. Front-end exception (John, 2026-10-23): DOMPurify to 2.5.x and Vuetify to the latest 2.x (2.7.2, or 2.6.16 if 2.7 does not build on Node 12) for security finding F34, each proven by a full client build and Cypress
- **Fixed in case 4692.** **SuperUser can be deleted (serious, pre-existing, own case; John's decision 2026-10-23: fixed with the other "next release" cases before 8.3.0 ships, not under the port):** `UserBiz.ValidateCanDelete` (`biz/UserBiz.cs:1055`) only refuses a delete when the user has related records; nothing protects user id 1, although updates to it are blocked (`UserBiz.cs:946`). On the droplet, whose sample data left SuperUser with no related records, the integration test `UserWithActivityShouldNotBeDeleteable` (`DELETE User/1`) succeeded and removed the account; `AYANOVA_SET_SUPERUSER_PW` cannot bring it back (it only resets an existing user 1). Same code in 8.2.4; likely also why `superuser` could not log in on the reused local database at the first baseline attempt. Any user with delete rights on Users can do this
- `RunProgram.Run` (and its `RunWindows`/`RunLinuxShell`/`RunOSX` helpers) has no callers since the backup moved to `RunNoShell`. It runs through `cmd.exe /C` or `/bin/sh -c` and logs its full command line on error: delete it rather than reuse it
- **Removed in case 4706.** `RunProgram.Run` (and its `RunWindows`/`RunLinuxShell`/`RunOSX` helpers) has no callers since the backup moved to `RunNoShell`. It runs through `cmd.exe /C` or `/bin/sh -c` and logs its full command line on error: delete it rather than reuse it
- **Own case after the port (John): automated provisioning from Sockeye.** Goal: from a trial subscription server request, one menu item in Sockeye (after John's manual approval) provisions the whole server through the DigitalOcean API, ready for login: droplet, DNS, firewall, setup, licence. Replaces today's manual steps (header comments of the setup script), which are slow and error-prone. Not part of this port
- Linux desktop manual (`ops-install-linux-desktop.md`), pre-existing, same in 8.2.4: `sudo mkdir ~/.ayanova-data` creates the folder in the user's home, but `sudo ./AyaNova` runs as root, so `AYANOVA_DATA_PATH` `~/.ayanova-data` resolves to `/root/.ayanova-data` and the user's folder is never used (data, logs and backups end up under `/root`). Found in the 8.3.0 desktop zip test. Fix the manual (absolute data path, or not running as root) after the release
- **Own case after the port (John):** the subscription server setup script is hard-coded in `biz-subscription-server.vue`, so every change needs a new Sockeye client build. Make it editable without a rebuild

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace AyaNova.Util
@@ -11,42 +10,6 @@ namespace AyaNova.Util
{
public static string Run(string cmd, string arguments, ILogger log = null, int waitForExitTimeOut = int.MaxValue)
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return RunWindows(cmd, arguments, waitForExitTimeOut);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return RunOSX(cmd, arguments, waitForExitTimeOut);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return RunLinuxShell(cmd, arguments, waitForExitTimeOut);
}
}
catch (Exception ex)
{
if (log != null)
{
log.LogError(ex, $"RunProgram error running command:{cmd} {arguments}");
}
throw;
}
throw new PlatformNotSupportedException();
}
/// <summary>
/// Run a program directly, no shell: each argument is passed as-is (no quoting or escaping needed, nothing is interpreted by a shell)
/// and extra environment variables are set for this process only (e.g. PGPASSWORD so a password never appears on a command line)
@@ -98,87 +61,5 @@ namespace AyaNova.Util
}
}
private static string RunWindows(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue)
{
//RunProgram.Run("cmd.exe",FullRunCommand, log);
// var FullRunCommand=$"/C {BackupUtilityCommand} {Arguments}";
//for Windows need to pass to cmd.exe because often have command line piping etc
var args = $"/C {cmd} {arguments}";
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
process.Start();
string result = $"{process.StandardOutput.ReadToEnd()}{process.StandardError.ReadToEnd()} ";
if (!process.WaitForExit(waitForExitTimeOut))
{
result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION";
}
return result;
}
}
private static string RunLinuxShell(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue)
{
var escapedArgs = $"{cmd} {arguments}".Replace("\"", "\\\"");
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
if (!process.WaitForExit(waitForExitTimeOut))
{
result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION";
}
return result;
}
}
private static string RunOSX(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue)
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = cmd,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
if (!process.WaitForExit(waitForExitTimeOut))
{
result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION";
}
return result;
}
}
}
}