How can a Django app on a Raspberry Pi safely trigger an upgrade of itself using apt and systemd?

I'm distributing a Django/Python web application as a Debian package on a Raspberry Pi. I’d like to implement an "Upgrade" button in the web UI that triggers a sudo apt update && sudo apt -y install my_app to upgrade the application package.

The challenge is that the app is trying to replace itself while it's still running — the very code initiating the upgrade is part of what will be upgraded.

What I've tried:

The Upgrade button sends a POST to the Django backend.

The backend uses python subprocess to start a oneshot systemd unit (gui-package-update.service) that runs the upgrade commands (via sudo).

The Django app itself is started on boot using another systemd service and runs as a non-root user.

I've configured sudoers to allow the Django user to run sudo systemctl start gui-package-update.service without a password.

A second POST api call is used to monitor the state of the upgrade, those states being:

  • running - the server is up and running
  • upgrading - the server has received the POST command to start an upgrade
  • 404/500 - the server is currently rebooting

The 404/500 errors are caught in the JS front end and interpreted to mean that the server is "reloading".

Problem:

This approach works only the first time after a reboot. On subsequent attempts, the oneshot service does not run again. Although there are no services in the failed state, I've tried resetting the service using systemctl reset-failed, but it doesn't fix the issue.

My suspicion is that the postinst script of the new Debian package runs systemctl daemon-reexec or systemctl daemon-reload, which interferes with the active systemd process launched by the app during the upgrade.

Question:

What is a reliable design pattern for allowing a Django app to trigger its own upgrade via a GUI button, considering the constraints of apt, systemd, and the fact that the running code is being replaced?

I'm looking for approaches that:

  • Avoid conflicts between systemd reloads and running services
  • Allow repeated upgrades without requiring a reboot
  • Work cleanly with Debian packaging and postinst scripts

I'm not going to insist on using a OneShot service, that's just the approach I tried.

Here is an example of my unit service file:

[Unit]
Description=Update specific ODIN16 GUI and collateral packages
After=network.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'apt update && apt-get install -y my_app'
StandardOutput=append:/var/log/gui-package-update.log
StandardError=append:/var/log/gui-package-update.log
User=root
Group=root
RemainAfterExit=no
Restart=no

[Install]
WantedBy=multi-user.target
Вернуться на верх