Add InstallScriptBuilder + GET /install.sh + GET /client.tar.gz

This commit is contained in:
2026-06-11 19:03:33 +00:00
parent e5ae228738
commit 751030dcef
5 changed files with 174 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
using Microsoft.Extensions.Configuration;
namespace backend.Install;
public static class InstallEndpoints
{
public static IEndpointRouteBuilder MapInstallEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/install.sh", (HttpContext http, InstallScriptBuilder builder) =>
{
var scheme = http.Request.Scheme;
var host = http.Request.Host.ToString();
var script = builder.Build($"{scheme}://{host}");
return Results.Text(script, "text/x-shellscript");
});
app.MapGet("/client.tar.gz", (IConfiguration cfg, IWebHostEnvironment env) =>
{
var path = cfg["Install:ClientTarballPath"]
?? Path.Combine(env.WebRootPath ?? "wwwroot", "client.tar.gz");
if (!File.Exists(path))
return Results.NotFound(new { error = "client.tar.gz not found at " + path });
return Results.File(path, "application/gzip", "client.tar.gz");
});
return app;
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace backend.Install;
public class InstallScriptBuilder
{
public string Build(string backendUrl)
{
return $$"""
#!/usr/bin/env bash
# Smart Assistant installer (idempotent). Re-run to upgrade.
set -euo pipefail
BACKEND_URL="{{backendUrl}}"
ASSISTANT_HOME="$HOME/assistant"
RESET=0
for arg in "$@"; do
case "$arg" in
--reset) RESET=1 ;;
esac
done
echo "Installing system deps (sudo password may be prompted)..."
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends python3-venv libportaudio2 alsa-utils
mkdir -p "$ASSISTANT_HOME/code" "$ASSISTANT_HOME/state"
if [ ! -d "$ASSISTANT_HOME/.venv" ]; then
python3 -m venv "$ASSISTANT_HOME/.venv"
fi
echo "Downloading client bundle from $BACKEND_URL ..."
curl -fsSL "$BACKEND_URL/client.tar.gz" | tar -xz -C "$ASSISTANT_HOME/code"
"$ASSISTANT_HOME/.venv/bin/pip" install --quiet -r "$ASSISTANT_HOME/code/requirements.txt"
"$ASSISTANT_HOME/.venv/bin/pip" install --quiet --no-deps -r "$ASSISTANT_HOME/code/requirements-nodeps.txt"
if [ "$RESET" -eq 1 ]; then
rm -f "$ASSISTANT_HOME/state/config.json"
fi
if [ ! -f "$ASSISTANT_HOME/state/config.json" ]; then
echo
read -r -p "Pairing code: " CODE
"$ASSISTANT_HOME/.venv/bin/python" -m client.pair --backend "$BACKEND_URL" --code "$CODE"
fi
mkdir -p "$HOME/.config/systemd/user"
cat > "$HOME/.config/systemd/user/assistant.service" <<UNIT
[Unit]
Description=Smart Assistant Pi client
After=network-online.target
[Service]
Environment=PA_ALSA_PLUGHW=1
ExecStart=$ASSISTANT_HOME/.venv/bin/python -m client.main
WorkingDirectory=$ASSISTANT_HOME/code
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
UNIT
loginctl enable-linger "$USER" 2>/dev/null || true
systemctl --user daemon-reload
systemctl --user enable --now assistant
echo "Done. systemctl --user status assistant"
""";
}
}
+3
View File
@@ -1,6 +1,7 @@
using backend.Auth;
using backend.Data;
using backend.Devices;
using backend.Install;
using backend.Pairing;
using Microsoft.EntityFrameworkCore;
@@ -14,6 +15,7 @@ builder.Services.AddAuthorization();
builder.Services.AddScoped<backend.Devices.DeviceTokenService>();
builder.Services.AddScoped<backend.Pairing.PairingService>();
builder.Services.AddSingleton<backend.Install.InstallScriptBuilder>();
var app = builder.Build();
@@ -37,6 +39,7 @@ app.UseAuthorization();
app.MapAuthEndpoints();
app.MapPairingEndpoints();
app.MapDevicesEndpoints();
app.MapInstallEndpoints();
app.MapGet("/health", () => Results.Ok(new { ok = true }));