1616#include < cstdlib>
1717#include < iostream>
1818#include < string>
19+ #include < array>
20+ #include < cstdio>
21+ #include < memory>
22+ #include < optional>
1923
2024namespace vix ::commands::deploy::runner
2125{
@@ -49,6 +53,41 @@ namespace vix::commands::deploy::runner
4953 return std::system (cmd.c_str ()) == 0 ;
5054 }
5155
56+ std::optional<std::string> run_capture (const std::string &cmd)
57+ {
58+ std::array<char , 2048 > buffer{};
59+ std::string output;
60+
61+ std::unique_ptr<FILE, decltype (&pclose)> pipe (
62+ popen (cmd.c_str (), " r" ),
63+ pclose);
64+
65+ if (!pipe)
66+ return std::nullopt ;
67+
68+ while (fgets (buffer.data (), static_cast <int >(buffer.size ()), pipe.get ()) != nullptr )
69+ output += buffer.data ();
70+
71+ while (!output.empty () &&
72+ (output.back () == ' \n ' ||
73+ output.back () == ' \r ' ||
74+ output.back () == ' ' ||
75+ output.back () == ' \t ' ))
76+ {
77+ output.pop_back ();
78+ }
79+
80+ if (output.empty ())
81+ return std::nullopt ;
82+
83+ return output;
84+ }
85+
86+ struct DeployState
87+ {
88+ std::string previousGitRef{};
89+ };
90+
5291 int fail (
5392 const DeployConfig &cfg,
5493 const DeployOptions &options,
@@ -60,16 +99,86 @@ namespace vix::commands::deploy::runner
6099 if (!fixMessage.empty ())
61100 output::fix (std::cerr, fixMessage);
62101
63- if (cfg.logsOnFailure && !cfg.serviceName .empty ())
102+ if (cfg.logsOnFailure )
103+ {
104+ const std::string logsCmd =
105+ " vix logs errors --repeated -n " +
106+ std::to_string (cfg.logLines );
107+
108+ output::step (std::cout, " Failure Logs" );
109+ output::command (std::cout, logsCmd);
110+
111+ if (!options.dryRun )
112+ (void )std::system (logsCmd.c_str ());
113+ }
114+
115+ return 1 ;
116+ }
117+
118+ bool rollback_deploy (
119+ const DeployConfig &cfg,
120+ const DeployOptions &options,
121+ const DeployState &state)
122+ {
123+ if (!cfg.rollback )
124+ return true ;
125+
126+ if (state.previousGitRef .empty ())
127+ {
128+ output::warn (std::cerr, " rollback skipped: no previous git revision captured" );
129+ return false ;
130+ }
131+
132+ output::step (std::cout, " Rollback" );
133+
134+ const std::string resetCmd =
135+ " git reset --hard " +
136+ shell_quote (state.previousGitRef );
137+
138+ if (!run_cmd (resetCmd, options))
139+ return false ;
140+
141+ if (!run_cmd (cfg.buildCommand , options))
142+ return false ;
143+
144+ if (!run_cmd (" vix service restart" , options))
145+ return false ;
146+
147+ if (!run_cmd (" vix service status" , options))
148+ return false ;
149+
150+ output::ok (std::cout, " rollback completed" );
151+ return true ;
152+ }
153+
154+ int fail_with_rollback (
155+ const DeployConfig &cfg,
156+ const DeployOptions &options,
157+ const DeployState &state,
158+ const std::string &message,
159+ const std::string &fixMessage = {})
160+ {
161+ output::error (std::cerr, message);
162+
163+ if (!fixMessage.empty ())
164+ output::fix (std::cerr, fixMessage);
165+
166+ if (cfg.rollback )
167+ {
168+ if (!rollback_deploy (cfg, options, state))
169+ {
170+ output::error (std::cerr, " rollback failed" );
171+ output::fix (std::cerr, " inspect the repository and service manually" );
172+ }
173+ }
174+
175+ if (cfg.logsOnFailure )
64176 {
65177 const std::string logsCmd =
66- " sudo journalctl -u " +
67- shell_quote (cfg.serviceName ) +
68- " -n " +
69- std::to_string (cfg.logLines ) +
70- " --no-pager" ;
178+ " vix logs errors --repeated -n " +
179+ std::to_string (cfg.logLines );
71180
72- output::step (std::cout, " Last Logs" );
181+ output::step (std::cout, " Failure Logs" );
73182 output::command (std::cout, logsCmd);
74183
75184 if (!options.dryRun )
@@ -124,11 +233,7 @@ namespace vix::commands::deploy::runner
124233
125234 output::step (std::cout, " Restart Service" );
126235
127- const std::string cmd =
128- " sudo systemctl restart " +
129- shell_quote (cfg.serviceName );
130-
131- return run_cmd (cmd, options);
236+ return run_cmd (" vix service restart" , options);
132237 }
133238
134239 bool verify_service (
@@ -140,23 +245,19 @@ namespace vix::commands::deploy::runner
140245
141246 output::step (std::cout, " Service Status" );
142247
143- const std::string cmd =
144- " systemctl is-active --quiet " +
145- shell_quote (cfg.serviceName );
146-
147- return run_cmd (cmd, options);
248+ return run_cmd (" vix service status" , options);
148249 }
149250
150- bool service_health (
251+ bool health_check (
151252 const DeployConfig &cfg,
152253 const DeployOptions &options)
153254 {
154255 if (!cfg.healthLocal && !cfg.healthPublic )
155256 return true ;
156257
157- output::step (std::cout, " Service Health" );
258+ output::step (std::cout, " Health Check " );
158259
159- return run_cmd (" vix service health" , options);
260+ return run_cmd (" vix health" , options);
160261 }
161262
162263 bool proxy_check (
@@ -190,6 +291,18 @@ namespace vix::commands::deploy::runner
190291 {
191292 output::print_summary (std::cout, cfg, options);
192293
294+ DeployState state;
295+
296+ if (cfg.rollback && !options.dryRun )
297+ {
298+ const auto ref = run_capture (" git rev-parse HEAD 2>/dev/null" );
299+
300+ if (ref)
301+ state.previousGitRef = *ref;
302+ else
303+ output::warn (std::cerr, " rollback enabled but current git revision could not be captured" );
304+ }
305+
193306 if (cfg.serviceName .empty ())
194307 {
195308 output::error (std::cerr, " Missing deployment service name." );
@@ -198,46 +311,102 @@ namespace vix::commands::deploy::runner
198311 }
199312
200313 if (!pull_latest (cfg, options))
201- return fail (cfg, options, " git pull failed" , " check repository state and branch" );
314+ {
315+ return fail_with_rollback (
316+ cfg,
317+ options,
318+ state,
319+ " git pull failed" ,
320+ " check repository state and branch" );
321+ }
202322
203323 if (cfg.pull )
204324 output::ok (std::cout, " git pull completed" );
205325
206326 if (!build_app (cfg, options))
207- return fail (cfg, options, " build failed" , " run the configured build command manually" );
327+ {
328+ return fail_with_rollback (
329+ cfg,
330+ options,
331+ state,
332+ " build failed" ,
333+ " run the configured build command manually" );
334+ }
208335
209336 output::ok (std::cout, " build completed" );
210337
211338 if (!run_tests (cfg, options))
212- return fail (cfg, options, " tests failed" , " run the configured test command manually" );
339+ {
340+ return fail_with_rollback (
341+ cfg,
342+ options,
343+ state,
344+ " tests failed" ,
345+ " run the configured test command manually" );
346+ }
213347
214348 if (cfg.tests )
215349 output::ok (std::cout, " tests completed" );
216350
217351 if (!restart_service (cfg, options))
218- return fail (cfg, options, " service restart failed" , " run `vix service restart`" );
352+ {
353+ return fail_with_rollback (
354+ cfg,
355+ options,
356+ state,
357+ " service restart failed" ,
358+ " run `vix service restart`" );
359+ }
219360
220361 output::ok (std::cout, " service restarted" );
221362
222363 if (!verify_service (cfg, options))
223- return fail (cfg, options, " service is not active" , " run `vix service status`" );
364+ {
365+ return fail_with_rollback (
366+ cfg,
367+ options,
368+ state,
369+ " service is not active" ,
370+ " run `vix service status`" );
371+ }
224372
225373 output::ok (std::cout, " service is active" );
226374
227- if (!service_health (cfg, options))
228- return fail (cfg, options, " service health check failed" , " run `vix service health`" );
375+ if (!health_check (cfg, options))
376+ {
377+ return fail_with_rollback (
378+ cfg,
379+ options,
380+ state,
381+ " health check failed" ,
382+ " run `vix health`" );
383+ }
229384
230385 if (cfg.healthLocal || cfg.healthPublic )
231- output::ok (std::cout, " service health checks passed" );
386+ output::ok (std::cout, " health checks passed" );
232387
233388 if (!proxy_check (cfg, options))
234- return fail (cfg, options, " proxy check failed" , " run `vix proxy nginx check`" );
389+ {
390+ return fail_with_rollback (
391+ cfg,
392+ options,
393+ state,
394+ " proxy check failed" ,
395+ " run `vix proxy nginx check`" );
396+ }
235397
236398 if (cfg.proxyCheck || cfg.proxyReload )
237399 output::ok (std::cout, " proxy config is valid" );
238400
239401 if (!proxy_reload (cfg, options))
240- return fail (cfg, options, " proxy reload failed" , " run `vix proxy nginx reload`" );
402+ {
403+ return fail_with_rollback (
404+ cfg,
405+ options,
406+ state,
407+ " proxy reload failed" ,
408+ " run `vix proxy nginx reload`" );
409+ }
241410
242411 if (cfg.proxyReload )
243412 output::ok (std::cout, " proxy reloaded" );
0 commit comments