{"id":487694,"date":"2026-07-17T16:37:47","date_gmt":"2026-07-17T16:37:47","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=487694"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=487694","title":{"rendered":"\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a \u043d\u0430 Go Wails: \u041f\u043e\u043b\u043d\u044b\u0439 \u0433\u0430\u0439\u0434. \u0421\u0432\u043e\u044f self-hosted \u0434\u0432\u0443\u0445\u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f VPN-\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u0427\u0410\u0421\u0422\u042c 3"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c 2 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u043f\u0440\u043e \u043c\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 <a href=\"https:\/\/habr.com\/ru\/articles\/1059486\/\" rel=\"noopener noreferrer nofollow\">ServeHub-2<\/a>. \u0417\u0434\u0435\u0441\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u0440\u0435\u0447\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u0430 \u043d\u0430 Go Wails v2.<\/p>\n<p>\u041f\u043e\u0432\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0434\u0442\u0438 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c: \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0432 \u0446\u0435\u043b\u043e\u043c, \u043f\u043e\u0442\u043e\u043c \u043a\u043e\u0434 \u043f\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c \u0441 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430\u043c\u0438 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0430, \u0432 \u044d\u0442\u043e\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d \u0432\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043a\u043e\u0434 \u0431\u0435\u0437 \u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u0441 \u0440\u0430\u0437\u0431\u043e\u0440\u043e\u043c \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.<\/p>\n<p>\u0412 Wails \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0444\u0430\u0439\u043b\u0430\u043c\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 90% \u0440\u0430\u0431\u043e\u0442\u044b \u044d\u0442\u043e <code>index.html<\/code>, <code>styles.css<\/code>, <code>main.js<\/code>, <code>app.go<\/code>. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u044b \u043b\u0438\u0431\u043e \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c, \u043b\u0438\u0431\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0432 \u0441\u0430\u043c\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0435.<\/p>\n<p>\u0414\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 html, css, js \u0431\u0435\u0437 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432 (\u0442\u0430\u043a \u043a\u0430\u043a \u044f \u0432 \u043d\u0438\u0445 \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0441\u044c). Frontend \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f, a html \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f js \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<h2>\u0427\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a?<\/h2>\n<ul>\n<li>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0435\u0442 \u0433\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\/\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043b\u0438 Docker (\u0432 \u043d\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f ansible).<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0440\u044f\u0434\u043e\u043c \u0441 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u043e\u043c.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 secrets.yml \u0434\u043b\u044f ansible (\u0444\u043e\u0440\u043c\u0430 \u0432\u0432\u043e\u0434\u0430, \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u0442\u043e\u043c \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0435 .yml \u043a\u043e\u043d\u0444\u0438\u0433\u0430).<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438: \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433, \u0442\u0430\u043a\u0436\u0435 \u0434\u0430\u0435\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0435\u0431\u0435 \u043a\u043e\u043f\u0438\u044e.<\/p>\n<\/li>\n<li>\n<p>\u0412\u044b\u0431\u043e\u0440 \u0434\u0435\u043f\u043b\u043e\u044f (\u043a\u0430\u043a \u0441\u0435\u0431\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u0435\u0441\u0442\u0438 Ansible). \u0414\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 3 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430: \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043d\u0430 \u043e\u0431\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0440\u0430\u0437\u0443. \u0418 \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u0435\u0449\u0435 \u043d\u0430 2: \u043f\u043e\u043b\u043d\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 (\u0441 \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u0430), \u043b\u0438\u0431\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0435\u0441\u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u0438 \u043d\u0430\u0447\u0430\u043b\u043e \u0434\u0435\u043f\u043b\u043e\u044f. \u041f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043a\u043d\u043e \u0441 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u043e\u043c \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0438\u0448\u0443\u0442\u0441\u044f \u043b\u043e\u0433\u0438 ansible.<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439 &#8212; \u043a\u043d\u043e\u043f\u043a\u0430 \u0432\u044b\u0439\u0442\u0438 \u0438\u043b\u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0437\u0430\u043d\u043e\u0432\u043e. \u0415\u0441\u043b\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0443\u0441\u043f\u0435\u0448\u0435\u043d &#8212; \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0433\u0430\u0439\u0434 \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u0432\u044b\u0445\u043e\u0434\u0430.<\/p>\n<\/li>\n<\/ul>\n<h2>\u042d\u043a\u0441\u043a\u0443\u0440\u0441 \u0432 Wails (\u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442)<\/h2>\n<p>Wails \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 Frontend \u0438 Backend \u0447\u0430\u0441\u0442\u044c. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0435\u0433\u043e \u0441\u043c\u044b\u0441\u043b \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043e\u043d \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u0430\u043a \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0432\u044b\u0432\u043e\u0434\u044b Go \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 js. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c: \u0432 Go \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443, Wails \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Go \u043a JS, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.<\/p>\n<h2>\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430<\/h2>\n<p>\u041f\u0440\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f:<\/p>\n<ul>\n<li>\n<p><strong>Go 1.21+<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>NPM \/ Node.js (15+)<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Wails v2<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>libwebkit2gtk-4.1-dev<\/strong> \u2014 <em>\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f Linux<\/em> (\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u0432\u0438\u0436\u043a\u0430 Wails)<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c <code>wails init<\/code>.<\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0444\u0430\u0439\u043b \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c &#8212; <code>main.go<\/code> (\u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0434\u0430\u0435\u043c \u0435\u043c\u0443 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e \u0442\u0438\u043f\u0443 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043e\u043a\u043d\u0430, \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0430\u0441\u0441\u0435\u0442\u044b).<\/p>\n<p><strong>main.go:<\/strong><\/p>\n<pre><code class=\"go\">package main \/\/ \u041e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044fimport (\"embed\" \/\/ \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\"github.com\/wailsapp\/wails\/v2\" \/\/ \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u0430\u043a\u0435\u0442 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Wails\"github.com\/wailsapp\/wails\/v2\/pkg\/options\" \/\/ \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\"github.com\/wailsapp\/wails\/v2\/pkg\/options\/assetserver\" \/\/ \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432)\/\/ \u0414\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0430 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0430 go:embed:\/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0443 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043f\u043a\u0438 'frontend\/dist' \u0432 \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b.\/\/ 'all:' \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0441\u043a\u0440\u044b\u0442\u044b\u0435 \u0444\u0430\u0439\u043b\u044b (\u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u0441 \u0442\u043e\u0447\u043a\u0438) \/\/ \u0438 \u0444\u0430\u0439\u043b\u044b \u0441 \u043d\u0438\u0436\u043d\u0438\u043c \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u0435\u043c.\/\/\/\/go:embed all:frontend\/distvar assets embed.FS \/\/ \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f 'assets' \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0443\u044e \u0432\u0441\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044bfunc main() {\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b App (\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 Go, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0438\u0437 JS)app := NewApp()\/\/ \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f Wails \u0441 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438:err := wails.Run(&amp;options.App{Title:     \"ServeHub-2 Installer\", \/\/ \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043e\u043a\u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044fWidth:     1024,                  \/\/ \u0428\u0438\u0440\u0438\u043d\u0430 \u043e\u043a\u043d\u0430Height:    768,                   \/\/ \u0412\u044b\u0441\u043e\u0442\u0430 \u043e\u043a\u043d\u0430Frameless: true,                  \/\/ \u041e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439 \u0440\u0430\u043c\u043a\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430AssetServer: &amp;assetserver.Options{Assets: assets, \/\/ \u041f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u043d\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0430\u0441\u0441\u0435\u0442\u043e\u0432},OnStartup: app.startup, \/\/ \u041c\u0435\u0442\u043e\u0434, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430Bind: []interface{}{ \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 App \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0438\u0437 JavaScriptapp,},})\/\/ \u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430, \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0435\u0451 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044cif err != nil {println(\"Error:\", err.Error())}}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u041b\u043e\u0433\u0438\u043a\u0430 \u0441\u043c\u0435\u043d\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446<\/h2>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e Wails &#8212; \u044d\u0442\u043e SPA, \u0442.\u0435 \u0432\u0441\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0430 javascript \u043c\u0435\u043d\u044f\u0435\u0442 \u0435\u0435 \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u201c\u0448\u0430\u0433\u043e\u0432\u201d.<\/p>\n<p><strong>index.html (\u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430):<\/strong><\/p>\n<pre><code>&lt;!-- \u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 --&gt;&lt;div class=\"progress-container\"&gt;    &lt;!--       \u041e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0448\u0430\u0433\u043e\u0432.       \u0417\u0434\u0435\u0441\u044c \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c .step-dot,       \u043a\u043e\u0442\u043e\u0440\u044b\u0435 JavaScript \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044e updateStepProgress.    --&gt;    &lt;div class=\"progress-bar-track\"&gt;        &lt;div id=\"step-progress-fill\" class=\"progress-bar-fill\"&gt;&lt;\/div&gt;                &lt;!-- \u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 7 \u0448\u0430\u0433\u0430\u043c --&gt;        &lt;div class=\"step-dot\" data-step=\"1\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"2\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"3\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"4\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"5\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"6\"&gt;&lt;\/div&gt;        &lt;div class=\"step-dot\" data-step=\"7\"&gt;&lt;\/div&gt;    &lt;\/div&gt;    &lt;!-- \u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u044d\u0442\u0430\u043f\u0430 --&gt;    &lt;div class=\"step-label\"&gt;        \u0428\u0430\u0433 &lt;span id=\"step-progress-current\"&gt;1&lt;\/span&gt; \u0438\u0437 7 \u2014         &lt;span id=\"step-progress-title\"&gt;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c&lt;\/span&gt;    &lt;\/div&gt;&lt;\/div&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u044e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b HTML \u0438 \u043b\u043e\u0433\u0438\u043a\u0443 <code>App.js<\/code>.<\/p>\n<p><strong>main.js (\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u0440\u043e\u0443\u0442\u0435\u0440):<\/strong><\/p>\n<pre><code class=\"javascript\">\/** * \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u0448\u0430\u0433\u043e\u0432 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0435. *\/const STEP_TITLES = {    1: '\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c',    2: '\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439',    3: '\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432',    4: '\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 secrets.yml',    5: '\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438',    6: '\u0412\u044b\u0431\u043e\u0440 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u0435\u043f\u043b\u043e\u044f',    7: '\u0417\u0430\u043f\u0443\u0441\u043a \u0434\u0435\u043f\u043b\u043e\u044f',};\/** * \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430 \u0438 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0448\u0430\u0433\u043e\u0432. * @param {number} stepNumber - \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0448\u0430\u0433\u0430. *\/function updateStepProgress(stepNumber) {    const totalSteps = 7;    const fill = document.getElementById('step-progress-fill');    const currentEl = document.getElementById('step-progress-current');    const titleEl = document.getElementById('step-progress-title');    \/\/ 1. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0448\u0438\u0440\u0438\u043d\u044b \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430 (\u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f)    if (fill) {        \/\/ \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 1-\u043c \u0448\u0430\u0433\u0435 \u0431\u044b\u043b\u043e 0%, \u0430 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u043c \u2014 100%        const pct = ((stepNumber - 1) \/ (totalSteps - 1)) * 100;        fill.style.width = `${pct}%`;    }        \/\/ 2. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432    if (currentEl) currentEl.innerText = stepNumber;    if (titleEl) titleEl.innerText = STEP_TITLES[stepNumber] || '';    \/\/ 3. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a (dots) \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430    document.querySelectorAll('.step-dot').forEach((dot) =&gt; {        const dotStep = parseInt(dot.getAttribute('data-step'), 10);        dot.classList.remove('completed', 'active');                \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u043a\u0430\u043a 'completed', \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u2014 \u043a\u0430\u043a 'active'        if (dotStep &lt; stepNumber) dot.classList.add('completed');        if (dotStep === stepNumber) dot.classList.add('active');    });}\/** * \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438. * \u0421\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0446\u0435\u043b\u0435\u0432\u043e\u0439. * @param {number} stepNumber - \u041d\u043e\u043c\u0435\u0440 \u0448\u0430\u0433\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430. *\/window.goToStep = function(stepNumber) {    \/\/ 1. \u0421\u043a\u0440\u044b\u0442\u0438\u0435 \u0432\u0441\u0435\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u0448\u0430\u0433\u043e\u0432    document.querySelectorAll('.step').forEach(step =&gt; {        step.classList.remove('active');    });    \/\/ 2. \u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0446\u0435\u043b\u0435\u0432\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430    const nextStep = document.getElementById(`step-${stepNumber}`);    if (nextStep) {        nextStep.classList.add('active');    }    \/\/ 3. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430    updateStepProgress(stepNumber);    \/\/ 4. \u0422\u0440\u0438\u0433\u0433\u0435\u0440\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438:    \/\/ \u041f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b (\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0438\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443).    if (stepNumber === 2) {        runDockerCheck();    }    if (stepNumber === 3) {        runInstallation();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u0420\u0435\u0432\u044c\u044e \u043f\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c<\/h2>\n<h4>\u0428\u0430\u0433 1: \u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c (SPA \u0438 \u0420\u043e\u0443\u0442\u0438\u043d\u0433)<\/h4>\n<p>\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430. \u041f\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>goToStep(2)<\/code>, \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p><strong>HTML-\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0430 \u0448\u0430\u0433\u0430 (<\/strong><code><strong>index.html<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code>&lt;!--   \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 ServeHub-2.  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.--&gt;&lt;div class=\"installer-container\"&gt;    &lt;header class=\"installer-header\"&gt;        &lt;h1&gt;ServeHub-2&lt;\/h1&gt;        &lt;p class=\"subtitle\"&gt;\u041c\u0430\u0441\u0442\u0435\u0440 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438&lt;\/p&gt;    &lt;\/header&gt;    &lt;main class=\"installer-content\"&gt;        &lt;p&gt;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c! \u042d\u0442\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 &lt;strong&gt;ServeHub-2&lt;\/strong&gt; \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435.&lt;\/p&gt;    &lt;\/main&gt;    &lt;footer class=\"installer-actions\"&gt;        &lt;!-- \u041a\u043d\u043e\u043f\u043a\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0448\u0430\u0433\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 --&gt;        &lt;button id=\"btn-start\" class=\"btn-primary\" onclick=\"goToStep(2)\"&gt;            \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c        &lt;\/button&gt;                &lt;!-- \u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f --&gt;        &lt;a href=\"https:\/\/github.com\/canntstand\/ServeHub-2\" target=\"_blank\" class=\"link-github\"&gt;            &lt;img src=\"assets\/github-icon.svg\" alt=\"GitHub Logo\" class=\"icon\"&gt;            GitHub Repository        &lt;\/a&gt;    &lt;\/footer&gt;&lt;\/div&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 2: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u044f Docker. \u0414\u043b\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0430 \u0432 Go \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>CheckResult<\/code>.<\/p>\n<p><strong>\u0411\u044d\u043a\u0435\u043d\u0434 \u043d\u0430 Go (<\/strong><code><strong>app.go<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/\/ CheckResult \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u0431\u044d\u043a\u0435\u043d\u0434\u0430.\/\/ \u041f\u043e\u043b\u044f \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b \u0442\u0435\u0433\u0430\u043c\u0438 json \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 JavaScript (\u0447\u0435\u0440\u0435\u0437 Wails).type CheckResult struct {    Success bool   `json:\"success\"` \/\/ \u0424\u043b\u0430\u0433 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438    Message string `json:\"message\"` \/\/ \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0438\u043b\u0438 \u0442\u0435\u043a\u0441\u0442 \u043e\u0448\u0438\u0431\u043a\u0438}\/\/ CheckDocker \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0438 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c Docker \u0438 Docker Compose \u0432 \u0445\u043e\u0441\u0442-\u0441\u0438\u0441\u0442\u0435\u043c\u0435.\/\/ \u041c\u0435\u0442\u043e\u0434 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0432\u044b\u0437\u043e\u0432\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u0447\u0435\u0440\u0435\u0437 os\/exec \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u0438.func (a *App) CheckDocker() CheckResult {    \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438 Docker:    \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c `docker info` \u2014 \u044d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u0435\u0441\u043b\u0438 Docker \u0437\u0430\u043f\u0443\u0449\u0435\u043d.    \/\/ \u0415\u0441\u043b\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0437\u043d\u0430\u0447\u0438\u0442 \u0434\u0435\u043c\u043e\u043d Docker \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u043b\u0438 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d.    _, err := exec.Command(\"docker\", \"info\").Output()    if err != nil {        return CheckResult{            Success: false,             Message: \"Docker \u043d\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u0438\u043b\u0438 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d!\",        }    }    \/\/ 2. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u044f Docker Compose:    \/\/ Docker Compose \u0447\u0430\u0441\u0442\u043e \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u043b\u0430\u0433\u0438\u043d \u0438\u043b\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0444\u0430\u0439\u043b.    \/\/ \u0412\u044b\u0437\u043e\u0432 `docker compose version` \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043b\u0430\u0433\u0438\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.    _, err = exec.Command(\"docker\", \"compose\", \"version\").Output()    if err != nil {        return CheckResult{            Success: false,             Message: \"Docker Compose \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435!\",        }    }    \/\/ 3. \u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:    \/\/ \u041e\u0431\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0438 \u0433\u043e\u0442\u043e\u0432\u044b \u043a \u0440\u0430\u0431\u043e\u0442\u0435.    return CheckResult{        Success: true,         Message: \"Docker \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u0438 \u0433\u043e\u0442\u043e\u0432 \u043a \u0440\u0430\u0431\u043e\u0442\u0435.\",    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"javascript\">\/** * \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 Docker \u0438 Docker Compose \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435. * \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. *\/window.runDockerCheck = async function() {    \/\/ 1. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 DOM-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430    const statusText = document.getElementById('status-text');    const spinner = document.getElementById('status-loading');    const btnNext = document.getElementById('btn-step2-next');    const btnRetry = document.getElementById('btn-retry');    \/\/ 2. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 UI \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438:    \/\/ \u0412\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0441\u043f\u0438\u043d\u043d\u0435\u0440, \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0438 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430    spinner.style.display = 'block';    spinner.className = 'spinner';    statusText.innerText = '\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c Docker \u0438 Docker Compose...';    statusText.style.color = '#9a9aa0'; \/\/ \u041d\u0435\u0439\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f    btnNext.disabled = true;    btnRetry.style.display = 'none';    \/\/ 3. \u0418\u043c\u0438\u0442\u0430\u0446\u0438\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 (700 \u043c\u0441):    \/\/ \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u043c\u0435\u043d\u0443 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439, \u0438\u0437\u0431\u0435\u0433\u0430\u044f \"\u0434\u0435\u0440\u0433\u0430\u043d\u044c\u044f\" \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430    \/\/ \u043f\u0440\u0438 \u043e\u0447\u0435\u043d\u044c \u0431\u044b\u0441\u0442\u0440\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435.    await new Promise(resolve =&gt; setTimeout(resolve, 700));    try {        \/\/ 4. \u0412\u044b\u0437\u043e\u0432 Go-\u0444\u0443\u043d\u043a\u0446\u0438\u0438 CheckDocker \u0447\u0435\u0440\u0435\u0437 \u043c\u043e\u0441\u0442 Wails:        const result = await CheckDocker();        spinner.style.display = 'none'; \/\/ \u0421\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0430        if (result.success) {            \/\/ \u0415\u0441\u043b\u0438 Docker \u043d\u0430\u0439\u0434\u0435\u043d \u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442            statusText.innerText = `\u2713 ${result.message}`;            statusText.style.color = '#818cf8'; \/\/ \u0410\u043a\u0446\u0435\u043d\u0442\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0443\u0441\u043f\u0435\u0445\u0430            btnNext.disabled = false; \/\/ \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0448\u0430\u0433\u0443        } else {            \/\/ \u0415\u0441\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Docker \u043d\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d)            statusText.innerText = `\u2717 \u041e\u0448\u0438\u0431\u043a\u0430: ${result.message}.`;            statusText.style.color = '#e5484d'; \/\/ \u0426\u0432\u0435\u0442 \u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438            btnRetry.style.display = 'block'; \/\/ \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u043c \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0435\u0449\u0435 \u0440\u0430\u0437        }    } catch (err) {        \/\/ 5. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441\u0431\u043e\u0439 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c)        spinner.style.display = 'none';        statusText.innerText = '\u041a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0431\u044d\u043a\u0435\u043d\u0434\u0430:\\n' + err;        statusText.style.color = '#e5484d';        btnRetry.style.display = 'block';    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 3: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432<\/h4>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0448\u0430\u0433\u0435 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 GitHub \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0430.<\/p>\n<p><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0430\u0440\u0445\u0438\u0432\u0430 \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 (<\/strong><code><strong>app.go<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/\/ InstallProject \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 GitHub \u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 \u0440\u0430\u0431\u043e\u0447\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e.\/\/ \u041c\u0435\u0442\u043e\u0434 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443, \u0435\u0441\u043b\u0438 \u043f\u0430\u043f\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.func (a *App) InstallProject() CheckResult {    \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430:    \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043f\u0430\u043f\u043a\u0438 \"ServeHub-2-main\". \u0415\u0441\u043b\u0438 \u043e\u043d\u0430 \u0435\u0441\u0442\u044c, \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c,     \/\/ \u0447\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442, \u0438 \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435.    if _, err := os.Stat(\"ServeHub-2-main\"); err == nil {        return CheckResult{            Success: true,             Message: \"\u041f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0431\u044b\u043b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442 \u0440\u0430\u043d\u0435\u0435 (\u043f\u0430\u043f\u043a\u0430 ServeHub-2-main \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442).\",        }    }    \/\/ 2. \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0430\u0440\u0445\u0438\u0432\u0430:    \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 HTTP-\u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f ZIP-\u0430\u0440\u0445\u0438\u0432\u0430 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f.    url := \"https:\/\/github.com\/canntstand\/ServeHub-2\/archive\/refs\/heads\/main.zip\"    resp, err := http.Get(url)    if err != nil {        return CheckResult{Success: false, Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a GitHub: \" + err.Error()}    }    defer resp.Body.Close() \/\/ \u0413\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435 \u043f\u043e\u0442\u043e\u043a\u0430 \u043e\u0442\u0432\u0435\u0442\u0430    \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u0435\u0440\u043d\u0443\u043b \u0441\u0442\u0430\u0442\u0443\u0441 200 OK    if resp.StatusCode != http.StatusOK {        return CheckResult{Success: false, Message: \"GitHub \u0432\u0435\u0440\u043d\u0443\u043b \u043e\u0448\u0438\u0431\u043a\u0443: \" + resp.Status}    }    \/\/ 3. \u0427\u0442\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0432 \u043f\u0430\u043c\u044f\u0442\u044c:    bodyBytes, err := io.ReadAll(resp.Body)    if err != nil {        return CheckResult{Success: false, Message: \"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0430\u0440\u0445\u0438\u0432\u0430: \" + err.Error()}    }    \/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c ZIP-\u0440\u0438\u0434\u0435\u0440 \u0438\u0437 \u0431\u0430\u0439\u0442\u043e\u0432\u043e\u0433\u043e \u043c\u0430\u0441\u0441\u0438\u0432\u0430    zipReader, err := zip.NewReader(bytes.NewReader(bodyBytes), int64(len(bodyBytes)))    if err != nil {        return CheckResult{Success: false, Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u043a\u0430\u0447\u0430\u043d\u043d\u044b\u0439 zip-\u0430\u0440\u0445\u0438\u0432: \" + err.Error()}    }    destFolder := \".\" \/\/ \u0420\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f    \/\/ 4. \u0418\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430:    for _, f := range zipReader.File {        fpath := filepath.Join(destFolder, f.Name)        \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0439: \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443, \u0435\u0441\u043b\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0430\u0440\u0445\u0438\u0432\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0435\u0439        if f.FileInfo().IsDir() {            os.MkdirAll(fpath, os.ModePerm)            continue        }        \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0430\u043f\u043e\u043a \u0434\u043b\u044f \u0444\u0430\u0439\u043b\u0430        if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {            return CheckResult{Success: false, Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0430\u043f\u043e\u043a: \" + err.Error()}        }        \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043d\u0430 \u0434\u0438\u0441\u043a\u0435 \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c\u0438 \u0432 ZIP-\u0430\u0440\u0445\u0438\u0432\u0435        outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())        if err != nil {            return CheckResult{Success: false, Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u043d\u0430 \u0434\u0438\u0441\u043a\u0435: \" + err.Error()}        }        \/\/ \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0430\u0440\u0445\u0438\u0432\u0430        rc, err := f.Open()        if err != nil {            outFile.Close()            return CheckResult{Success: false, Message: \"\u041e\u0448\u0438\u0431\u043a\u0430 \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430: \" + err.Error()}        }        \/\/ \u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430 \u0432 \u0444\u0430\u0439\u043b \u043d\u0430 \u0434\u0438\u0441\u043a\u0435        _, err = io.Copy(outFile, rc)        outFile.Close() \/\/ \u0417\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438        rc.Close()      \/\/ \u0417\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0430\u0440\u0445\u0438\u0432\u0430        if err != nil {            return CheckResult{Success: false, Message: \"\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0444\u0430\u0439\u043b\u0430 \u043d\u0430 \u0434\u0438\u0441\u043a: \" + err.Error()}        }    }    return CheckResult{        Success: true,        Message: \"ServeHub-2 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043a\u0430\u0447\u0430\u043d \u0438 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u043f\u0430\u043f\u043a\u0435!\",    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0423\u043c\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435 (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/** * \u0418\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f) \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u0435  * \u0444\u0430\u0439\u043b\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e \u043f\u0443\u0442\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. *\/window.runInstallation = async function() {    \/\/ 1. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 DOM-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430    const installText = document.getElementById('install-text');    const spinner = document.getElementById('install-loading');    const btnFinish = document.getElementById('btn-finish');    const btnRetry = document.getElementById('btn-install-retry');    const btnEditSecrets = document.getElementById('btn-edit-secrets');    const btnStep6Back = document.getElementById('btn-step6-back');    \/\/ 2. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f UI: \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0438    spinner.style.display = 'block';    installText.innerText = '\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0430\u0440\u0445\u0438\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 ServeHub-2 \u0441 GitHub \u0432 \u043f\u0430\u043c\u044f\u0442\u044c...';    installText.style.color = 'var(--text-secondary)';        btnFinish.disabled = true;    btnFinish.style.display = 'inline-block';    btnRetry.style.display = 'none';    btnEditSecrets.style.display = 'none';    try {        \/\/ 3. \u0412\u044b\u0437\u043e\u0432 \u0431\u044d\u043a\u0435\u043d\u0434\u0430 \u0434\u043b\u044f \u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\/\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430        const result = await InstallProject();                \/\/ 4. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f        if (result.success) {            \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0431\u044b\u043b \u043b\u0438 \u0440\u0430\u043d\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u043d \u0444\u0430\u0439\u043b secrets.yml            const secretsCheck = await CheckSecrets();            spinner.style.display = 'none';            if (secretsCheck.success) {                \/\/ \u0415\u0441\u043b\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430\u0439\u0434\u0435\u043d\u0430: \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443                installText.innerHTML = `\u2713 ${result.message}&lt;br&gt;&lt;br&gt;&lt;span style=\"color: var(--accent-soft); font-weight: bold;\"&gt;\u2139 \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430\u0439\u0434\u0435\u043d\u0430:&lt;\/span&gt; \u0424\u0430\u0439\u043b &lt;code style=\"background: var(--bg-3); padding: 2px 6px; border-radius: 4px;\"&gt;secrets.yml&lt;\/code&gt; \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u0428\u0430\u0433\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u044b.`;                installText.style.color = 'var(--accent-bright)';                                btnFinish.disabled = false;                btnFinish.innerText = '\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0432\u044b\u0431\u043e\u0440\u0443 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u043a\u0438';                btnFinish.setAttribute('onclick', 'goToStep(6)'); \/\/ \u041d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430 \u0432\u044b\u0431\u043e\u0440 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f                btnEditSecrets.style.display = 'inline-block';                if (btnStep6Back) {                    btnStep6Back.setAttribute('onclick', 'goToStep(3)');                }            } else {                \/\/ \u0415\u0441\u043b\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0435\u0442: \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u0448\u0430\u0433 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a                installText.innerText = `\u2713 ${result.message}`;                installText.style.color = 'var(--accent-bright)';                                btnFinish.disabled = false;                btnFinish.innerText = '\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435';                btnFinish.setAttribute('onclick', 'goToStep(4)'); \/\/ \u041d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430 \u0432\u0432\u043e\u0434 \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432                if (btnStep6Back) {                    btnStep6Back.setAttribute('onclick', 'goToStep(5)');                }            }        } else {            \/\/ \u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430)            spinner.style.display = 'none';            installText.innerText = `\u2717 \u041e\u0448\u0438\u0431\u043a\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438: ${result.message}`;            installText.style.color = 'var(--danger)';            btnFinish.style.display = 'none';            btnRetry.style.display = 'inline-block'; \/\/ \u0414\u0430\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c        }    } catch (err) {        \/\/ 5. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0441\u0431\u043e\u0435\u0432 (\u043e\u0448\u0438\u0431\u043a\u0438 API Wails \u0438\u043b\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0431\u044d\u043a\u0435\u043d\u0434\u0430)        spinner.style.display = 'none';        installText.innerText = '\u041a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438:\\n' + err;        installText.style.color = 'var(--danger)';        btnFinish.style.display = 'none';        btnRetry.style.display = 'inline-block';    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 4: \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 secrets.yml<\/h4>\n<p>\u0412 \u043f\u043e\u043b\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432\u0435\u0441\u044c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u043e\u043b\u0435\u0439 \u0434\u043b\u044f \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438\u0437 YAML-\u0444\u0430\u0439\u043b\u0430. \u041f\u0430\u0440\u0441\u0435\u0440 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u043c\u043d\u043e\u0433\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u044b (\u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 SSH-\u043a\u043b\u044e\u0447\u0438), \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044f \u043b\u0438\u0448\u043d\u0438\u0435 \u043e\u0442\u0441\u0442\u0443\u043f\u044b.<\/p>\n<p><strong>\u0417\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0444\u043e\u0440\u043c\u044b \u0438\u0437 YAML (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"javascript\">\/** * \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043b\u044e\u0447\u0435\u0439 YAML (\u0438\u0437 secrets.yml)  * \u0441 ID \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0432\u0432\u043e\u0434\u0430 \u0432 HTML-\u0444\u043e\u0440\u043c\u0435. * \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0438 UI. *\/const SECRETS_FIELD_MAP = {    vps_public_ip: 'sec_vps_ip',    vps_user: 'sec_vps_user',    vps_root_password: 'sec_vps_root_pass',    local_private_ip: 'sec_local_ip',    local_user: 'sec_local_user',    local_root_password: 'sec_local_root_pass',    server_name: 'sec_server_name',    admin_user: 'sec_admin_user',    admin_password: 'sec_admin_pass',    email: 'sec_email',    telegram_token: 'sec_tg_token',    telegram_chat_id: 'sec_tg_chat_id',    webnames_apikey: 'sec_webnames',    postgres_db_nextcloud: 'sec_pg_nc',    postgres_db_vaultwarden: 'sec_pg_vw',    postgres_user: 'sec_pg_user',    postgres_password: 'sec_pg_pass',    nextcloud_redis_pass: 'sec_redis_pass',    secret_vaultwarden_password: 'sec_vw_admin_pass',    ssh_public_key: 'sec_public_ssh_key',    ssh_private_key: 'sec_private_ssh_key',    borgmatic_encryption_passphrase: 'sec_borg_pass',    backup_disk_uuid: 'sec_disk_uuid',};\/** * \u041f\u0430\u0440\u0441\u0438\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 YAML-\u0444\u0430\u0439\u043b\u0430 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u044f HTML-\u0444\u043e\u0440\u043c\u044b. * \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0442\u0430\u043a \u0438 \u043c\u043d\u043e\u0433\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0435 \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u044b (\u0434\u043b\u044f SSH-\u043a\u043b\u044e\u0447\u0435\u0439). * @param {string} yamlText - \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0444\u0430\u0439\u043b\u0430 secrets.yml. *\/function populateSecretsForm(yamlText) {    const lines = yamlText.split('\\n');    let i = 0;    while (i &lt; lines.length) {        const line = lines[i];        \/\/ 1. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043c\u043d\u043e\u0433\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0445 \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u043e\u0432 (\u043a\u043b\u044e\u0447 \u0432\u0438\u0434\u0430 `key: |`)        \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f SSH-\u043a\u043b\u044e\u0447\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u0441\u0442\u0440\u043e\u043a        const literalMatch = line.match(\/^([a-z_]+):\\s*\\|\\s*$\/);        if (literalMatch) {            const fieldId = SECRETS_FIELD_MAP[literalMatch[1]];            i++; \/\/ \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 (\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0431\u043b\u043e\u043a\u0430)            const blockLines = [];                        \/\/ \u0427\u0438\u0442\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u043c\u0435\u044e\u0442 \u043e\u0442\u0441\u0442\u0443\u043f (4 \u043f\u0440\u043e\u0431\u0435\u043b\u0430)            while (i &lt; lines.length &amp;&amp; (lines[i].startsWith('    ') || lines[i].trim() === '')) {                \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c 4 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0431\u0435\u043b\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435                blockLines.push(lines[i].replace(\/^ {4}\/, ''));                i++;            }                        \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043f\u0443\u0441\u0442\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043a\u043e\u043d\u0446\u0435 \u0431\u043b\u043e\u043a\u0430            while (blockLines.length &amp;&amp; blockLines[blockLines.length - 1] === '') {                blockLines.pop();            }                        \/\/ \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0431\u043b\u043e\u043a \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u043f\u043e\u043b\u0435 \u0444\u043e\u0440\u043c\u044b            if (fieldId) {                const el = document.getElementById(fieldId);                if (el) el.value = blockLines.join('\\n');            }            continue; \/\/ \u041f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442 i \u0432 \u043a\u043e\u043d\u0446\u0435 \u0446\u0438\u043a\u043b\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u044b \u0443\u0436\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u043b\u0438\u0441\u044c        }        \/\/ 2. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 (\u043a\u043b\u044e\u0447 \u0432\u0438\u0434\u0430 `key: \"value\"`)        const simpleMatch = line.match(\/^([a-z_]+):\\s*\"(.*)\"\\s*$\/);        if (simpleMatch) {            const fieldId = SECRETS_FIELD_MAP[simpleMatch[1]];            if (fieldId) {                const el = document.getElementById(fieldId);                \/\/ \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u0433\u0440\u0443\u043f\u043f\u044b \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430                if (el) el.value = simpleMatch[2];            }        }        i++;    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 5: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438<\/h4>\n<p>\u041f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u201c\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c\u201d \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 23 \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0435\u0434\u0438\u043d\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p><strong>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"javascript\">\/** * \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u043e\u0440\u043c\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 YAML-\u0444\u0430\u0439\u043b \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 * \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. *\/window.generateAndReviewYaml = function() {    \/\/ 1. \u0421\u0431\u043e\u0440 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u0435\u0439 \u0432\u0432\u043e\u0434\u0430 \u0444\u043e\u0440\u043c\u044b \u043f\u043e \u0438\u0445 ID    const vpsIp = document.getElementById('sec_vps_ip').value;    const vpsUser = document.getElementById('sec_vps_user').value;    const vpsRootPass = document.getElementById('sec_vps_root_pass').value;    const localIp = document.getElementById('sec_local_ip').value;    const localUser = document.getElementById('sec_local_user').value;    const localRootPass = document.getElementById('sec_local_root_pass').value;        const serverName = document.getElementById('sec_server_name').value;    const adminUser = document.getElementById('sec_admin_user').value;    const adminPass = document.getElementById('sec_admin_pass').value;    const email = document.getElementById('sec_email').value;        const tgToken = document.getElementById('sec_tg_token').value;    const tgChatId = document.getElementById('sec_tg_chat_id').value;    const webnamesKey = document.getElementById('sec_webnames').value;        const pgNc = document.getElementById('sec_pg_nc').value;    const pgVw = document.getElementById('sec_pg_vw').value;    const pgUser = document.getElementById('sec_pg_user').value;    const pgPass = document.getElementById('sec_pg_pass').value;    const redisPass = document.getElementById('sec_redis_pass').value;        const vwAdminPass = document.getElementById('sec_vw_admin_pass').value;    const publicsshKey = document.getElementById('sec_public_ssh_key').value;    const privatesshKey = document.getElementById('sec_private_ssh_key').value;    const borgPass = document.getElementById('sec_borg_pass').value;    const diskUuid = document.getElementById('sec_disk_uuid').value;    \/\/ 2. \u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0433\u043e SSH \u043a\u043b\u044e\u0447\u0430:    \/\/ YAML \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0442\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f \u043c\u043d\u043e\u0433\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0445 \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u043e\u0432 (\u0441\u0438\u043c\u0432\u043e\u043b `|`).    \/\/ \u041c\u044b \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u0443 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c 4 \u043f\u0440\u043e\u0431\u0435\u043b\u0430 \u043a \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435.    const formattedPrivateKey = privatesshKey        .split('\\n')        .map(line =&gt; '    ' + line)        .join('\\n');    \/\/ 3. \u0424\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 YAML-\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430:    \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 (` `) \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445.     \/\/ \u042d\u0442\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0430, \u043f\u043e\u043d\u044f\u0442\u043d\u0443\u044e \u0434\u043b\u044f Ansible.    generatedYamlString = `# ==============================================================================#                 ServeHub-2: \u0421\u0413\u0415\u041d\u0415\u0420\u0418\u0420\u041e\u0412\u0410\u041d\u041d\u042b\u0419 \u041a\u041e\u041d\u0424\u0418\u0413\u0423\u0420\u0410\u0426\u0418\u041e\u041d\u041d\u042b\u0419 SECRETS.YML# ==============================================================================# 1. \u0421\u0415\u0422\u0415\u0412\u0410\u042f \u0418\u041d\u0424\u0420\u0410\u0421\u0422\u0420\u0423\u041a\u0422\u0423\u0420\u0410 \u0418 \u041f\u041e\u041b\u042c\u0417\u041e\u0412\u0410\u0422\u0415\u041b\u0418vps_public_ip: \"${vpsIp}\"vps_user: \"${vpsUser}\"vps_root_password: \"${vpsRootPass}\"local_private_ip: \"${localIp}\"local_user: \"${localUser}\"local_root_password: \"${localRootPass}\"# 2. \u0413\u041b\u041e\u0411\u0410\u041b\u042c\u041d\u042b\u0415 \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418server_name: \"${serverName}\"admin_user: \"${adminUser}\"admin_password: \"${adminPass}\"email: \"${email}\"# 3. \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418 \u0410\u041b\u0415\u0420\u0422\u0418\u041d\u0413\u0410telegram_token: \"${tgToken}\"telegram_chat_id: \"${tgChatId}\"# 4. \u0418\u041d\u0422\u0415\u0413\u0420\u0410\u0426\u0418\u042f DNSwebnames_apikey: \"${webnamesKey}\"# 5. \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418 POSTGRESQLpostgres_db_nextcloud: \"${pgNc}\"postgres_db_vaultwarden: \"${pgVw}\"postgres_user: \"${pgUser}\"postgres_password: \"${pgPass}\"nextcloud_redis_pass: \"${redisPass}\"# 6. \u0421\u0415\u041a\u0420\u0415\u0422\u042b \u0421\u0415\u0420\u0412\u0418\u0421\u041e\u0412secret_vaultwarden_password: \"${vwAdminPass}\"ssh_public_key: \"${publicsshKey}\"ssh_private_key: |${formattedPrivateKey}borgmatic_encryption_passphrase: \"${borgPass}\"backup_disk_uuid: \"${diskUuid}\"`;    \/\/ 4. \u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430:    \/\/ \u0412\u044b\u0432\u043e\u0434\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 YAML \u0432 \u0431\u043b\u043e\u043a \u043f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435    document.getElementById('yaml-preview').innerText = generatedYamlString;    \/\/ 5. \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439:    \/\/ \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \"\u041d\u0430\u0437\u0430\u0434\", \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u0432\u0435\u043b\u0430 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445    const btnStep6Back = document.getElementById('btn-step6-back');    if (btnStep6Back) {        btnStep6Back.setAttribute('onclick', 'goToStep(5)');    }        \/\/ \u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043a \u0448\u0430\u0433\u0443 5 (\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435)    goToStep(5);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 (<\/strong><code><strong>app.go<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/\/ SaveSecrets \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 YAML-\u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0432 \u0444\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.\/\/ \u041c\u0435\u0442\u043e\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0441 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e secrets.yml \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.func (a *App) SaveSecrets(yamlContent string) CheckResult {    \/\/ 1. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u0443\u0442\u0438 \u043a \u0444\u0430\u0439\u043b\u0443:    \/\/ \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438, \u0433\u0434\u0435 Ansible \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0444\u0430\u0439\u043b \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445.    \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f filepath.Join \u0434\u043b\u044f \u043a\u0440\u043e\u0441\u0441\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u043e\u0439 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0443\u0442\u0435\u0439.    targetDir := filepath.Join(\".\", \"ServeHub-2-main\", \"ansible\", \"vars\")    \/\/ 2. \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0439:    \/\/ os.MkdirAll \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0432\u0441\u044e \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0439, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442.    \/\/ os.ModePerm (0777) \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u043b\u043d\u044b\u0435 \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0445 \u043f\u0430\u043f\u043e\u043a.    err := os.MkdirAll(targetDir, os.ModePerm)    if err != nil {        \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443, \u0435\u0441\u043b\u0438 \u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0435\u0442 \u043f\u0440\u0430\u0432 \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0430\u043f\u043e\u043a \u0438\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0441 \u0434\u0438\u0441\u043a\u043e\u043c.        return CheckResult{            Success: false,             Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438: \" + err.Error(),        }    }    \/\/ 3. \u0424\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0443\u0442\u0438 \u043a \u0444\u0430\u0439\u043b\u0443:    targetFile := filepath.Join(targetDir, \"secrets.yml\")    \/\/ 4. \u0417\u0430\u043f\u0438\u0441\u044c \u0434\u0430\u043d\u043d\u044b\u0445:    \/\/ os.WriteFile \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0444\u0430\u0439\u043b (\u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439) \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0432 \u043d\u0435\u0433\u043e \u0431\u0430\u0439\u0442\u044b.    \/\/ \u041f\u0440\u0430\u0432\u0430 0644 (rw-r--r--) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0443 \u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u0444\u0430\u0439\u043b, \u0430 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0442\u0430\u0442\u044c.    \/\/ \u042d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u0441\u0435\u043a\u0440\u0435\u0442\u0430\u043c\u0438.    err = os.WriteFile(targetFile, []byte(yamlContent), 0644)    if err != nil {        \/\/ \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u043d\u0435 \u0443\u0434\u0430\u043b\u0430\u0441\u044c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0444\u0430\u0439\u043b \u0437\u0430\u043d\u044f\u0442 \u0438\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d), \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443.        return CheckResult{            Success: false,             Message: \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0430\u0439\u043b secrets.yml: \" + err.Error(),        }    }    \/\/ 5. \u0423\u0441\u043f\u0435\u0448\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435:    \/\/ \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0430 \u043d\u0430 \u0434\u0438\u0441\u043a.    return CheckResult{        Success: true,         Message: \"\u0424\u0430\u0439\u043b secrets.yml \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d!\",    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 6: \u0412\u044b\u0431\u043e\u0440 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u0435\u043f\u043b\u043e\u044f<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0440\u0442\u0430.<\/p>\n<p><strong>\u0412\u044b\u0431\u043e\u0440 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"javascript\">\/\/ \u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c ID \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f.\/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0443\u043b\u0435\u043c, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u044b\u0431\u043e\u0440 \u0435\u0449\u0435 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d.let selectedOptionNum = 0; \/** * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u044b\u0431\u043e\u0440 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u0435\u043f\u043b\u043e\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. * @param {number} num - \u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f. *\/window.selectDeployOption = function(num) {    selectedOptionNum = num;    \/\/ 1. \u0421\u0431\u0440\u043e\u0441 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f:    \/\/ \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u0432\u0441\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c 'deploy-option-card' \u0438 \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0443 \u043d\u0438\u0445 \u043a\u043b\u0430\u0441\u0441 'selected',    \/\/ \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u043d\u043e\u0432\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0441\u0442\u0430\u0440\u0430\u044f \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u0430 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435.    document.querySelectorAll('.deploy-option-card').forEach(card =&gt; {        card.classList.remove('selected');    });    \/\/ 2. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043d\u043e\u0432\u043e\u0433\u043e \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f:    \/\/ \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u043f\u043e ID (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, 'opt-1') \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0439 \u043a\u043b\u0430\u0441\u0441 'selected' \u0434\u043b\u044f \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 CSS.    const selectedCard = document.getElementById(`opt-${num}`);    if (selectedCard) {        selectedCard.classList.add('selected');    }    \/\/ 3. \u0410\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430:    \/\/ \u0414\u0435\u043b\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 '\u0414\u0430\u043b\u0435\u0435' \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u044b\u0431\u0440\u0430\u043d.    const btnNext = document.getElementById('btn-deploy-next');    if (btnNext) {        btnNext.disabled = false;    }}\/** * \u041f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u0447\u0430\u043b\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Ansible-\u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f. *\/window.confirmDeployment = function() {    \/\/ \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043b \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 (\u0432\u044b\u0431\u0440\u0430\u043d 0), \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435.    if (selectedOptionNum === 0) return;    \/\/ \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0433\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u043d\u043a\u0442\u0430 \u0432 UI.    const optionTexts = {        1: \"\u041f\u0443\u043d\u043a\u0442 1. \u041f\u043e\u043b\u043d\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 (\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 + VPS)\",        2: \"\u041f\u0443\u043d\u043a\u0442 2. \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 (\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 + VPS)\",        3: \"\u041f\u0443\u043d\u043a\u0442 3. \u041f\u043e\u043b\u043d\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 (\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439)\",        4: \"\u041f\u0443\u043d\u043a\u0442 4. \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 (\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439)\",        5: \"\u041f\u0443\u043d\u043a\u0442 5. \u041f\u043e\u043b\u043d\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 (VPS)\",        6: \"\u041f\u0443\u043d\u043a\u0442 6. \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 (VPS)\"    };    \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f.    const confirmTextEl = document.getElementById('confirm-option-text');    if (confirmTextEl) {        confirmTextEl.innerText = optionTexts[selectedOptionNum] || `\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u2116${selectedOptionNum}`;    }    \/\/ 4. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 (\u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435):    \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \"\u0414\u0435\u0442\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043b\u043e\u0433\u043e\u0432\" (-vvv).    const isVerbose = document.getElementById('verbose-logs-toggle').checked;    const confirmVerboseEl = document.getElementById('confirm-verbose-text');        \/\/ \u0412\u044b\u0432\u043e\u0434\u0438\u043c \u0441\u0442\u0430\u0442\u0443\u0441 \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.    if (confirmVerboseEl) {        confirmVerboseEl.innerText = isVerbose ? \"\u0414\u0430 (-vvv)\" : \"\u041d\u0435\u0442\";    }    \/\/ \u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043a \u0448\u0430\u0433\u0443 7 \u2014 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u044d\u043a\u0440\u0430\u043d\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0434\u0435\u043f\u043b\u043e\u044f.    goToStep(7);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0428\u0430\u0433 7: \u0417\u0430\u043f\u0443\u0441\u043a \u0434\u0435\u043f\u043b\u043e\u044f<\/h4>\n<p>\u042d\u0442\u043e \u0441\u0430\u043c\u044b\u0439 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0448\u0430\u0433, \u0433\u0434\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0432 \u043f\u0441\u0435\u0432\u0434\u043e\u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435 \u0438 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u043b\u043e\u0433\u043e\u0432 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u0412 \u043f\u043e\u043b\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445 \u0432\u0438\u0434\u043d\u043e, \u043a\u0430\u043a \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435 6 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0434\u0435\u043f\u043b\u043e\u044f \u0447\u0435\u0440\u0435\u0437 <code>switch\/case<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043a\u0430\u043a \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u043a\u043e\u0434\u044b \u0438\u0437 \u043f\u043e\u0442\u043e\u043a\u0430 \u043b\u043e\u0433\u043e\u0432.<\/p>\n<p><strong>\u0411\u044d\u043a\u0435\u043d\u0434: \u0417\u0430\u043f\u0443\u0441\u043a Ansible \u0432 PTY (<\/strong><code><strong>app.go<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/\/ ansiEscape \u2014 \u044d\u0442\u043e \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f ANSI escape-\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0435\u0439.\/\/ \u042d\u0442\u0438 \u043a\u043e\u0434\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0441\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 (\u0446\u0432\u0435\u0442\u0430 \u0448\u0440\u0438\u0444\u0442\u0430, \u0444\u043e\u043d\u0430, \u0436\u0438\u0440\u043d\u043e\u0441\u0442\u044c).\/\/ \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u043b\u043e\u0433\u0438 \u0432 HTML-\u0431\u043b\u043e\u043a &lt;pre&gt;, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0445 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0432 \u043b\u043e\u0433\u0430\u0445 \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u043b\u0438\u0441\u044c \"\u043c\u0443\u0441\u043e\u0440\u043d\u044b\u0435\" \u0441\u0438\u043c\u0432\u043e\u043b\u044b.var ansiEscape = regexp.MustCompile(\"\\x1b\\\\][^\\x07\\x1b]*(\\x07|\\x1b\\\\\\\\)\" + \/\/ \u041a\u043e\u0434\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439\"|\\x1b\\\\[[0-9;?]*[a-zA-Z]\" + \/\/ \u041a\u043e\u0434\u044b \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0433\u0440\u0430\u0444\u0438\u043a\u0438 (\u0446\u0432\u0435\u0442\u0430 \u0438 \u0441\u0442\u0438\u043b\u0438)\"|\\x1b[()[A-Za-z0-9]\" + \/\/ \u041a\u043e\u0434\u044b \u0432\u044b\u0431\u043e\u0440\u0430 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0438 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432\"|\\x1b[=&gt;78]\", \/\/ \u041a\u043e\u0434\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438\/\u0441\u0431\u0440\u043e\u0441\u0430 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430)\/\/ stripAnsi \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0432\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 ANSI-\u043a\u043e\u0434\u044b \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u0438.func stripAnsi(s string) string {return ansiEscape.ReplaceAllString(s, \"\")}\/\/ RunDeployment \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 Ansible-\u043f\u043b\u044d\u0439\u0431\u0443\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 Docker-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \/\/ \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f PTY \u0434\u043b\u044f \u044d\u043c\u0443\u043b\u044f\u0446\u0438\u0438 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430.func (a *App) RunDeployment(option int, verbose bool) CheckResult {var steps []stringdebugArgs := \"\"if verbose {debugArgs = \" -vvv\" \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u043b\u0430\u0433 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0433\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e}\/\/ 1. \u0424\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u0435\u043f\u043b\u043e\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\/\/ \u041a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433 \u2014 \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u043a\u043e\u043c\u0430\u043d\u0434\u044b ansible-playbook \u0447\u0435\u0440\u0435\u0437 docker composeswitch option {case 1:steps = []string{fmt.Sprintf(\"ansible-playbook -i ansible\/inventory.ini ansible\/deploy.yml --limit vps,local --tags bootstrap%s\", debugArgs),fmt.Sprintf(\"ansible-playbook -i ansible\/inventory.ini ansible\/deploy.yml --limit vps --skip-tags bootstrap%s\", debugArgs),fmt.Sprintf(\"ansible-playbook -i ansible\/inventory.ini ansible\/deploy.yml --limit local --skip-tags bootstrap%s\", debugArgs),}\/\/ ... (\u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u0435\u0439\u0441\u044b)default:return CheckResult{Success: false, Message: \"\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f!\"}}\/\/ 2. \u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 stepsfor _, ansibleCmd := range steps {wailsRuntime.EventsEmit(a.ctx, \"deploy-log\")\/\/ \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430cmdArgs := []string{\"compose\",\"-f\", \".\/ServeHub-2-main\/docker-compose.ansible.yaml\",\"run\", \"--rm\", \"ansible\",\"sh\", \"-c\", ansibleCmd,}\/\/ 3. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0441\u0435\u0432\u0434\u043e\u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430 (PTY):\/\/ Ansible \u0442\u0440\u0435\u0431\u0443\u0435\u0442 PTY, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u044b \u0438 \u0446\u0432\u0435\u0442\u043e\u0432\u0443\u044e \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443.ptty, err := pty.New()if err != nil {return CheckResult{Success: false, Message: fmt.Sprintf(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0441\u0435\u0432\u0434\u043e\u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b: %s\", err.Error())}}_ = ptty.Resize(200, 50) \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440 \u043e\u043a\u043d\u0430 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430 \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430 \u0441\u0442\u0440\u043e\u043a\/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430cmd := ptty.CommandContext(a.ctx, \"docker\", cmdArgs...)cmd.Env = append(os.Environ(), \"TERM=xterm-256color\") \/\/ \u042d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043bif err := cmd.Start(); err != nil {ptty.Close()return CheckResult{Success: false, Message: fmt.Sprintf(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Docker: %s\", err.Error())}}\/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0441\u044b\u043b\u043a\u0438 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 App, \u0447\u0442\u043e\u0431\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u044f SendEnter \u043c\u043e\u0433\u043b\u0430 \u0438\u043c\u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044fa.ptty = pttya.cmdStdin = pttya.activeCmd = cmd\/\/ 4. \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u0447\u0442\u0435\u043d\u0438\u0435 \u043b\u043e\u0433\u043e\u0432:\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0447\u0438\u0442\u0430\u0435\u0442 \u0432\u044b\u0432\u043e\u0434 \u0438\u0437 PTYgo func(p pty.Pty) {scanner := bufio.NewScanner(p)scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) \/\/ \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0431\u0443\u0444\u0435\u0440 \u0434\u043b\u044f \u0434\u043b\u0438\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u043e\u043a \u043b\u043e\u0433\u0430for scanner.Scan() {clean := stripAnsi(scanner.Text())if clean == \"\" {continue}\/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 (Wails \u0441\u043e\u0431\u044b\u0442\u0438\u0435)wailsRuntime.EventsEmit(a.ctx, \"deploy-log\", clean+\"\\n\")}}(ptty)\/\/ \u0416\u0434\u0435\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043b\u044d\u0439\u0431\u0443\u043a\u0430waitErr := cmd.Wait()\/\/ \u041e\u0447\u0438\u0441\u0442\u043a\u0430 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044bptty.Close()a.ptty = nila.cmdStdin = nilif waitErr != nil {return CheckResult{Success: false,Message: fmt.Sprintf(\"Ansible \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439: %s\", waitErr.Error()),}}}return CheckResult{Success: true,Message: \"\u0412\u0441\u0435 \u044d\u0442\u0430\u043f\u044b \u0434\u0435\u043f\u043b\u043e\u044f Ansible \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b!\",}}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434: \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u043e\u0432 (<\/strong><code><strong>main.js<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"javascript\">\/** * \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043f\u043b\u043e\u044f \u0447\u0435\u0440\u0435\u0437 Ansible. * \u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u043e\u0439 \u043d\u0430 \u043f\u043e\u0442\u043e\u043a\u0438 \u043b\u043e\u0433\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043e\u0448\u0438\u0431\u043e\u043a. *\/window.startDeployment = async function() {    \/\/ 1. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 DOM-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c    const logContainer = document.getElementById('deploy-logs-container');    const logPre = document.getElementById('deploy-logs');    const btnRun = document.getElementById('btn-deploy-run');    const btnBack = document.getElementById('btn-deploy-back');    const btnExit = document.getElementById('btn-deploy-exit');    const btnEnter = document.getElementById('btn-deploy-enter');    const isVerbose = document.getElementById('verbose-logs-toggle').checked;    \/\/ 2. \u041f\u0435\u0440\u0432\u0438\u0447\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 UI: \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043b\u043e\u0433\u043e\u0432 \u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430    logContainer.style.display = 'block';    logPre.innerText = '\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043f\u043b\u043e\u044f...\\n';    \/\/ \u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u043a\u043d\u043e\u043f\u043e\u043a \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043c\u043e\u0433 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441    btnRun.disabled = true;    btnBack.disabled = true;    if (btnExit) btnExit.style.display = 'none';    \/\/ \u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \"Enter\" \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 Ansible    if (btnEnter) btnEnter.style.display = 'block';    const postDeployGuide = document.getElementById('post-deploy-guide');    if (postDeployGuide) postDeployGuide.style.display = 'none';    \/**     * \u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043b\u043e\u0433\u043e\u0432 \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b \u0441 \u0430\u0432\u0442\u043e\u0441\u043a\u0440\u043e\u043b\u043b\u043e\u043c.     *\/    function addLog(message) {        const logPre = document.getElementById('deploy-logs');        \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438 \u0441\u043a\u0440\u043e\u043b\u043b \u0432 \u0441\u0430\u043c\u043e\u043c \u043d\u0438\u0437\u0443 (\u043f\u043e\u0433\u0440\u0435\u0448\u043d\u043e\u0441\u0442\u044c 5px)        const isAtBottom = (logPre.scrollHeight - logPre.clientHeight) &lt;= (logPre.scrollTop + 5);                \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0432 \u0431\u043b\u043e\u043a \u043b\u043e\u0433\u043e\u0432        logPre.innerText += message;                \/\/ \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043f\u0440\u043e\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u043b \u043b\u043e\u0433 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u0432\u0435\u0440\u0445, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u043e\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u0435\u043c \u0432\u043d\u0438\u0437        if (isAtBottom) {            logPre.scrollTop = logPre.scrollHeight;        }    }    \/\/ 3. \u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043b\u043e\u0433\u043e\u0432:    \/\/ \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c EventsOn \u0438\u0437 Wails, \u0447\u0442\u043e\u0431\u044b \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438\u0437 Go.    \/\/ \u0424\u043b\u0430\u0433 deployLogListened \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u043d\u043e\u0433\u043e\u043a\u0440\u0430\u0442\u043d\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443 \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0445.    if (!window.deployLogListened) {        EventsOn('deploy-log', (message) =&gt; {            addLog(message);        });        window.deployLogListened = true;    }    \/\/ 4. \u0417\u0430\u043f\u0443\u0441\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043f\u043b\u043e\u044f:    try {        \/\/ \u0412\u044b\u0437\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 Go-\u0431\u044d\u043a\u0435\u043d\u0434\u0430. \u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 \u044d\u0442\u043e\u0442 \u043f\u043e\u0442\u043e\u043a, \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f.        const result = await RunDeployment(selectedOptionNum, isVerbose);                \/\/ \u0421\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u0432\u0432\u043e\u0434\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0434\u0435\u043f\u043b\u043e\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f        if (btnEnter) btnEnter.style.display = 'none';        \/\/ 5. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 (\u0423\u0441\u043f\u0435\u0445\/\u041e\u0448\u0438\u0431\u043a\u0430 Ansible):        if (result.success) {            logPre.innerText += `\\n[ \u0423\u0421\u041f\u0415\u0425 ]: ${result.message}\\n`;            logPre.style.borderColor = '#818cf8'; \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 \u0433\u0440\u0430\u043d\u0438\u0446 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0445\u0430            btnRun.style.display = 'none';            btnBack.style.display = 'none';            if (btnExit) {                btnExit.style.display = 'block';                btnExit.className = 'btn-primary';            }            \/\/ \u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0434\u0435\u043f\u043b\u043e\u044f            const guide = document.getElementById('post-deploy-guide');            if (guide) guide.style.display = 'block';        } else {            \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0448\u0438\u0431\u043e\u043a Ansible (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0448\u0438\u0431\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438)            logPre.innerText += `\\n[ \u041e\u0428\u0418\u0411\u041a\u0410 ]: \u0414\u0435\u043f\u043b\u043e\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u043d\u0435\u0443\u0434\u0430\u0447\u0435\u0439.\\n\u041f\u0440\u0438\u0447\u0438\u043d\u0430: ${result.message}\\n`;            logPre.style.borderColor = '#e5484d'; \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 \u0433\u0440\u0430\u043d\u0438\u0446 (\u043a\u0440\u0430\u0441\u043d\u044b\u0439)            btnRun.disabled = false;            btnBack.disabled = false;            if (btnExit) btnExit.style.display = 'block';        }    } catch (err) {        \/\/ 6. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0441\u0430\u043c\u043e\u0433\u043e Docker\/Ansible)        if (btnEnter) btnEnter.style.display = 'none';        logPre.innerText += `\\n[ \u041a\u0420\u0418\u0422\u0418\u0427\u0415\u0421\u041a\u0410\u042f \u041e\u0428\u0418\u0411\u041a\u0410 \u0421\u0418\u0421\u0422\u0415\u041c\u042b ]: \u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u044b\u0437\u043e\u0432\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0430:\\n${err}\\n`;        logPre.style.borderColor = '#e5484d';        btnRun.disabled = false;        btnBack.disabled = false;        if (btnExit) btnExit.style.display = 'block';    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u0430 Enter \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 (<\/strong><code><strong>app.go<\/strong><\/code><strong>):<\/strong><\/p>\n<pre><code class=\"go\">\/\/ SendEnter \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0438\u0433\u043d\u0430\u043b \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043b\u0430\u0432\u0438\u0448\u0438 \"Enter\" \u0432 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 Ansible.\/\/ \u0414\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 Ansible \u043f\u0440\u0438\u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f\/\/ \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 sudo-\u043f\u0430\u0440\u043e\u043b\u044f \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0438 \u0444\u0430\u0439\u043b\u0430).func (a *App) SendEnter() CheckResult {    \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430:    \/\/ \u041c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d \u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u0432\u0432\u043e\u0434\u0430 (stdin).    \/\/ \u0415\u0441\u043b\u0438 cmdStdin \u0440\u0430\u0432\u0435\u043d nil, \u0437\u043d\u0430\u0447\u0438\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043b\u0438\u0431\u043e \u0435\u0449\u0435 \u043d\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d, \u043b\u0438\u0431\u043e \u0443\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f,    \/\/ \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u0430 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u0430.    if a.cmdStdin == nil {        return CheckResult{            Success: false,             Message: \"\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u0434\u0435\u043f\u043b\u043e\u044f \u043d\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u0438\u043b\u0438 \u043d\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0432\u043e\u0434\u0430\",        }    }    \/\/ 2. \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0435\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438:    \/\/ \u041c\u044b \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u043f\u043e\u0442\u043e\u043a \u0432\u0432\u043e\u0434\u0430 \"\\r\\n\" (Return + Newline).     \/\/ \u042d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u044d\u043c\u0443\u043b\u044f\u0446\u0438\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043b\u0430\u0432\u0438\u0448\u0438 Enter \u0434\u043b\u044f POSIX-\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0445 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u043e\u0432,    \/\/ \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 Ansible \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c.    _, err := a.cmdStdin.Write([]byte(\"\\r\\n\"))        \/\/ 3. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0437\u0430\u043f\u0438\u0441\u0438:    \/\/ \u0415\u0441\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u043f\u043e\u0442\u043e\u043a (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u0430\u043d\u0430\u043b \u0431\u044b\u043b \u0437\u0430\u043a\u0440\u044b\u0442 \u0434\u043e \u0442\u043e\u0433\u043e, \u043a\u0430\u043a    \/\/ \u043c\u044b \u0443\u0441\u043f\u0435\u043b\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0431\u0430\u0439\u0442\u044b), \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u0442\u0430\u0442\u0443\u0441 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u044b.    if err != nil {        return CheckResult{            Success: false,             Message: fmt.Sprintf(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c Enter: %s\", err.Error()),        }    }    \/\/ 4. \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438:    \/\/ \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u0442\u0430\u0442\u0443\u0441 \u0443\u0441\u043f\u0435\u0445\u0430.    return CheckResult{        Success: true,         Message: \"\u0421\u0438\u0433\u043d\u0430\u043b Enter \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\",    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0442\u0430\u043a\u0436\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0430:<\/p>\n<pre><code class=\"javascript\">\/** * \u0424\u0443\u043d\u043a\u0446\u0438\u044f-\u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u0438\u0433\u043d\u0430\u043b\u0430 Enter \u0432 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 Ansible. * \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 Ansible * (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0438 \u0444\u0430\u0439\u043b\u043e\u0432), \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0442. *\/window.sendEnterToAnsible = async function() {    try {        \/\/ \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u043c GO-\u0444\u0443\u043d\u043a\u0446\u0438\u044e SendEnter, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0432 app.go        \/\/ \u041e\u043d\u0430 \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043a \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 \u0432\u0432\u043e\u0434\u0443 (stdin) \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430 (PTY)        const result = await SendEnter();                \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435        if (!result.success) {            \/\/ \u0415\u0441\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0443\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f),             \/\/ \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u043d\u0435 \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f            console.warn(\"\u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 Enter \u0431\u044b\u043b\u0430 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0430:\", result.message);        } else {            \/\/ \u041e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e: \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043b\u043e\u0433 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f            console.log(\"\u0421\u0438\u0433\u043d\u0430\u043b Enter \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441.\");        }    } catch (err) {        \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0448\u0438\u0431\u043e\u043a (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0442\u0435\u0440\u044f \u0441\u0432\u044f\u0437\u0438 \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c Wails)        \/\/ \u0412\u044b\u0432\u043e\u0434\u0438\u043c \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c        console.error(\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 Enter:\", err);    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041f\u043e\u0441\u0442\u0430\u0440\u0430\u043b\u0441\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u043c\u043e\u043c\u0435\u043d\u0442\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u0430 Go Wails. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u043e\u0434\u043d\u0430\u043a\u043e \u0432 \u0446\u0435\u043b\u043e\u043c \u043f\u043e\u043b\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0438 \u0440\u0430\u0431\u043e\u0442\u0430 \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e \u0432\u0438\u0434\u043d\u044b, \u043d\u0430\u0434\u0435\u044e\u0441\u044c \u0447\u0442\u043e \u044d\u0442\u043e \u0442\u0430\u043a.<\/p>\n<p>\u0412\u0435\u0440\u043e\u044f\u0442\u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0443 ServeHub-2, \u044f \u0435\u0449\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043c\u043e\u0433\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e \u0441\u0431\u043e\u0440\u043a\u0443 AppImage, \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043f\u0440\u043e\u0439\u0442\u0438\u0441\u044c \u043f\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c \u0438 \u0442\u0434, \u043d\u043e \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e \u044f \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u044e \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u0435\u0449\u0435 \u0447\u0442\u043e-\u0442\u043e, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<p>\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u0432\u0438\u0434 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0441\u043a\u0430\u0447\u0430\u0432 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a \u0438\u043b\u0438 AppImage \u0438\u0437 \u0440\u0435\u043b\u0438\u0437\u043e\u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u044f \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u044b\u0442\u0430\u043b\u0441\u044f \u0432\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0438\u0434\u0435\u043e, \u043d\u043e \u0443 \u043c\u0435\u043d\u044f \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0442 \u0442\u0430\u043a.<\/p>\n<p>\u0412\u0441\u044f \u043a\u043e\u0434\u043e\u0432\u0430\u044f \u0431\u0430\u0437\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f, \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u044b \u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0434\u043b\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430:<\/p>\n<p>\ud83d\udd17 <strong>GitHub-\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439:<\/strong> <a href=\"https:\/\/github.com\/canntstand\/ServeHub-2\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/canntstand\/ServeHub-2<\/a><\/p>\n<p>\u0411\u0443\u0434\u0443 \u0440\u0430\u0434 \u0432\u0430\u0448\u0435\u043c\u0443 \u0444\u0438\u0434\u0431\u0435\u043a\u0443 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445!<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1060336\/\">https:\/\/habr.com\/ru\/articles\/1060336\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c 2 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u043f\u0440\u043e \u043c\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 ServeHub-2. \u0417\u0434\u0435\u0441\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u0440\u0435\u0447\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u0430 \u043d\u0430 Go Wails v2.\u041f\u043e\u0432\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0434\u0442\u0438 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c: \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0432 \u0446\u0435\u043b\u043e\u043c, \u043f\u043e\u0442\u043e\u043c \u043a\u043e\u0434 \u043f\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c \u0441 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430\u043c\u0438 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0430, \u0432 \u044d\u0442\u043e\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d \u0432\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043a\u043e\u0434 \u0431\u0435\u0437 \u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u0441 \u0440\u0430\u0437\u0431\u043e\u0440\u043e\u043c \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.\u0412 Wails \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0444\u0430\u0439\u043b\u0430\u043c\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 90% \u0440\u0430\u0431\u043e\u0442\u044b \u044d\u0442\u043e index.html, styles.css, main.js, app.go. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u044b \u043b\u0438\u0431\u043e \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c, \u043b\u0438\u0431\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0432 \u0441\u0430\u043c\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0435.\u0414\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 html, css, js \u0431\u0435\u0437 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432 (\u0442\u0430\u043a \u043a\u0430\u043a \u044f \u0432 \u043d\u0438\u0445 \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0441\u044c). Frontend \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f, a html \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f js \u0444\u0443\u043d\u043a\u0446\u0438\u0438.\u0427\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a?\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0435\u0442 \u0433\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c.\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\/\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043b\u0438 Docker (\u0432 \u043d\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f ansible).\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0440\u044f\u0434\u043e\u043c \u0441 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u043e\u043c.\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 secrets.yml \u0434\u043b\u044f ansible (\u0444\u043e\u0440\u043c\u0430 \u0432\u0432\u043e\u0434\u0430, \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u0442\u043e\u043c \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0435 .yml \u043a\u043e\u043d\u0444\u0438\u0433\u0430).\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438: \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433, \u0442\u0430\u043a\u0436\u0435 \u0434\u0430\u0435\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0435\u0431\u0435 \u043a\u043e\u043f\u0438\u044e.\u0412\u044b\u0431\u043e\u0440 \u0434\u0435\u043f\u043b\u043e\u044f (\u043a\u0430\u043a \u0441\u0435\u0431\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u0435\u0441\u0442\u0438 Ansible). \u0414\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 3 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430: \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043d\u0430 \u043e\u0431\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0440\u0430\u0437\u0443. \u0418 \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u0435\u0449\u0435 \u043d\u0430 2: \u043f\u043e\u043b\u043d\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 (\u0441 \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u0430), \u043b\u0438\u0431\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0435\u0441\u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f.\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u0438 \u043d\u0430\u0447\u0430\u043b\u043e \u0434\u0435\u043f\u043b\u043e\u044f. \u041f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043a\u043d\u043e \u0441 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u043e\u043c \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0438\u0448\u0443\u0442\u0441\u044f \u043b\u043e\u0433\u0438 ansible.\u0415\u0441\u043b\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439 &#8212; \u043a\u043d\u043e\u043f\u043a\u0430 \u0432\u044b\u0439\u0442\u0438 \u0438\u043b\u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0437\u0430\u043d\u043e\u0432\u043e. \u0415\u0441\u043b\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0443\u0441\u043f\u0435\u0448\u0435\u043d &#8212; \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0433\u0430\u0439\u0434 \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u0432\u044b\u0445\u043e\u0434\u0430.\u042d\u043a\u0441\u043a\u0443\u0440\u0441 \u0432 Wails (\u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442)Wails \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 Frontend \u0438 Backend \u0447\u0430\u0441\u0442\u044c. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0435\u0433\u043e \u0441\u043c\u044b\u0441\u043b \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043e\u043d \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u0430\u043a \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0432\u044b\u0432\u043e\u0434\u044b Go \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 js. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c: \u0432 Go \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443, Wails \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Go \u043a JS, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u041f\u0440\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f:Go 1.21+NPM \/ Node.js (15+)Wails v2libwebkit2gtk-4.1-dev \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f Linux (\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u0432\u0438\u0436\u043a\u0430 Wails)\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c wails init.\u041f\u0435\u0440\u0432\u044b\u0439 \u0444\u0430\u0439\u043b \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c &#8212; main.go (\u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0434\u0430\u0435\u043c \u0435\u043c\u0443 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e \u0442\u0438\u043f\u0443 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043e\u043a\u043d\u0430, \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0430\u0441\u0441\u0435\u0442\u044b).main.go:package main \/\/ \u041e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044fimport (&#171;embed&#187; \/\/ \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f&#187;github.com\/wailsapp\/wails\/v2&#8243; \/\/ \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u0430\u043a\u0435\u0442 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Wails&#187;github.com\/wailsapp\/wails\/v2\/pkg\/options&#187; \/\/ \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f&#187;github.com\/wailsapp\/wails\/v2\/pkg\/options\/assetserver&#187; \/\/ \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432)\/\/ \u0414\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0430 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0430 go:embed:\/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0443 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043f\u043a\u0438 &#8216;frontend\/dist&#8217; \u0432 \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b.\/\/ &#8216;all:&#8217; \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0441\u043a\u0440\u044b\u0442\u044b\u0435 \u0444\u0430\u0439\u043b\u044b (\u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u0441 \u0442\u043e\u0447\u043a\u0438) \/\/ \u0438 \u0444\u0430\u0439\u043b\u044b \u0441 \u043d\u0438\u0436\u043d\u0438\u043c \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u0435\u043c.\/\/\/\/go:embed all:frontend\/distvar assets embed.FS \/\/ \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f &#8216;assets&#8217; \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0443\u044e \u0432\u0441\u0435 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044bfunc main() {\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b App (\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 Go, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0438\u0437 JS)app := NewApp()\/\/ \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f Wails \u0441 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438:err := wails.Run(&amp;options.App{Title:     &#171;ServeHub-2 Installer&#187;, \/\/ \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043e\u043a\u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044fWidth:     1024,                  \/\/ \u0428\u0438\u0440\u0438\u043d\u0430 \u043e\u043a\u043d\u0430Height:    768,                   \/\/ \u0412\u044b\u0441\u043e\u0442\u0430 \u043e\u043a\u043d\u0430Frameless: true,                  \/\/ \u041e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439 \u0440\u0430\u043c\u043a\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430AssetServer: &amp;assetserver.Options{Assets: assets, \/\/ \u041f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u043d\u0443\u044e \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0430\u0441\u0441\u0435\u0442\u043e\u0432},OnStartup: app.startup, \/\/ \u041c\u0435\u0442\u043e\u0434, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430Bind: []interface{}{ \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 App \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0438\u0437 JavaScriptapp,},})\/\/ \u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430, \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0435\u0451 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044cif err != nil {println(&#171;Error:&#187;, err.Error())}}\u041b\u043e\u0433\u0438\u043a\u0430 \u0441\u043c\u0435\u043d\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e Wails &#8212; \u044d\u0442\u043e SPA, \u0442.\u0435 \u0432\u0441\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0430 javascript \u043c\u0435\u043d\u044f\u0435\u0442 \u0435\u0435 \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u201c\u0448\u0430\u0433\u043e\u0432\u201d.index.html (\u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430):&lt;!&#8212; \u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 &#8212;&gt;&lt;div class=&#187;progress-container&#187;&gt;    &lt;!&#8212;       \u041e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0448\u0430\u0433\u043e\u0432.       \u0417\u0434\u0435\u0441\u044c \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c .step-dot,       \u043a\u043e\u0442\u043e\u0440\u044b\u0435 JavaScript \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044e updateStepProgress.    &#8212;&gt;    &lt;div class=&#187;progress-bar-track&#187;&gt;        &lt;div id=&#187;step-progress-fill&#187; class=&#187;progress-bar-fill&#187;&gt;&lt;\/div&gt;                &lt;!&#8212; \u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 7 \u0448\u0430\u0433\u0430\u043c &#8212;&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;1&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;2&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;3&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;4&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;5&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;6&#8243;&gt;&lt;\/div&gt;        &lt;div class=&#187;step-dot&#187; data-step=&#187;7&#8243;&gt;&lt;\/div&gt;    &lt;\/div&gt;    &lt;!&#8212; \u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u044d\u0442\u0430\u043f\u0430 &#8212;&gt;    &lt;div class=&#187;step-label&#187;&gt;        \u0428\u0430\u0433 &lt;span id=&#187;step-progress-current&#187;&gt;1&lt;\/span&gt; \u0438\u0437 7 \u2014         &lt;span id=&#187;step-progress-title&#187;&gt;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c&lt;\/span&gt;    &lt;\/div&gt;&lt;\/div&gt;\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u044e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b HTML \u0438 \u043b\u043e\u0433\u0438\u043a\u0443 App.js.main.js (\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u0440\u043e\u0443\u0442\u0435\u0440):\/** * \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u0448\u0430\u0433\u043e\u0432 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0435. *\/const STEP_TITLES = {    1: &#8216;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c&#8217;,    2: &#8216;\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439&#8217;,    3: &#8216;\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432&#8217;,    4: &#8216;\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 secrets.yml&#8217;,    5: &#8216;\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438&#8217;,    6: &#8216;\u0412\u044b\u0431\u043e\u0440 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u0435\u043f\u043b\u043e\u044f&#8217;,    7: &#8216;\u0417\u0430\u043f\u0443\u0441\u043a \u0434\u0435\u043f\u043b\u043e\u044f&#8217;,};\/** * \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430 \u0438 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0448\u0430\u0433\u043e\u0432. * @param {number} stepNumber &#8212; \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0448\u0430\u0433\u0430. *\/function updateStepProgress(stepNumber) {    const totalSteps = 7;    const fill = document.getElementById(&#8216;step-progress-fill&#8217;);    const currentEl = document.getElementById(&#8216;step-progress-current&#8217;);    const titleEl = document.getElementById(&#8216;step-progress-title&#8217;);    \/\/ 1. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0448\u0438\u0440\u0438\u043d\u044b \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430 (\u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f)    if (fill) {        \/\/ \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 1-\u043c \u0448\u0430\u0433\u0435 \u0431\u044b\u043b\u043e 0%, \u0430 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u043c \u2014 100%        const pct = ((stepNumber &#8212; 1) \/ (totalSteps &#8212; 1)) * 100;        fill.style.width = `${pct}%`;    }        \/\/ 2. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432    if (currentEl) currentEl.innerText = stepNumber;    if (titleEl) titleEl.innerText = STEP_TITLES[stepNumber] || &#187;;    \/\/ 3. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a (dots) \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430    document.querySelectorAll(&#8216;.step-dot&#8217;).forEach((dot) =&gt; {        const dotStep = parseInt(dot.getAttribute(&#8216;data-step&#8217;), 10);        dot.classList.remove(&#8216;completed&#8217;, &#8216;active&#8217;);                \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u043a\u0430\u043a &#8216;completed&#8217;, \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u2014 \u043a\u0430\u043a &#8216;active&#8217;        if (dotStep &lt; stepNumber) dot.classList.add(&#8216;completed&#8217;);        if (dotStep === stepNumber) dot.classList.add(&#8216;active&#8217;);    });}\/** * \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438. * \u0421\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0446\u0435\u043b\u0435\u0432\u043e\u0439. * @param {number} stepNumber &#8212; \u041d\u043e\u043c\u0435\u0440 \u0448\u0430\u0433\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430. *\/window.goToStep = function(stepNumber) {    \/\/ 1. \u0421\u043a\u0440\u044b\u0442\u0438\u0435 \u0432\u0441\u0435\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u0448\u0430\u0433\u043e\u0432    document.querySelectorAll(&#8216;.step&#8217;).forEach(step =&gt; {        step.classList.remove(&#8216;active&#8217;);    });    \/\/ 2. \u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0446\u0435\u043b\u0435\u0432\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430    const nextStep = document.getElementById(`step-${stepNumber}`);    if (nextStep) {        nextStep.classList.add(&#8216;active&#8217;);    }    \/\/ 3. \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430    updateStepProgress(stepNumber);    \/\/ 4. \u0422\u0440\u0438\u0433\u0433\u0435\u0440\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438:    \/\/ \u041f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0435 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b (\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0438\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443).    if (stepNumber === 2) {        runDockerCheck();    }    if (stepNumber === 3) {        runInstallation();    }}\u0420\u0435\u0432\u044c\u044e \u043f\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0428\u0430\u0433 1: \u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c (SPA \u0438 \u0420\u043e\u0443\u0442\u0438\u043d\u0433)\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430. \u041f\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f goToStep(2), \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.HTML-\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0430 \u0448\u0430\u0433\u0430 (index.html):&lt;!&#8212;   \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 ServeHub-2.  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.&#8212;&gt;&lt;div class=&#187;installer-container&#187;&gt;    &lt;header class=&#187;installer-header&#187;&gt;        &lt;h1&gt;ServeHub-2&lt;\/h1&gt;        &lt;p class=&#187;subtitle&#187;&gt;\u041c\u0430\u0441\u0442\u0435\u0440 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438&lt;\/p&gt;    &lt;\/header&gt;    &lt;main class=&#187;installer-content&#187;&gt;        &lt;p&gt;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c! \u042d\u0442\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 &lt;strong&gt;ServeHub-2&lt;\/strong&gt; \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435.&lt;\/p&gt;    &lt;\/main&gt;    &lt;footer class=&#187;installer-actions&#187;&gt;        &lt;!&#8212; \u041a\u043d\u043e\u043f\u043a\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0448\u0430\u0433\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 &#8212;&gt;        &lt;button id=&#187;btn-start&#187; class=&#187;btn-primary&#187; onclick=&#187;goToStep(2)&#187;&gt;            \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c        &lt;\/button&gt;                &lt;!&#8212; \u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f &#8212;&gt;        &lt;a href=&#187;https:\/\/github.com\/canntstand\/ServeHub-2&#8243; target=&#187;_blank&#187; class=&#187;link-github&#187;&gt;            &lt;img src=&#187;assets\/github-icon.svg&#187; alt=&#187;GitHub Logo&#187; class=&#187;icon&#187;&gt;            GitHub Repository        &lt;\/a&gt;    &lt;\/footer&gt;&lt;\/div&gt;\u0428\u0430\u0433 2: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0441 \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u044f Docker. \u0414\u043b\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0430 \u0432 Go \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f CheckResult.\u0411\u044d\u043a\u0435\u043d\u0434 \u043d\u0430 Go (app.go):\/\/ CheckResult \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u0431\u044d\u043a\u0435\u043d\u0434\u0430.\/\/ \u041f\u043e\u043b\u044f \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b \u0442\u0435\u0433\u0430\u043c\u0438 json \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 JavaScript &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-487694","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/487694","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=487694"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/487694\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=487694"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=487694"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=487694"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}