Automatic backup of save variables file

This macro is meant to back up your save_variables file. it runs at every printer startup, and is then scheduled to run every 4 hours.
It is checking for atleast 24 hours to have passed, and if the contents of the SVF changed, backs it up to the directory entered into the path variable

Yeah idk not much more to say about this.
Maybe someones day will get saved by this :stuck_out_tongue:

Update 07/09/2025

Added guard so it doesnt run while printing or moving

[gcode_macro SVF_BACKUP]
variable_path: "~/printer_data/backup/save_variables_backup"
variable_min_hours_for_backup: 24
gcode:
    {% set sv      = printer.printer.lookup_object('save_variables', none) %}
    {% set can_run = printer.motion_report.live_velocity <= 0.001 and printer.print_stats.state|lower != 'printing' %}
    {% if sv is not none %}
        {% if can_run %}
            {% set import  = printer.printer.__class__.__init__.__globals__.importlib.import_module %}
            {% set time    = import('time') %}
            {% set os      = import('os') %}
            {% set glob    = import('glob') %}
            {% set pathlib = import('pathlib') %}

            UPDATE_DELAYED_GCODE ID=_SVF_BACKUP DURATION={4*60*60}
            {% set orig     = sv.filename %}
            {% set orig_abs = os.path.expanduser(orig) %}
            {% set defaultp = os.path.dirname(orig_abs) %}
            {% set basep    = path if path|length else defaultp %}
            {% set absdir   = os.path.realpath(os.path.expanduser(basep)) %}

            {% if not os.path.isdir(absdir) %}
                {% set _ = os.makedirs(absdir) %}{% endif %}

            {% set files = glob.glob(absdir ~ '/svf-backup_*.cfg')|sort %}
            {% set latest  = None %}
            {% set last_ts = 0 %}
            {% if files|length %}
                {% set latest  = files|last %}
                {% set stamp   = latest.rsplit('/',1)[1][11:-4] %}
                {% set last_ts = time.mktime(time.strptime(stamp, '%Y-%m-%d_%H%M'))|int %}{% endif %}

            {% set now_ts = time.time()|int %}
            {% if latest is none or (now_ts - last_ts) >= min_hours_for_backup|int(24)*60*60 %}
                {% set curr_txt = pathlib.Path(orig).read_text() %}
                {% set prev_txt = pathlib.Path(latest).read_text() if latest is not none else '' %}
                {% if latest is none or curr_txt != prev_txt %}
                    {% set ts   = time.strftime('%Y-%m-%d_%H%M', time.localtime(now_ts)) %}
                    {% set dest = absdir ~ '/svf-backup_' ~ ts ~ '.cfg' %}
                    {% set _    = pathlib.Path(dest).write_text(curr_txt) %}
                    RESPOND MSG="[SVF_BACKUP] wrote backup '{dest}'"    
                {% endif %}
            {% endif %}
        {% else %}
            UPDATE_DELAYED_GCODE ID=_SVF_BACKUP DURATION={15*60} # reschedule 15 mins from now
        {% endif %}
    {% endif %}

[delayed_gcode _SVF_BACKUP]
initial_duration: 1
gcode:
    SVF_BACKUP
1 Like