From 1153a4f7a1f36203bb2687553178da0024622f65 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Thu, 4 Jun 2026 21:04:51 +0200 Subject: [PATCH] feat: templates shell APT + rendu Mustache Co-Authored-By: Claude Opus 4.8 --- server/templates/render.test.ts | 17 +++++++++++++++++ server/templates/render.ts | 16 ++++++++++++++++ templates/apt/check.sh.tpl | 12 ++++++++++++ templates/apt/full-upgrade.sh.tpl | 11 +++++++++++ templates/apt/reboot.sh.tpl | 6 ++++++ 5 files changed, 62 insertions(+) create mode 100644 server/templates/render.test.ts create mode 100644 server/templates/render.ts create mode 100644 templates/apt/check.sh.tpl create mode 100644 templates/apt/full-upgrade.sh.tpl create mode 100644 templates/apt/reboot.sh.tpl diff --git a/server/templates/render.test.ts b/server/templates/render.test.ts new file mode 100644 index 0000000..144a8dd --- /dev/null +++ b/server/templates/render.test.ts @@ -0,0 +1,17 @@ +// server/templates/render.test.ts +import { describe, it, expect } from "vitest"; +import { renderTemplate } from "./render.js"; + +describe("renderTemplate", () => { + it("rend check.sh.tpl sans proxy", () => { + const out = renderTemplate("apt/check.sh.tpl", { aptProxy: null }); + expect(out).toContain("apt-get update -qq"); + expect(out).toContain("===SU:SIMULATE==="); + expect(out).not.toContain("http_proxy"); + }); + + it("injecte le proxy quand fourni", () => { + const out = renderTemplate("apt/check.sh.tpl", { aptProxy: "http://cache:3142" }); + expect(out).toContain('http_proxy="http://cache:3142"'); + }); +}); diff --git a/server/templates/render.ts b/server/templates/render.ts new file mode 100644 index 0000000..c1b33fb --- /dev/null +++ b/server/templates/render.ts @@ -0,0 +1,16 @@ +// server/templates/render.ts +import Mustache from "mustache"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const TEMPLATES_ROOT = resolve(process.cwd(), "templates"); + +export interface TemplateVars { + aptProxy?: string | null; +} + +export function renderTemplate(relPath: string, vars: TemplateVars): string { + const tpl = readFileSync(resolve(TEMPLATES_ROOT, relPath), "utf8"); + // Mustache échappe le HTML par défaut; on désactive (ce sont des scripts shell). + return Mustache.render(tpl, vars, {}, { escape: (s) => s }); +} diff --git a/templates/apt/check.sh.tpl b/templates/apt/check.sh.tpl new file mode 100644 index 0000000..e3fe178 --- /dev/null +++ b/templates/apt/check.sh.tpl @@ -0,0 +1,12 @@ +#!/bin/sh +# Rendu sans sudo: le script entier est exécuté sous sudo par la couche SSH. +export LC_ALL=C +{{#aptProxy}}export http_proxy="{{aptProxy}}"; export https_proxy="{{aptProxy}}" +{{/aptProxy}} +echo "===SU:UPDATE===" +apt-get update -qq 2>&1 +echo "===SU:SIMULATE===" +apt-get -s -y full-upgrade 2>&1 +echo "===SU:REBOOT===" +if [ -f /var/run/reboot-required ]; then echo "REBOOT_REQUIRED=1"; else echo "REBOOT_REQUIRED=0"; fi +echo "===SU:END===" diff --git a/templates/apt/full-upgrade.sh.tpl b/templates/apt/full-upgrade.sh.tpl new file mode 100644 index 0000000..5c1ef13 --- /dev/null +++ b/templates/apt/full-upgrade.sh.tpl @@ -0,0 +1,11 @@ +#!/bin/sh +export LC_ALL=C +export DEBIAN_FRONTEND=noninteractive +{{#aptProxy}}export http_proxy="{{aptProxy}}"; export https_proxy="{{aptProxy}}" +{{/aptProxy}} +echo "===SU:UPGRADE===" +apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold full-upgrade 2>&1 +CODE=$? +echo "===SU:REBOOT===" +if [ -f /var/run/reboot-required ]; then echo "REBOOT_REQUIRED=1"; else echo "REBOOT_REQUIRED=0"; fi +echo "===SU:EXIT=${CODE}===" diff --git a/templates/apt/reboot.sh.tpl b/templates/apt/reboot.sh.tpl new file mode 100644 index 0000000..4a63017 --- /dev/null +++ b/templates/apt/reboot.sh.tpl @@ -0,0 +1,6 @@ +#!/bin/sh +export LC_ALL=C +echo "===SU:REBOOT_NOW===" +# Reboot différé pour laisser le canal SSH se fermer proprement. +nohup sh -c 'sleep 2; reboot' >/dev/null 2>&1 & +echo "reboot planifié"