xetup/web/spec/index.html
X9 Dev d20b0b439e
All checks were successful
release / build-and-release (push) Successful in 32s
fix(backinfo): set desktop background color in all profiles (no black border)
BackInfo paints a centered bitmap; when it is smaller than the screen the area
around it shows HKCU\Control Panel\Colors\Background, which was black in profiles
step 04 had not touched. Set the solid background (#223B47) in HKU\.DEFAULT and in
every existing user profile (loading each hive as needed), on top of the Default
hive and current user, so the border around the bitmap blends into the background.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 14:35:22 +02:00

1365 lines
62 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Specifikace &amp; anotace xetup</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<style>
:root {
--bg: #0f1117;
--card: #1a1d27;
--card2: #141720;
--border: #2a2d3a;
--text: #e0e0e0;
--muted: #888;
--accent: #223B47;
--accent-bright: #2d5266;
--green: #2ea043;
--blue: #58a6ff;
--yellow: #d29922;
--red: #da3633;
--purple: #8b949e;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
}
/* ---- HEADER ---- */
header {
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
align-items: center;
gap: .75rem;
position: sticky;
top: 0;
background: var(--bg);
z-index: 100;
}
.logo-text { font-size: 1.1rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
.logo-sub { font-size: .8rem; color: var(--muted); margin-left: .2rem; }
header nav { margin-left: auto; display: flex; gap: 1.5rem; }
header nav a { color: var(--muted); text-decoration: none; font-size: .85rem; transition: color .15s; }
header nav a:hover, header nav a.active { color: var(--text); }
/* ---- LAYOUT ---- */
.layout {
display: grid;
grid-template-columns: 220px 1fr;
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1.5rem;
gap: 2rem;
align-items: start;
}
/* ---- SIDEBAR ---- */
.sidebar {
position: sticky;
top: 57px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
padding: 1rem;
}
.sidebar h4 {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--muted);
margin-bottom: .75rem;
}
.sidebar a {
display: block;
color: var(--muted);
text-decoration: none;
font-size: .83rem;
padding: .3rem .4rem;
border-radius: 5px;
transition: background .1s, color .1s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar a:hover { background: var(--card2); color: var(--text); }
.sidebar-divider { border: none; border-top: 1px solid var(--border); margin: .6rem 0; }
/* ---- CONTENT ---- */
.content h1 {
font-size: 1.7rem;
font-weight: 800;
letter-spacing: -.03em;
color: #fff;
margin-bottom: .4rem;
}
.meta {
font-size: .82rem;
color: var(--muted);
margin-bottom: 2rem;
display: flex;
gap: 1.2rem;
flex-wrap: wrap;
align-items: center;
}
.meta a { color: var(--muted); text-decoration: none; }
.meta a:hover { color: var(--blue); }
/* ---- LEGEND ---- */
.legend {
display: flex;
gap: .6rem;
flex-wrap: wrap;
margin-bottom: 2rem;
padding: .8rem 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
font-size: .8rem;
}
.legend span { display: flex; align-items: center; gap: .3rem; color: var(--muted); }
/* ---- STEP CARD ---- */
.step {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 1.2rem;
overflow: hidden;
}
.step-header {
display: flex;
align-items: center;
gap: .75rem;
padding: .9rem 1.2rem;
border-bottom: 1px solid var(--border);
background: var(--card2);
}
.step-num {
font-size: .72rem;
font-weight: 700;
color: var(--muted);
font-family: monospace;
min-width: 30px;
}
.step-title {
font-size: .95rem;
font-weight: 600;
color: #fff;
flex: 1;
}
.step-body { padding: 1rem 1.2rem; }
/* ---- ITEM ROWS ---- */
.items { width: 100%; border-collapse: collapse; font-size: .83rem; }
.items td {
padding: .45rem .6rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
line-height: 1.45;
}
.items tr:last-child td { border-bottom: none; }
.items td:first-child { color: var(--text); width: 55%; }
.items td:last-child { color: var(--muted); width: 45%; font-size: .78rem; }
.items tr.flag-mustfix td:first-child { color: #f0853a; }
.items tr.flag-done td:first-child { color: var(--green); }
.items tr.flag-todo td:first-child { color: var(--yellow); }
.items tr.flag-open td:first-child { color: var(--blue); }
.items tr.flag-warn td:first-child { color: #da3633; }
/* ---- BADGES ---- */
.badge {
display: inline-block;
border-radius: 4px;
padding: .15rem .4rem;
font-size: .68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
white-space: nowrap;
margin-left: .3rem;
vertical-align: middle;
}
.badge-ok { background: #1a2e1a; color: var(--green); border: 1px solid var(--green); }
.badge-mustfix { background: #2e1c10; color: #f0853a; border: 1px solid #f0853a; }
.badge-todo { background: #2e250a; color: var(--yellow); border: 1px solid var(--yellow); }
.badge-open { background: #0d1e30; color: var(--blue); border: 1px solid var(--blue); }
.badge-warn { background: #2e0d0c; color: #da3633; border: 1px solid #da3633; }
.badge-future { background: #1e1e2e; color: var(--purple); border: 1px solid var(--purple); }
.badge-new { background: #0d1e30; color: #79c0ff; border: 1px solid #79c0ff; }
/* ---- ROW EXPAND ---- */
.items td.xp { width: 54px; padding: .25rem .3rem; text-align: right; vertical-align: middle; }
.xp-btn {
background: rgba(88,166,255,.08);
border: 1px solid rgba(88,166,255,.25);
color: var(--blue);
cursor: pointer; font-size: .7rem; padding: .15rem .4rem;
border-radius: 4px; line-height: 1.4; transition: background .1s, border-color .1s;
white-space: nowrap;
}
.xp-btn:hover { background: rgba(88,166,255,.18); border-color: var(--blue); }
.xp-btn.open { background: rgba(88,166,255,.2); border-color: var(--blue); }
.items tr.detail-tr td {
background: rgba(88,166,255,.04);
border-bottom: 1px solid var(--border);
padding: .8rem 1rem .8rem 1.2rem;
font-size: .82rem;
color: var(--muted);
}
.detail-heading {
font-size: .7rem; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted); margin-bottom: .4rem;
}
.detail-desc-script {
color: var(--text); line-height: 1.65; font-size: .85rem;
margin-bottom: .6rem; white-space: pre-wrap; word-break: break-word;
}
.detail-desc-script--empty { color: var(--muted); font-style: italic; }
.detail-note {
margin-top: .3rem; margin-bottom: .5rem; color: var(--muted);
font-size: .78rem; font-style: italic; line-height: 1.45;
}
.detail-divider {
border: none; border-top: 1px solid var(--border); margin: .65rem 0;
}
/* row comment thread */
.rcl { margin-bottom: .4rem; }
.rci { padding: .3rem 0; border-bottom: 1px dashed rgba(255,255,255,.06); font-size: .8rem; }
.rci:last-child { border-bottom: none; }
.rci-meta { font-size: .7rem; color: var(--muted); margin-bottom: .1rem; }
.rci-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.rcf { display: flex; gap: .35rem; align-items: flex-start; margin-top: .5rem; flex-wrap: wrap; }
.rcf-name {
width: 110px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .78rem;
padding: .28rem .45rem; font-family: inherit;
}
.rcf-text {
flex: 1; min-width: 140px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .8rem;
padding: .28rem .45rem; font-family: inherit; resize: none; min-height: 36px;
}
.rcf-name:focus, .rcf-text:focus { outline: none; border-color: var(--accent-bright); }
.rcf-send {
background: var(--accent-bright); color: #fff; border: none;
border-radius: 5px; padding: .28rem .6rem; font-size: .78rem;
cursor: pointer; white-space: nowrap; line-height: 1.4;
}
.rcf-send:hover { opacity: .85; }
.rcf-send:disabled { opacity: .35; cursor: default; }
/* ---- NOTES ---- */
.note {
margin-top: .75rem;
padding: .6rem .8rem;
background: rgba(88,166,255,.06);
border-left: 3px solid var(--accent-bright);
border-radius: 0 5px 5px 0;
font-size: .8rem;
color: var(--muted);
line-height: 1.5;
}
.note strong { color: var(--text); }
.note code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- STEP FOOTER ---- */
.step-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: .6rem 1.2rem;
border-top: 1px solid var(--border);
background: var(--card2);
gap: .5rem;
flex-wrap: wrap;
}
.step-footer code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
color: var(--muted);
}
.step-status { font-size: .78rem; color: var(--muted); }
/* ---- SECTION HEADING ---- */
.section-label {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--muted);
margin: 1.8rem 0 .7rem;
padding-bottom: .3rem;
border-bottom: 1px solid var(--border);
}
.section-label:first-child { margin-top: 0; }
/* ---- INLINE CODE ---- */
code {
font-family: monospace;
font-size: .82em;
background: rgba(255,255,255,.08);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- COMMENT WIDGET ---- */
.comment-widget { width: 100%; }
.comment-toggle {
background: none;
border: 1px dashed var(--border);
border-radius: 5px;
color: var(--muted);
font-size: .8rem;
padding: .25rem .7rem;
cursor: pointer;
transition: border-color .15s, color .15s;
display: flex;
align-items: center;
gap: .4rem;
}
.comment-toggle:hover { border-color: var(--blue); color: var(--blue); }
.comment-toggle .cnt {
background: var(--accent-bright);
color: #fff;
border-radius: 10px;
font-size: .7rem;
padding: .05rem .35rem;
display: none;
}
.comment-toggle .cnt.visible { display: inline; }
.comment-panel {
display: none;
margin-top: .5rem;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.comment-panel.open { display: block; }
.comment-list { padding: .5rem; }
.comment-item {
padding: .5rem .6rem;
border-bottom: 1px solid var(--border);
font-size: .82rem;
line-height: 1.5;
}
.comment-item:last-child { border-bottom: none; }
.comment-meta {
font-size: .72rem;
color: var(--muted);
margin-bottom: .25rem;
}
.comment-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.comment-empty {
padding: .6rem;
font-size: .8rem;
color: var(--muted);
text-align: center;
font-style: italic;
}
.comment-form {
border-top: 1px solid var(--border);
padding: .6rem;
display: flex;
flex-direction: column;
gap: .4rem;
}
.comment-input-name {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .8rem;
padding: .35rem .5rem;
resize: none;
font-family: inherit;
}
.comment-input {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .82rem;
padding: .4rem .5rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
line-height: 1.4;
}
.comment-input:focus, .comment-input-name:focus {
outline: none;
border-color: var(--accent-bright);
}
.comment-submit {
align-self: flex-end;
background: var(--accent-bright);
color: #fff;
border: none;
border-radius: 5px;
padding: .3rem .8rem;
font-size: .8rem;
cursor: pointer;
transition: opacity .15s;
}
.comment-submit:hover { opacity: .85; }
.comment-submit:disabled { opacity: .4; cursor: default; }
.comment-error { font-size: .75rem; color: var(--red); }
/* ---- NEW REQUEST WIDGET ---- */
.req-list {
margin-bottom: .8rem;
}
.req-item {
padding: .55rem .7rem;
border: 1px solid var(--border);
border-radius: 7px;
margin-bottom: .45rem;
font-size: .83rem;
line-height: 1.5;
background: var(--card2);
}
.req-item-meta { font-size: .72rem; color: var(--muted); margin-bottom: .2rem; }
.req-item-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.req-form { display: flex; flex-direction: column; gap: .45rem; }
.req-name {
width: 220px;
background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
color: var(--text); font-size: .8rem; padding: .35rem .5rem; font-family: inherit;
}
.req-text {
width: 100%;
background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
color: var(--text); font-size: .82rem; padding: .4rem .5rem;
resize: vertical; min-height: 70px; font-family: inherit; line-height: 1.4;
}
.req-name:focus, .req-text:focus { outline: none; border-color: var(--accent-bright); }
.req-submit {
align-self: flex-start;
background: var(--accent-bright); color: #fff; border: none;
border-radius: 5px; padding: .32rem .9rem; font-size: .8rem; cursor: pointer;
transition: opacity .15s;
}
.req-submit:hover { opacity: .85; }
.req-submit:disabled { opacity: .4; cursor: default; }
.req-error { font-size: .75rem; color: var(--red); margin-top: .1rem; }
footer {
border-top: 1px solid var(--border);
padding: 1.2rem 2rem;
text-align: center;
font-size: .8rem;
color: var(--muted);
margin-top: 3rem;
}
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); }
@media (max-width: 700px) {
.layout { grid-template-columns: 1fr; }
.sidebar { position: static; }
}
</style>
</head>
<body>
<header>
<div>
<a href="/" style="text-decoration:none;display:flex;align-items:center;gap:.6rem">
<img src="/x9-logo.jpeg" alt="X9.cz" style="height:28px;width:28px;border-radius:5px;object-fit:cover;">
<span class="logo-text">xetup</span><span class="logo-sub">by X9.cz</span>
</a>
</div>
<nav>
<a href="/navod/">Navod</a>
<a href="/spec/" class="active">Specifikace</a>
<a href="/changelog/">Changelog</a>
<a href="https://git.xetup.x9.cz/x9/xetup">Git</a>
<a href="https://git.xetup.x9.cz/x9/xetup/issues">Issues</a>
</nav>
</header>
<div class="layout">
<!-- SIDEBAR -->
<aside class="sidebar">
<h4>Kroky (scripty)</h4>
<a href="#step-00">00 &ndash; Admin ucet</a>
<a href="#step-01">01 &ndash; Bloatware</a>
<a href="#step-02">02 &ndash; Software (winget)</a>
<a href="#step-03">03 &ndash; Registry (HKLM)</a>
<a href="#step-04">04 &ndash; Default Profile + Personalizace</a>
<a href="#step-07">07 &ndash; BackInfo</a>
<a href="#step-08">08 &ndash; Aktivace Windows</a>
<a href="#step-09">09 &ndash; PC identita</a>
<a href="#step-10">10 &ndash; Network discovery</a>
<a href="#step-11">11 &ndash; Dell Command | Update</a>
<a href="#step-12">12 &ndash; Windows Update</a>
<hr class="sidebar-divider">
<h4>Architektura</h4>
<a href="#arch-xetup">xetup.exe (Go GUI)</a>
<a href="#arch-spec">spec.yaml</a>
<hr class="sidebar-divider">
<a href="#new-requests">+ Novy pozadavek</a>
</aside>
<!-- CONTENT -->
<div class="content">
<h1>Specifikace &amp; anotace</h1>
<div class="meta">
<span>Verze: 0.6</span>
<span>Datum: 2026-04-28</span>
<span><a href="https://git.xetup.x9.cz/x9/xetup">x9/xetup</a></span>
<span>Status: aktivni vyvoj</span>
</div>
<div class="legend">
<span><span class="badge badge-ok">OK</span> hotovo, v provozu</span>
<span><span class="badge badge-mustfix">Must fix</span> zname chyby, nutno opravit</span>
<span><span class="badge badge-todo">TODO</span> naplanovano, nerealizovano</span>
<span><span class="badge badge-open">Open</span> otevrena otazka</span>
<span><span class="badge badge-warn">Warn</span> potencialni problem</span>
<span><span class="badge badge-new">New</span> nova feature</span>
<span><span class="badge badge-future">Future</span> dlouhodoby plan</span>
</div>
<!-- ============================================================ -->
<p class="section-label">Stavajici kroky (scripty)</p>
<!-- STEP 00 -->
<div class="step" id="step-00">
<div class="step-header">
<span class="step-num">00</span>
<span class="step-title">Admin ucet (adminx9)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Vytvorit lokalni ucet <code>adminx9</code></td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Pridat do skupiny Administrators</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Skryt z login obrazovky (SpecialAccounts\UserList = 0)</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Heslo nevypirsi, uzivatel nesmeni heslo</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Zadne heslo (aktualne nastavovano z config.json)</td><td>Opraveno &ndash; prazdny SecureString, config.json heslo odstranen</td></tr>
<tr class="flag-done"><td>FullName = "X9.cz s.r.o." (via ADSI)</td><td>Opraveno &ndash; ADSI SetInfo() po vytvoreni uctu</td></tr>
</table>
<div class="note">
<strong>Proc bez hesla:</strong> Ucet je skryty pred uzivateli, slouzi pouze MSP adminstraci.
Heslo v config.json by bylo ulozene citelne.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>00-admin-account.ps1</code></span>
<div class="comment-widget" data-issue="1"></div>
</div>
</div>
<!-- STEP 01 -->
<div class="step" id="step-01">
<div class="step-header">
<span class="step-num">01</span>
<span class="step-title">Bloatware removal</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>AppX balicky &ndash; odstraneni pro vsechny uzivatele a provisioned</td><td>Remove-AppxPackage -AllUsers + Remove-AppxProvisionedPackage</td></tr>
<tr class="flag-done"><td>Zachovano: Microsoft.WindowsCalculator</td><td>Zamerny vyjimek</td></tr>
<tr class="flag-done"><td>Windows Capabilities (Fax, IE, OpenSSH, WMP, WordPad, …)</td><td>Remove-WindowsCapability</td></tr>
<tr class="flag-done"><td>Windows Optional Features (PS 2.0, MediaPlayback, Recall, …)</td><td>Disable-WindowsOptionalFeature</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>01-bloatware.ps1</code></span>
<div class="comment-widget" data-issue="2"></div>
</div>
</div>
<!-- STEP 02 -->
<div class="step" id="step-02">
<div class="step-header">
<span class="step-num">02</span>
<span class="step-title">Software (winget)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>7-Zip (<code>7zip.7zip</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Adobe Acrobat Reader 64-bit (<code>Adobe.Acrobat.Reader.64-bit</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>OpenVPN Connect (<code>OpenVPNTechnologies.OpenVPNConnect</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Atera Agent install (pod SYSTEM)</td><td>Invoke-WebRequest + <code>msiexec /i /qn</code> spustene jako <code>NT AUTHORITY\SYSTEM</code> pres docasny scheduled task. Pod SYSTEM se agent registruje tise <strong>bez MFA okna</strong> &ndash; bez zasahu technika.</td></tr>
<tr class="flag-done"><td>Adobe PDF default: .pdf -&gt; AcroRd32 po instalaci</td><td>OK &ndash; UCPD stop/start kolem zapisu asociace</td></tr>
<tr class="flag-done"><td>UCPD.sys (kernel driver, od Feb 2024) blokuje UserChoice</td><td>Stop-Service ucpd + 2s sleep + overeni zastaveni pred HKCR zapisem. Na Win11 24H2 je UCPD chranena sluzba a stop selze &ndash; logovano jako WARN (ne ERROR); HKCR zapis (system-wide) projde i tak.</td></tr>
<tr class="flag-done"><td>Winget parallel joby: timeout 600s + kill zavislych</td><td>Wait-Job -Timeout 600; po vyprseni Kill + Remove zavislych jobu</td></tr>
<tr class="flag-done"><td>Winget cesta explicitne predavana do parallel jobu</td><td>Opraveno &ndash; Start-Job nezdedi PATH; winget.exe fullpath preda jako argument. Exit 3010 (success+reboot) nyni vyhodnocen jako OK.</td></tr>
<tr class="flag-done"><td>Winget: <code>--source winget</code> u kazde instalace</td><td>Field fix &ndash; fresh Win11 ISO ma App Installer se starym pinned certem, msstore source padá na <code>0x8a15005e</code> a instalaci prerusi. <code>--source winget</code> msstore obejde. Plati i pro krok 11 (Dell).</td></tr>
<tr class="flag-done"><td>Atera: detekce pres sluzbu <code>Get-Service AteraAgent</code></td><td>Field fix &ndash; agent se obcas instaluje do <code>C:\ProgramData\</code>; existence sluzby je spolehlivejsi nez kontrola souboru. Fallback na cesty vc. ProgramData.</td></tr>
</table>
<div class="note">
<strong>Atera Agent URL:</strong><br>
<code>https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&amp;aeid=50b72e7113e54a63ac76b96c54c7e337</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>02-software.ps1</code></span>
<div class="comment-widget" data-issue="3"></div>
</div>
</div>
<!-- STEP 03 -->
<div class="step" id="step-03">
<div class="step-header">
<span class="step-num">03</span>
<span class="step-title">System Registry (HKLM)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Bypass NRO (OOBE\BypassNRO = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz auto-instalace Teams</td><td>ConfigureChatAutoInstall = 0</td></tr>
<tr class="flag-done"><td>Zakaz Cloud Optimized Content</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz Widgets / News and Interests</td><td>OK</td></tr>
<tr class="flag-done"><td>Hesla bez expirace (<code>net accounts /maxpwage:UNLIMITED</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Casova zona: Central Europe Standard Time</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz GameDVR</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz Smart App Control (Intelligent App Control)</td><td>HKLM\SYSTEM\CurrentControlSet\Control\CI\Policy\VerifiedAndReputablePolicyState = 0. Permanentni &ndash; bez reset OS nelze zapnout zpet.</td></tr>
<tr class="flag-done"><td>Zakaz hibernace (<code>powercfg /hibernate off</code>)</td><td>Odstrani hiberfil.sys, predchazi problemum s power stavy pri deployi</td></tr>
<tr class="flag-done"><td>Edge &ndash; Mandatory: zakaz First Run, telemetrie</td><td>HideFirstRunExperience=1, DefaultBrowserSettingEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0. Uzivatel nemuze zmenit.</td></tr>
<tr class="flag-done"><td>Edge &ndash; initial_preferences: UI defaults (uzivatel muze zmenit)</td><td>Zapise <code>C:\Program Files (x86)\Microsoft\Edge\Application\initial_preferences</code>. Edge precte jednou pri prvnim vytvoreni profilu. Nastavuje: oblibene vzdy viditelne, skryt Home, zakaz navrhu vyhledavani, cisty NTP, zakaz nakupniho asistenta, odmen a kolekci.</td></tr>
<tr class="flag-done"><td>OneDrive uninstall (intentional)</td><td>OneDriveSetup.exe /uninstall &ndash; odstrani pre-installed verzi. M365 si nainstaluje vlastni.</td></tr>
<tr class="flag-done"><td>Powercfg nastaveni (spotreba energie)</td><td>hibernate off; standby-ac 0, monitor-ac 60, standby-dc 30, monitor-dc 15</td></tr>
<tr class="flag-done"><td>Proxy auto-detect zakaz (AutoDetect = 0)</td><td>HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>03-system-registry.ps1</code></span>
<div class="comment-widget" data-issue="4"></div>
</div>
</div>
<!-- STEP 04 -->
<div class="step" id="step-04">
<div class="step-header">
<span class="step-num">04</span>
<span class="step-title">Default Profile + Personalizace</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Taskbar: zarovnat vlevo (TaskbarAl = 0)</td><td>Win11 default je center</td></tr>
<tr class="flag-done"><td>Taskbar: skryt Search, Copilot, Task View, Widgets, Chat</td><td>OK</td></tr>
<tr class="flag-done"><td>Taskbar: zobrazit vsechny ikonky v tray (EnableAutoTray = 0)</td><td>Win11 periodicky znovu skryva tray ikony po updatu</td></tr>
<tr class="flag-done"><td>Taskbar: explicitni pinlist (TaskbarLayoutModification.xml)</td><td>default/user: Pruzkumnik + Edge; admin: Pruzkumnik + Edge + PowerShell. <code>PinListPlacement="Replace"</code> &ndash; prazdny seznam by dovoloval Windows pridat Store a dalsi vychozi. Pruzkumnik se pinuje pres AUMID <code>DesktopApplicationID="Microsoft.Windows.Explorer"</code>, ne vlastni .lnk &ndash; ten by spoustel druhy Explorer a slo by ho spatne odepnout.</td></tr>
<tr class="flag-done"><td>Explorer: zobrazovat pripony souboru (HideFileExt = 0)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: otevrit na This PC (LaunchTo = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: ShowRecent = 0, ShowFrequent = 0</td><td>Skryt nedavne a caste soubory v Quick Access</td></tr>
<tr class="flag-done"><td>Explorer: FullPath = 1 (CabinetState)</td><td>Zobrazovat plnou cestu v titulku okna Explorera</td></tr>
<tr class="flag-done"><td>Start menu: vyprazdnit piny (Win11)</td><td>ConfigureStartPins = {"pinnedList":[]}</td></tr>
<tr class="flag-done"><td>Start menu: zakaz Bing vyhledavani</td><td>DisableSearchBoxSuggestions = 1</td></tr>
<tr class="flag-done"><td>Copilot: zakaz (TurnOffWindowsCopilot = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>NumLock zapnout pri startu (InitialKeyboardIndicators = 2)</td><td>OK</td></tr>
<tr class="flag-done"><td>Klavesnice: CZ primarni + US sekundarni</td><td>Field fix &ndash; Set-WinUserLanguageList (cs-CZ + en-US) pro aktualniho uzivatele; Preload (<code>1=00000405</code> CZ, <code>2=00000409</code> US) do Default hive i <code>HKU\.DEFAULT</code> (welcome screen + systemove ucty). Aplikuje se vzdy automaticky.</td></tr>
<tr class="flag-done"><td>System tema (taskbar, Start): Dark</td><td>OK</td></tr>
<tr class="flag-done"><td>Aplikacni tema: Light</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva: #223B47 (tmave modroseda)</td><td>AccentColor DWORD = 0xFF473B22 (ABGR) + <strong>AccentPalette</strong> (REG_BINARY, 8 odstinu). Bez AccentPalette Win11 vlastni accent na Startu/taskbaru ignoruje a spadne na default &ndash; proto se barvy „neaplikovaly vsude".</td></tr>
<tr class="flag-done"><td>Accent barva na Start a taskbaru (ColorPrevalence = 1)</td><td>Themes\Personalize i DWM\ColorPrevalence = 1 (Start/taskbar i zahlavi a ohraniceni oken). Rezim Custom: tmavy system + svetle aplikace.</td></tr>
<tr class="flag-done"><td>Motiv aplikovan ve vsech profilech</td><td>Default hive (novi uzivatele) + aktualni HKCU + <code>HKU\.DEFAULT</code> (lock/welcome screen, systemovy kontext).</td></tr>
<tr class="flag-done"><td>Pruhlednost: vypnuta (EnableTransparency = 0)</td><td>OK</td></tr>
<tr class="flag-done"><td>Tapeta: jednobarevna #223B47; Wallpaper="" v default hive</td><td>BackInfo prepise tapetu svym BMP; prazdny retezec = solid barva, ne cerna plocha</td></tr>
<tr class="flag-done"><td>OneDrive RunOnce klic odstranen</td><td>Odstraneni branil reinstalaci pres M365 &ndash; klice smazany ze skriptu</td></tr>
</table>
<div class="note">
<strong>Metoda:</strong> <code>reg load HKU\DefaultProfile C:\Users\Default\NTUSER.DAT</code>
&rarr; zapsat zmeny &rarr; <code>reg unload HKU\DefaultProfile</code>.<br>
Personalizace (barvy, tapeta) je soucasti tohoto kroku &ndash; jeden hive load/unload,
zadna kontigence s dalsimi skripty. BackInfo.exe (krok 07) prepise tapetu BMP pri kazdem logonu.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>04-default-profile.ps1</code></span>
<div class="comment-widget" data-issue="5"></div>
</div>
</div>
<!-- STEP 07 -->
<div class="step" id="step-07">
<div class="step-header">
<span class="step-num">07</span>
<span class="step-title">BackInfo (systemovy info na tapete)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td><code>07-desktop-info.ps1</code> SMAZAT &ndash; stary pristup</td><td>Nahrazeno novym <code>07-backinfo.ps1</code></td></tr>
<tr class="flag-done"><td>Zkopirovat <code>assets/Backinfo/</code> do <code>C:\Program Files\Backinfo\</code></td><td>Implementovano v 07-backinfo.ps1</td></tr>
<tr class="flag-done"><td>Spustit <code>backinfo_W11.ps1</code> (detekce OS, registry, Startup)</td><td>Logika inlinovana v 07-backinfo.ps1</td></tr>
<tr class="flag-done"><td>BackInfo.exe v assets/Backinfo/ k dispozici</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>BackInfo auto-start pri kazdem logonu via Startup shortcut</td><td>Shortcut do ProgramData\StartUp vytvori 07-backinfo.ps1</td></tr>
<tr class="flag-done"><td>Pozadi #223B47 (<code>BackgroundColor = 4668194</code>)</td><td>BackInfo bere hodnotu jako COLORREF (<code>0x00BBGGRR</code>, BGR): B=71 G=59 R=34 = 4668194. Hodnota 2243399 (RGB) se cetla jako COLORREF, prohodila R/B a renderovala olivove hnedou (#473B22).</td></tr>
<tr class="flag-done"><td>Desktopova barva pozadi #223B47 ve vsech profilech</td><td>BackInfo dela vystredenou bitmapu; kdyz je mensi nez obrazovka, okolo prosvita <code>Control Panel\Colors\Background</code>. Nastavuje se v Default hive, aktualnim HKCU, <code>HKU\.DEFAULT</code> i v kazdem existujicim profilu &ndash; jinak byl okraj cerny.</td></tr>
</table>
<div class="note">
<strong>BackInfo.ini konfiguruje:</strong> hostname (velky, centrovan), uzivatelske jmeno,
OS verze, HW info (CPU, RAM, disk), sitove informace (IP, hostname).<br><br>
<strong>Proc BackInfo misto vlastniho PS:</strong>
BackInfo.exe podporuje Win10 i Win11 bez specialnich hacku, je stabilni a uz je v assets.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>07-backinfo.ps1</code></span>
<div class="comment-widget" data-issue="8"></div>
</div>
</div>
<!-- STEP 08 -->
<div class="step" id="step-08">
<div class="step-header">
<span class="step-num">08</span>
<span class="step-title">Windows aktivace</span>
<span class="badge badge-ok">OK</span>
<span class="badge badge-open">Open</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>OA3 BIOS/UEFI klic &ndash; kontrola embedded key</td><td>WMI: SoftwareLicensingService.OA3xOriginalProductKey</td></tr>
<tr class="flag-done"><td>Klic z config.json (<code>activation.productKey</code>)</td><td>OK &ndash; priorita nad OA3 a GVLK</td></tr>
<tr class="flag-done"><td>Fallback na GVLK (KMS client key) dle EditionID</td><td>Registry <code>EditionID</code> (nelokalizovany) misto Win32_OperatingSystem.Caption</td></tr>
<tr class="flag-done"><td>Volitelny KMS server (<code>activation.kmsServer</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Preskocit pokud jiz aktivovano</td><td>OK</td></tr>
<tr class="flag-open"><td>Typ klice: MAK vs KMS vs retail?</td><td>Zavisi na klientovi &ndash; otevrena otazka</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>08-activation.ps1</code></span>
<div class="comment-widget" data-issue="9"></div>
</div>
</div>
<!-- STEP 09 -->
<div class="step" id="step-09">
<div class="step-header">
<span class="step-num">09</span>
<span class="step-title">PC identita &ndash; Rename + C:\X9</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Rename-Computer dle parametru z GUI nebo config.json</td><td><code>deployment.pcName</code> v config.json; preskoci pokud neni nastaveno</td></tr>
<tr class="flag-done"><td>Nastavit popis pocitace (Computer Description)</td><td>LanmanServer\Parameters\SrvComment; default "X9 deployment"</td></tr>
<tr class="flag-done"><td>Vytvorit <code>C:\X9\</code> adresarovou strukturu</td><td>C:\X9\Logs, Scripts, Assets</td></tr>
<tr class="flag-done"><td>Vlastni ikonka pro <code>C:\X9\</code> slozku</td><td>Desktop.ini + X9-ikona.ico z assets\Logo\</td></tr>
</table>
<div class="note">
Rename-Computer vyzaduje restart. Tento krok bezi jako posledni pred finalnim shrnutim &ndash; po nem nasleduje automaticky odpocet a restart v GUI.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>09-pc-identity.ps1</code></span>
<div class="comment-widget" data-issue="12"></div>
</div>
</div>
<!-- STEP 10 -->
<div class="step" id="step-10">
<div class="step-header">
<span class="step-num">10</span>
<span class="step-title">Network discovery + firewall</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Nastavit sitovy profil jako Private (ne Public)</td><td>Set-NetConnectionProfile pro vsechny pripojene adaptery</td></tr>
<tr class="flag-done"><td>Povolit ping (ICMP) pro diagnostiku</td><td>Enable-NetFirewallRule: FPS-ICMP4-ERQ-In + FPS-ICMP6-ERQ-In</td></tr>
<tr class="flag-done"><td>Zapnout Network Discovery pro Private profil</td><td>Set-NetFirewallRule <code>-Group "@FirewallAPI.dll,-32752"</code> (resource string, nezavisly na jazyku) + netsh fallback. Field fix: drivejsi <code>-DisplayGroup "Network Discovery"</code> selhalo na ceske lokalizaci Win11.</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>10-network.ps1</code></span>
<div class="comment-widget" data-issue="10"></div>
</div>
</div>
<!-- STEP 11 -->
<div class="step" id="step-11">
<div class="step-header">
<span class="step-num">11</span>
<span class="step-title">Dell Command | Update</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Detekce Dell hardware (<code>Win32_ComputerSystem</code>)</td><td>Non-Dell stroj krok preskoci bez chyby &ndash; stejny skript pro vsechny HW</td></tr>
<tr class="flag-done"><td>Instalace Dell Command | Update via winget</td><td><code>Dell.CommandUpdate.Universal</code> &ndash; silent, Win10 + Win11</td></tr>
<tr class="flag-done"><td>Spusteni vsech aktualizaci: drivery, firmware, BIOS</td><td><code>dcu-cli.exe /applyUpdates -silent -reboot=disable</code></td></tr>
<tr class="flag-done"><td>BIOS/firmware se staging &ndash; dokonci se pri restartu</td><td>Restart na konci deploymetu vse dokonci</td></tr>
</table>
<div class="note">
Non-Dell stroje: krok se preskoci automaticky, zadna chyba. Dell Latitude, OptiPlex,
Precision, Vostro, XPS &ndash; vsechny podporovane DCU Universal.<br><br>
<strong>Casova narocnost:</strong> 5&ndash;20 minut podle poctu dostupnych aktualizaci a rychlosti sitoveho pripojeni.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>11-dell-update.ps1</code></span>
<div class="comment-widget" data-issue="16"></div>
</div>
</div>
<!-- STEP 12 -->
<div class="step" id="step-12">
<div class="step-header">
<span class="step-num">12</span>
<span class="step-title">Windows Update</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Instalace NuGet providera + PSWindowsUpdate modulu ze PSGallery</td><td>Install-PackageProvider + Install-Module PSWindowsUpdate -Force</td></tr>
<tr class="flag-done"><td>Prvni pruchod aktualizaci (bez rebootu)</td><td>Install-WindowsUpdate -AcceptAll -IgnoreReboot. Vystup formatovan pres <code>$_.Result</code> + <code>$_.Title</code> (drive log zaplaval radky "System.__ComObject").</td></tr>
<tr class="flag-done"><td>Reboot cyklus (exit 9) &ndash; pokracovani pres xetup resume</td><td>Exit 9 = reboot needed; xetup ulozi stav, nastavi autologon + X9-Resume task, restartuje</td></tr>
<tr class="flag-done"><td>Automaticky restart po skonceni deploymetu (GUI odpocet)</td><td>xetup.exe zobrazi 60s odpocet + tlacitka "Restartovat ted" / "Zrusit restart"</td></tr>
</table>
<div class="note">
Windows Update typicky vyzaduje vice restartovacich kolu. Kazdy pruchod skonci exit 9 (dalsi updaty) nebo exit 0 (hotovo).
xetup resume cyklus automaticky opakuje az do uplne aktualizace. Max 5 rebootu na krok (ochrana proti smycce).<br><br>
<strong>Pozor:</strong> Prvni restart spusti jak Windows Update, tak Dell firmware staging (krok 11).
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>12-windows-update.ps1</code></span>
<div class="comment-widget" data-issue="17"></div>
</div>
</div>
<!-- TASKBAR PROFILY - detail implementace kroku 04 -->
<div class="step" id="step-taskbar">
<div class="step-header">
<span class="step-num">04+</span>
<span class="step-title">Taskbar pinned apps (profily)</span>
<span class="badge badge-ok">OK</span>
<span class="badge badge-open">Open</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td><code>-ProfileType</code> parametr: default / admin / user</td><td>Parametr GUI nebo config.json; predano do skriptu 04</td></tr>
<tr class="flag-done"><td>XML layout pro "default" a "user": Pruzkumnik + Edge</td><td><code>PinListPlacement="Replace"</code> &ndash; explicitni seznam zamezuje pridani Store a dalsich vychozich appek</td></tr>
<tr class="flag-done"><td>XML layout pro "admin": Pruzkumnik + Edge + PowerShell</td><td>TaskbarLayoutModification.xml; PowerShell.lnk z System32</td></tr>
<tr class="flag-open"><td>Win11 24H2 kompatibilita layoutu</td><td>24H2 vyzaduje ProvisionedLayoutModification.xml &ndash; nutno otestovat na realne instalaci</td></tr>
</table>
<div class="note">
Layout se pri aplikaci zamkne. UnlockStartLayout scheduled task (registrovany v kroku 04) ho odemkne 5 min po startu, aby uzivatel mohl dale upravovat.
</div>
</div>
<div class="step-footer">
<span class="step-status">Integrovan do <code>04-default-profile.ps1</code></span>
<div class="comment-widget" data-issue="13"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Architektura</p>
<!-- ARCH XETUP -->
<div class="step" id="arch-xetup">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">xetup.exe &ndash; Go GUI (Walk / Win32)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Single binary (go:embed scripty + assets)</td><td><code>embed.go</code> + <code>cmd/xetup/main.go</code>; ~5 MB .exe, zadne externi zavislosti</td></tr>
<tr class="flag-done"><td>Walk GUI &ndash; Win32 nativni ovladky, bez OpenGL</td><td>Funguje na VMware ESXi / SVGA II (Fyne/OpenGL nefungovalo). <code>internal/gui/gui.go</code></td></tr>
<tr class="flag-done"><td>Formular: PC jmeno, popis, product key, profil</td><td>Faze 1 &ndash; config form pred spustenim</td></tr>
<tr class="flag-done"><td>Checklist kroku (on/off per-feature) + nacist / ulozit config.json</td><td>Tlacitka "Nacist config..." a "Ulozit config..." pro per-klient presety</td></tr>
<tr class="flag-done"><td>Live log + prubehovy strip kroku (0&ndash;12)</td><td>Barevne indikatory: · cekajici, &#x25ba; bezici (modra), &#x2713; OK (zelena), &#x2717; chyba (cervena)</td></tr>
<tr class="flag-done"><td>Summary s odpoctem a automatickym restartem</td><td>Faze 3 &ndash; 60s odpocet, tlacitka "Restartovat ted" / "Zrusit restart"</td></tr>
<tr class="flag-done"><td>UAC requireAdministrator (app.manifest + rsrc)</td><td>Windows vyzada elevaci pri spusteni; ComCtl32 v6 + DPI awareness</td></tr>
<tr class="flag-done"><td>PowerShell okno skryte na pozadi</td><td>SysProcAttr.HideWindow = true; PS chyby filtrovane (At line:, CategoryInfo atd.)</td></tr>
<tr class="flag-done"><td>Watchdog timer: 30 min ticho = kill skriptu</td><td>time.AfterFunc s Reset pri kazdem vystupu &ndash; zabiji zavisly PS proces</td></tr>
<tr class="flag-done"><td>Reboot loop ochrana: max 5 pokusu na krok</td><td>RetryCounts v state.json; po 5 restartech krok oznacen ERROR a preskocen</td></tr>
<tr class="flag-done"><td>Atomicky zapis state.json (write tmp + rename)</td><td>Prevence korupce pri vypadku napajeni behem zapisu</td></tr>
<tr class="flag-done"><td>E-mail report: 3 pokusy + lokalni HTML fallback</td><td>Backoff 0/1/5s; vzdy ulozi <code>C:\X9\report.html</code> bez ohledu na SMTP</td></tr>
<tr class="flag-done"><td>Hive unload retry (5 pokusu, GC pred kazdym)</td><td>Prevence "hive in use" chyby pri reg unload ntuser.dat</td></tr>
<tr class="flag-done"><td>Resume fix: StepsByIDs nyni nastavuje Enabled=true</td><td>Opravena kriticka chyba &ndash; resume mode preskakoval vsechny kroky</td></tr>
<tr class="flag-done"><td>Code signing: Azure Trusted Signing (jsign)</td><td>CI podepise <code>xetup.exe</code> certem <strong>X9.cz s.r.o.</strong> + RFC3161 timestamp. Trusted Signing cert je kratkodoby (~3 dny) &ndash; timestamp udrzi podpis platny i po expiraci. SmartScreen/UAC pak ukaze overeneho vydavatele.</td></tr>
<tr class="flag-todo"><td>Self-update: stahnout novou verzi z xetup.x9.cz/dl</td><td>Overit hash pred spustenim</td></tr>
<tr class="flag-future"><td>config.json: per-klient preset na USB</td><td>Jmeno PC prefix, SW seznam, klic &ndash; lezi vedle .exe</td></tr>
</table>
<div class="note">
<strong>Struktura:</strong> <code>cmd/xetup/</code>, <code>internal/config/</code>, <code>internal/gui/</code>, <code>internal/runner/</code><br><br>
<strong>Go zavislosti:</strong> github.com/lxn/walk (Win32 GUI), golang.org/x/sys<br><br>
<strong>Build:</strong> <code>CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -H windowsgui" ./cmd/xetup/</code><br>
CI (Forgejo Actions) sestavi, <strong>podepise</strong> (Azure Trusted Signing, cert X9.cz s.r.o. + RFC3161 timestamp pres jsign) a publikuje automaticky pri kazdem push na main.<br><br>
<strong>Spolehlivost (v0.6):</strong> Watchdog zabiji zavisle skripty po 30 min ticha. Reboot-loop ochrana omezi opakovani kazdeho kroku na 5 restartu.
State file se zapisuje atomicky (tmp+rename). E-mail report se zkusi 3x s backoff a vzdy ulozi lokalni HTML kopii.
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: v provozu &ndash; <a href="https://xetup.x9.cz/dl" style="color:var(--blue)">xetup.x9.cz/dl</a></span>
<div class="comment-widget" data-issue="11"></div>
</div>
</div>
<!-- ARCH SPEC -->
<div class="step" id="arch-spec">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">spec.yaml &ndash; single source of truth</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Popis vsech kroku: id, label, script, default</td><td>xetup.exe cte spec.yaml pro TUI checklist</td></tr>
<tr class="flag-todo"><td>Pole "requires" (napr. activation vyzaduje productKey)</td><td>TUI upozorni pokud chybi</td></tr>
<tr class="flag-future"><td>Auto-generovana dokumentace z spec.yaml</td><td>CI akce: spec.yaml &rarr; tato stranka</td></tr>
<tr class="flag-future"><td>spec.yaml jako SSOT pro tuto stranku i deploy skripty</td><td>Idealni stav: stranka vzdy odpovida kodu</td></tr>
</table>
<div class="note">
<strong>Navrh struktury spec.yaml:</strong><br>
<code style="display:block; white-space:pre; line-height:1.6">steps:
- id: admin-account
label: "Admin account (adminx9)"
script: 00-admin-account.ps1
default: true
- id: activation
label: "Windows activation"
script: 08-activation.ps1
default: true
requires: [productKey]</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: navrh, zatim zadny soubor</span>
<div class="comment-widget" data-issue="14"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Nove nastaveni &ndash; pozadavky</p>
<!-- NEW REQUESTS -->
<div class="step" id="new-requests">
<div class="step-header">
<span class="step-num" style="font-size:1.1rem">+</span>
<span class="step-title">Novy pozadavek na automatizaci</span>
<span class="badge badge-new">Pozadavky</span>
</div>
<div class="step-body">
<p style="color:var(--muted);font-size:.84rem;line-height:1.5;margin-bottom:1rem">
Chcete automatizovat neco, co skript zatim neresi?
Napiste pozadavek sem &ndash; ulozi se do repozitare.
Technicky tym ho projde a zaradi do planu.
</p>
<div class="req-list" id="req-list">
<div style="font-size:.8rem;color:var(--muted);font-style:italic">Nacitam pozadavky...</div>
</div>
<div class="req-form" id="req-form">
<input class="req-name" id="req-name" placeholder="Vase jmeno (volitelne)" maxlength="60">
<textarea class="req-text" id="req-text" placeholder="Co by mel automat delat? Popiste konkretne, idealne i proc." rows="3"></textarea>
<button class="req-submit" id="req-submit">Odeslat pozadavek</button>
<span class="req-error" id="req-error"></span>
</div>
</div>
</div>
</div><!-- /content -->
</div><!-- /layout -->
<footer>
&copy; 2026 <a href="https://x9.cz">X9.cz s.r.o.</a>
&nbsp;&middot;&nbsp;
<a href="https://git.xetup.x9.cz/x9/xetup">Forgejo</a>
&nbsp;&middot;&nbsp;
<a href="/">xetup.x9.cz</a>
&nbsp;&middot;&nbsp;
web: <span id="deploy-sha" style="font-family:monospace">...</span>
</footer>
<script>
(function() {
const API = '/forgejo-api';
const REPO = 'x9/xetup';
const TOKEN = 'e67f674af71847c4349b79b51d2b66a1ea41d031';
const HEADS = { 'Authorization': 'token ' + TOKEN, 'Content-Type': 'application/json' };
function timeAgo(iso) {
const s = Math.floor((Date.now() - new Date(iso)) / 1000);
if (s < 60) return 'prave ted';
if (s < 3600) return Math.floor(s/60) + ' min';
if (s < 86400)return Math.floor(s/3600) + ' hod';
return Math.floor(s/86400) + ' dni';
}
function renderComments(list, listEl) {
listEl.innerHTML = '';
if (!list.length) {
listEl.innerHTML = '<div class="comment-empty">Zatim zadne komentare. Bud prvni.</div>';
return;
}
list.forEach(c => {
const el = document.createElement('div');
el.className = 'comment-item';
const name = c.user.login === 'xetup-bot'
? (c.body.match(/^\*\*(.+?)\*\*/) ? c.body.match(/^\*\*(.+?)\*\*/)[1] : 'anon')
: c.user.login;
const body = c.body.replace(/^\*\*.+?\*\*\n/, '');
el.innerHTML =
'<div class="comment-meta">' + name + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="comment-body">' + body.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div>';
listEl.appendChild(el);
});
}
function initWidget(el) {
const issue = el.dataset.issue;
if (!issue || issue === '0') return;
el.innerHTML =
'<button class="comment-toggle" title="Komentare k teto sekci">' +
'&#128172; Komentare <span class="cnt"></span>' +
'</button>' +
'<div class="comment-panel">' +
'<div class="comment-list"><div class="comment-empty">Nacitam...</div></div>' +
'<div class="comment-form">' +
'<input class="comment-input-name" placeholder="Tvoje jmeno (volitelne)" maxlength="40">' +
'<textarea class="comment-input" placeholder="Napiste komentar..."></textarea>' +
'<div style="display:flex;gap:.5rem;align-items:center;justify-content:flex-end">' +
'<span class="comment-error"></span>' +
'<button class="comment-submit">Odeslat</button>' +
'</div>' +
'</div>' +
'</div>';
const toggle = el.querySelector('.comment-toggle');
const panel = el.querySelector('.comment-panel');
const listEl = el.querySelector('.comment-list');
const nameEl = el.querySelector('.comment-input-name');
const textEl = el.querySelector('.comment-input');
const submitEl= el.querySelector('.comment-submit');
const errEl = el.querySelector('.comment-error');
const cntEl = el.querySelector('.cnt');
let loaded = false;
let comments = [];
function load() {
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
comments = Array.isArray(data) ? data : [];
renderComments(comments, listEl);
if (comments.length) {
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
}
loaded = true;
})
.catch(() => { listEl.innerHTML = '<div class="comment-empty">Chyba nacitani.</div>'; });
}
toggle.addEventListener('click', () => {
const open = panel.classList.toggle('open');
if (open && !loaded) load();
});
submitEl.addEventListener('click', () => {
const text = textEl.value.trim();
if (!text) { errEl.textContent = 'Komentar nesmi byt prazdny.'; return; }
errEl.textContent = '';
submitEl.disabled = true;
submitEl.textContent = 'Odesilam...';
const name = nameEl.value.trim() || 'anon';
const body = '**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(c => {
comments.push(c);
renderComments(comments, listEl);
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
textEl.value = '';
nameEl.value = '';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
})
.catch(() => {
errEl.textContent = 'Chyba odeslani. Zkus znovu.';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
});
});
}
document.querySelectorAll('.comment-widget').forEach(initWidget);
// ---- DESCRIPTIONS FROM scripts/*.ps1 headers ----
let DESCRIPTIONS = {};
fetch('/data/descriptions.json')
.then(r => r.json())
.then(d => { DESCRIPTIONS = d; })
.catch(() => {});
// Map spec step id -> script id
const STEP_SCRIPT = {
'step-00': '00-admin-account',
'step-01': '01-bloatware',
'step-02': '02-software',
'step-03': '03-system-registry',
'step-04': '04-default-profile',
'step-07': '07-backinfo',
'step-08': '08-activation',
'step-09': '09-pc-identity',
'step-10': '10-network',
'step-11': '11-dell-update',
'step-12': '12-windows-update',
};
function getItemDesc(stepId, slug) {
const scriptId = STEP_SCRIPT[stepId];
if (!scriptId || !DESCRIPTIONS[scriptId]) return null;
return DESCRIPTIONS[scriptId].items[slug] || null;
}
// ---- ROW-LEVEL EXPAND + COMMENTS ----
function rSlug(txt) {
return txt.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').substring(0, 40);
}
function esc(s) { return s.replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function renderRowComments(all, slug, listEl) {
const prefix = '[row:' + slug + ']';
const relevant = all.filter(c => c.body.startsWith(prefix));
listEl.innerHTML = '';
if (!relevant.length) {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted);font-style:italic;padding:.2rem 0">Zatim zadne komentare.</div>';
return;
}
relevant.forEach(c => {
const body = c.body.slice(prefix.length + 1);
const m = body.match(/^\*\*(.+?)\*\*\n([\s\S]*)$/);
const name = m ? m[1] : 'anon';
const text = m ? m[2] : body;
const el = document.createElement('div');
el.className = 'rci';
el.innerHTML = '<div class="rci-meta">' + esc(name) + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="rci-body">' + esc(text) + '</div>';
listEl.appendChild(el);
});
}
function enhanceRows() {
document.querySelectorAll('.step').forEach(step => {
const widget = step.querySelector('.comment-widget');
const issueNum = widget ? widget.dataset.issue : null;
const stepId = step.id;
step.querySelectorAll('.items tr:not(.detail-tr)').forEach(tr => {
const cells = tr.querySelectorAll('td');
if (!cells.length) return;
const rowText = cells[0].textContent.trim();
const noteText = cells[1] ? cells[1].textContent.trim() : '';
const slug = rSlug(rowText);
// Expand button as last cell
const xpCell = document.createElement('td');
xpCell.className = 'xp';
const btn = document.createElement('button');
btn.className = 'xp-btn';
btn.innerHTML = 'detail ▸';
btn.title = 'Zobrazit detail a komentare';
xpCell.appendChild(btn);
tr.appendChild(xpCell);
// Detail row
const dtr = document.createElement('tr');
dtr.className = 'detail-tr';
dtr.style.display = 'none';
const dtd = document.createElement('td');
dtd.colSpan = 3;
// Description from script header (loaded async - may not be ready yet,
// will be filled in when detail opens)
const scriptDesc = () => getItemDesc(stepId, slug);
dtd.innerHTML =
// description from script header
'<div class="detail-heading">Co to dela</div>' +
'<div class="detail-desc-script"></div>' +
// brief note from col 2 as secondary context
(noteText ? '<div class="detail-note">' + esc(noteText) + '</div>' : '') +
'<hr class="detail-divider">' +
// comments
'<div class="detail-heading">Komentare</div>' +
'<div class="rcl"></div>' +
'<div class="rcf">' +
'<input class="rcf-name" placeholder="Jmeno" maxlength="40">' +
'<textarea class="rcf-text" placeholder="Komentar k tomuto radku..." rows="2"></textarea>' +
'<button class="rcf-send">Odeslat</button>' +
'</div>';
dtr.appendChild(dtd);
tr.after(dtr);
let loaded = false;
let allComments = [];
const getAll = () => allComments;
btn.addEventListener('click', () => {
const opening = dtr.style.display === 'none';
dtr.style.display = opening ? '' : 'none';
btn.classList.toggle('open', opening);
btn.innerHTML = opening ? 'detail ▾' : 'detail ▸';
if (opening) {
// Fill script description (JSON may now be loaded)
const descEl = dtd.querySelector('.detail-desc-script');
const desc = scriptDesc();
if (desc) {
descEl.textContent = desc;
descEl.className = 'detail-desc-script';
} else {
descEl.textContent = 'Popis bude dostupny po spusteni tools/extract-docs.py.';
descEl.className = 'detail-desc-script detail-desc-script--empty';
}
}
if (opening && !loaded && issueNum) {
loaded = true;
const listEl = dtd.querySelector('.rcl');
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted)">Nacitam...</div>';
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
allComments = Array.isArray(data) ? data : [];
renderRowComments(allComments, slug, listEl);
})
.catch(() => {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--red)">Chyba nacitani.</div>';
});
}
});
dtd.querySelector('.rcf-send').addEventListener('click', function() {
const nameEl = dtd.querySelector('.rcf-name');
const textEl = dtd.querySelector('.rcf-text');
const text = textEl.value.trim();
if (!text || !issueNum) return;
this.disabled = true;
const name = nameEl.value.trim() || 'anon';
const body = '[row:' + slug + ']\n**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => r.json())
.then(c => {
allComments.push(c);
renderRowComments(allComments, slug, dtd.querySelector('.rcl'));
textEl.value = ''; nameEl.value = '';
this.disabled = false;
})
.catch(() => { this.disabled = false; });
});
});
});
}
// ---- NEW-REQUEST WIDGET (issue #15) ----
(function() {
const ISSUE = 15;
const listEl = document.getElementById('req-list');
const nameEl = document.getElementById('req-name');
const textEl = document.getElementById('req-text');
const submitEl = document.getElementById('req-submit');
const errorEl = document.getElementById('req-error');
if (!listEl) return;
function renderRequests(data) {
listEl.innerHTML = '';
if (!data.length) {
listEl.innerHTML = '<div style="font-size:.8rem;color:var(--muted);font-style:italic;margin-bottom:.5rem">Zatim zadne pozadavky. Bud prvni.</div>';
return;
}
data.forEach(c => {
const m = c.body.match(/^\*\*(.+?)\*\*\n([\s\S]*)$/);
const name = m ? m[1] : (c.user.login === 'xetup-bot' ? 'anon' : c.user.login);
const text = m ? m[2] : c.body;
const el = document.createElement('div');
el.className = 'req-item';
el.innerHTML =
'<div class="req-item-meta">' + name.replace(/</g,'&lt;') + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="req-item-body">' + text.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div>';
listEl.appendChild(el);
});
}
fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', { headers: HEADS })
.then(r => r.json())
.then(d => renderRequests(Array.isArray(d) ? d : []))
.catch(() => { listEl.innerHTML = '<div style="font-size:.8rem;color:var(--red)">Chyba nacitani.</div>'; });
submitEl.addEventListener('click', () => {
const text = textEl.value.trim();
if (!text) { errorEl.textContent = 'Pozadavek nesmi byt prazdny.'; return; }
errorEl.textContent = '';
submitEl.disabled = true;
submitEl.textContent = 'Odesilam...';
const name = nameEl.value.trim() || 'anon';
const body = '**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(c => {
textEl.value = ''; nameEl.value = '';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat pozadavek';
// re-fetch to show updated list
return fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', { headers: HEADS });
})
.then(r => r.json())
.then(d => renderRequests(Array.isArray(d) ? d : []))
.catch(() => {
errorEl.textContent = 'Chyba odeslani. Zkus znovu.';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat pozadavek';
});
});
})();
enhanceRows();
// Show deployed commit in footer
fetch('/data/deploy.json')
.then(r => r.json())
.then(d => {
const el = document.getElementById('deploy-sha');
if (el && d.sha) {
const ts = d.ts ? ' (' + new Date(d.ts).toLocaleString('cs-CZ', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) + ')' : '';
el.textContent = d.sha + ts;
el.title = d.ts || '';
}
})
.catch(() => {});
})();
</script>
</body>
</html>