@@ -721,12 +721,10 @@ func (s *Server) runSudo(ctx context.Context, argv []string, timeout time.Durati
721721}
722722
723723// maintenanceUnits is the set of transient maintenance units that must not
724- // overlap each other or be interrupted by a reboot. Each touches apt/dpkg,
725- // release artefacts, or shells out to the others, and would deadlock on
726- // the dpkg lock or leave half-configured state if any overlapped with
727- // another or with a shutdown.
724+ // be interrupted by a reboot. The orchestrator touches apt/dpkg and release
725+ // artefacts, and would deadlock on the dpkg lock or leave half-configured
726+ // state if it overlapped with a shutdown.
728727var maintenanceUnits = []string {
729- "airplanes-system-upgrade.service" ,
730728 "airplanes-update-orchestrator.service" ,
731729}
732730
@@ -752,51 +750,6 @@ func (s *Server) maintenanceUnitActive(ctx context.Context) string {
752750 return ""
753751}
754752
755- // startTransientUnit kicks off a transient systemd unit via the supplied
756- // pinned argv (sudo systemd-run ...). It refuses with 409 if any maintenance
757- // unit is already busy, and maps systemd-run's "already exists" stderr to a
758- // 409 as well. On success it writes 202 + the unit name.
759- //
760- // The is-active guard and the systemd-run call are serialized via
761- // maintenanceMu so two concurrent POSTs can't both observe an idle state and
762- // then both kick off — by the time the second contender acquires the lock,
763- // the first's transient unit is already registered and is-active reports it
764- // as activating.
765- func (s * Server ) startTransientUnit (w http.ResponseWriter , r * http.Request , argv []string , unit , label string ) {
766- s .maintenanceMu .Lock ()
767- defer s .maintenanceMu .Unlock ()
768- if busy := s .maintenanceUnitActive (r .Context ()); busy != "" {
769- writeJSONError (w , http .StatusConflict , label + " refused: " + busy + " is in progress" )
770- return
771- }
772- cctx , cancel := context .WithTimeout (r .Context (), systemctlTimeout )
773- defer cancel ()
774- res , err := s .runner (cctx , argv )
775- if err != nil {
776- stderr := strings .TrimSpace (string (res .Stderr ))
777- log .Printf ("%s: %v stderr=%q" , label , err , stderr )
778- if strings .Contains (stderr , "already exists" ) || strings .Contains (stderr , "already running" ) {
779- writeJSONError (w , http .StatusConflict , label + " is already in progress" )
780- return
781- }
782- writeJSONError (w , http .StatusInternalServerError , label + " start failed" )
783- return
784- }
785- writeJSON (w , http .StatusAccepted , map [string ]string {
786- "status" : "running" ,
787- "unit" : unit ,
788- "started_at" : time .Now ().UTC ().Format (time .RFC3339 ),
789- })
790- }
791-
792- // /api/system-upgrade (POST): kicks off a transient
793- // airplanes-system-upgrade.service that runs apt-get update + upgrade.
794- // Returns 202 + the unit name so the SPA can stream
795- // /api/log/system-upgrade for live output.
796- func (s * Server ) handleSystemUpgrade (w http.ResponseWriter , r * http.Request ) {
797- s .startTransientUnit (w , r , s .priv .StartSystemUpgrade , "airplanes-system-upgrade.service" , "system upgrade" )
798- }
799-
800753// /api/reboot (POST): refuses with 409 if a maintenance unit is active
801754// (rebooting mid-dpkg would brick the device). Otherwise writes 202 + flushes,
802755// then triggers reboot from a goroutine after a brief delay so the response
0 commit comments