Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4d528da
Added ui for btrfs subvolume names
fused0 Nov 23, 2025
85efc0b
Merge branch 'linuxmint:master' into btrfs_subvol_names
fused0 Nov 23, 2025
6cf8745
Improve subvolume name option layout
fused0 Nov 23, 2025
bd54ed4
early out of subvolume listing if configured subvolume is not the sam…
fused0 Nov 23, 2025
f3e3bc8
fix add missing return value in Main.query_subvolume_id
fused0 Nov 23, 2025
52b7cf4
Subvolume selection is now a combobox
fused0 Nov 24, 2025
9cc883b
improved subvolume selection by adding a distribution hint
fused0 Nov 24, 2025
0ebe92a
combine subvol comboboxes
fused0 Nov 24, 2025
4e13156
detect subvolume layout
fused0 Nov 24, 2025
69b8468
Merge branch 'linuxmint:master' into btrfs_subvol_names
fused0 Nov 24, 2025
04bfffd
cleanup create_btrfs_subvolume_selection
fused0 Nov 24, 2025
dd769f5
call init_backend on layout change
fused0 Nov 24, 2025
d7b5070
also call type_changed on subvol layout change
fused0 Nov 24, 2025
0e71046
Disable "include home subvol" option when home subvolume name is empty
fused0 Nov 30, 2025
7f55d83
fix for debian style layout
fused0 Nov 30, 2025
aaccff3
added ui for custom subvolume layout
fused0 Nov 30, 2025
a1f3892
Merge branch 'linuxmint:master' into btrfs_subvol_names
fused0 Nov 30, 2025
44edb09
Change variable name in SnapshotBackendBox
fused0 Dec 8, 2025
7528ba9
Simplify toggling checkbox sensitivity in UsersBox
fused0 Dec 8, 2025
deb0ecb
Change early out of query_subvolume_id to has_key
fused0 Dec 8, 2025
6a0240f
Properly fix query_subvolume_id
fused0 Dec 8, 2025
216393b
Simplify vbox_subvolume_custom.visible assignment
fused0 Dec 8, 2025
59339bb
Fix typo in comment
fused0 Dec 8, 2025
b32ac4e
Making sure to consistently call Main::check_btrfs_layout_system befo…
fused0 Dec 9, 2025
4b5cf13
fix null pointer exception in query_subvolume_quota
fused0 Dec 9, 2025
01f0445
Merge branch 'linuxmint:master' into btrfs_subvol_names
fused0 Dec 9, 2025
3ceb27d
document and fix query_subvolume_id
fused0 Dec 10, 2025
4031b17
document and fix query_subvolume_quota
fused0 Dec 10, 2025
4630806
Added comments to SnapshotBackendBox
fused0 Dec 16, 2025
c6cd723
Main: add check_btrfs_system_config to check if the BTRFS system conf…
fused0 Jan 6, 2026
0611f62
AppConsole: make sure App.check_btrfs_layout_system is called before …
fused0 Jan 6, 2026
7d29174
Merge branch 'linuxmint:master' into btrfs_subvol_names
fused0 Jan 6, 2026
3228362
Fix more occurences of @ and @home in:
fused0 Jan 9, 2026
aefb4e4
AppConsole: Don't call check_btrfs_layout_system before device selection
fused0 Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/AppConsole.vala
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,10 @@ public class AppConsole : GLib.Object {

public bool delete_snapshot(){

if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){
return false;
}

select_snapshot_device(true);

select_snapshot_for_deletion();
Expand All @@ -1367,6 +1371,10 @@ public class AppConsole : GLib.Object {
}

public bool delete_all_snapshots(){

if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){
return false;
}

select_snapshot_device(true);

Expand Down
279 changes: 206 additions & 73 deletions src/Core/Main.vala

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/Core/Snapshot.vala
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public class Snapshot : GLib.Object{
live = json_get_bool(config,"live",false);
string type = config.get_string_member_with_default("type", "rsync");

string extension = (type == "btrfs") ? "@" : "localhost";
string extension = (type == "btrfs") ? App.root_subvolume_name : "localhost";
distro = LinuxDistro.get_dist_info(path_combine(path, extension));

//log_debug("repo.mount_path: %s".printf(repo.mount_path));
Expand All @@ -239,7 +239,7 @@ public class Snapshot : GLib.Object{

foreach(string subvol_name in subvols.get_members()){

if ((subvol_name != "@")&&(subvol_name != "@home")){ continue; }
if ((subvol_name != App.root_subvolume_name)&&(subvol_name != App.home_subvolume_name)){ continue; }

paths[subvol_name] = path.replace(repo.mount_path, repo.mount_paths[subvol_name]);

Expand Down Expand Up @@ -321,7 +321,7 @@ public class Snapshot : GLib.Object{
string fstab_path = path_combine(path, "/localhost/etc/fstab");

if (btrfs_mode){
fstab_path = path_combine(path, "/@/etc/fstab");
fstab_path = path_combine(path, @"/$(App.root_subvolume_name)/etc/fstab");
}

fstab_list = FsTabEntry.read_file(fstab_path);
Expand All @@ -332,7 +332,7 @@ public class Snapshot : GLib.Object{
string crypttab_path = path_combine(path, "/localhost/etc/crypttab");

if (btrfs_mode){
crypttab_path = path_combine(path, "/@/etc/crypttab");
crypttab_path = path_combine(path, @"/$(App.root_subvolume_name)/etc/crypttab");
}

cryttab_list = CryptTabEntry.read_file(crypttab_path);
Expand Down Expand Up @@ -439,7 +439,7 @@ public class Snapshot : GLib.Object{

public bool has_subvolumes(){
foreach(FsTabEntry en in fstab_list){
if (en.options.contains("subvol=@")){
if (en.options.contains("subvol=" + App.root_subvolume_name)){
return true;
}
}
Expand Down
135 changes: 90 additions & 45 deletions src/Core/SnapshotRepo.vala
Original file line number Diff line number Diff line change
Expand Up @@ -191,33 +191,33 @@ public class SnapshotRepo : GLib.Object{
if (mount_path.length == 0){
return false;
}

// rsync
mount_paths["@"] = "";
mount_paths["@home"] = "";

if (btrfs_mode){

mount_paths["@"] = mount_path;
mount_paths["@home"] = mount_path; //default

if (App.root_subvolume_name == "") return false;

// Don't add mount paths if root_subvolume_name or home_subvolume_name are empty.
// We don't add default values either anymore, because it could lead us to bugs.
mount_paths[App.root_subvolume_name] = mount_path;
if (App.home_subvolume_name != "") mount_paths[App.home_subvolume_name] = mount_path;
device_home = device; //default

// mount @home if on different disk -------

var repo_subvolumes = Subvolume.detect_subvolumes_for_system_by_path(path_combine(mount_path,"@"), this, parent_window);
var repo_subvolumes = Subvolume.detect_subvolumes_for_system_by_path(path_combine(mount_path,App.root_subvolume_name), this, parent_window);

if (repo_subvolumes.has_key("@home")){
if (App.home_subvolume_name != "" && repo_subvolumes.has_key(App.home_subvolume_name)){

var subvol = repo_subvolumes["@home"];
var subvol = repo_subvolumes[App.home_subvolume_name];

if (subvol.device_uuid != device.uuid){

// @home is on a separate device
device_home = subvol.get_device();

mount_paths["@home"] = unlock_and_mount_device(device_home, App.mount_point_app + "/backup-home");
mount_paths[App.home_subvolume_name] = unlock_and_mount_device(device_home, App.mount_point_app + "/backup-home");

if (mount_paths["@home"].length == 0){
if (mount_paths[App.home_subvolume_name].length == 0){
return false;
}
}
Expand Down Expand Up @@ -469,51 +469,96 @@ public class SnapshotRepo : GLib.Object{

//log_debug("checking selected device");

// Snapshot repo is available if the config is valid.
bool ok = check_config(out status_message, out status_details, out status_code);

if (ok){
log_debug(status_message);
log_debug("is_available: ok");
} else {
log_debug(status_message);
log_debug("is_available: false");
}

return ok;
}

/*
* Validates the btrfs config and displays an appropriate error message for the user.
* It's a little convoluted because it needs to catch any misconfiguration leading
* to error states.
*/
private bool check_config(out string title, out string msg, out SnapshotLocationStatus code){

log_debug("SnapshotRepo: check_btrfs_config()");

log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string()));

if (device == null){
if (App.backup_uuid == null || App.backup_uuid.length == 0){
log_debug("device is null");
status_message = _("Snapshot device not selected");
status_details = _("Select the snapshot device");
status_code = SnapshotLocationStatus.NOT_SELECTED;
log_debug("is_available: false");
title = _("Snapshot device not selected");
msg = _("Select the snapshot device");
code = SnapshotLocationStatus.NOT_SELECTED;
return false;
}
else{
status_message = _("Snapshot device not available");
status_details = _("Device not found") + ": UUID='%s'".printf(App.backup_uuid);
status_code = SnapshotLocationStatus.NOT_AVAILABLE;
log_debug("is_available: false");
title = _("Snapshot device not available");
msg = _("Device not found") + ": UUID='%s'".printf(App.backup_uuid);
code = SnapshotLocationStatus.NOT_AVAILABLE;
return false;
}
}
else{
if (btrfs_mode){
bool ok = has_btrfs_system();
if (ok){
log_debug("is_available: ok");
}
return ok;
}
else{
log_debug("is_available: ok");
return true;

if (btrfs_mode) {
// Run the btrfs checks from Main.check_btrfs_system_config.
if (!App.check_btrfs_system_config(out title, out msg)) {
code = SnapshotLocationStatus.NO_BTRFS_SYSTEM;
return false;
}
}
}

public bool has_btrfs_system(){

log_debug("SnapshotRepo: has_btrfs_system()");
// Run some additional checks, these cases are unlikely to come up,
// but they could point to bugs in SnapshotRepo code or any other
// code related to configuring the system layout.
// Bear with me, even though it seems confusing at first...

var root_path = path_combine(mount_paths["@"],"@");
log_debug("root_path=%s".printf(root_path));
log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string()));
if (btrfs_mode){
if (!dir_exists(root_path)){
status_message = _("Selected snapshot device is not a system disk");
status_details = _("Select BTRFS system disk with root subvolume (@)");
status_code = SnapshotLocationStatus.NO_BTRFS_SYSTEM;
log_debug(status_message);
// Check the home subvolume configuration.
// home_path can be null if mount_paths[App.home_subvolume_name] doesn't exist.
var home_path = path_combine(mount_paths[App.home_subvolume_name], App.home_subvolume_name);
var home_subvolume_configured = App.home_subvolume_name != "";
var has_home_mount_path = mount_paths.has_key(App.home_subvolume_name);

log_debug("home_path=%s".printf(home_path));
log_debug("home_subvolume_configured=%s".printf(home_subvolume_configured.to_string()));
log_debug("has_home_mount_path=%s".printf(has_home_mount_path.to_string()));

// If home_subvolume_configured and the directory does not exists, the
// configuration is invalid.
if (!has_home_mount_path || (home_subvolume_configured && !dir_exists(home_path))) {
title = _("Home subvolume configuration is invalid");
msg = _("Home subvolume is configured but the path does not exist") + " (" + App.home_subvolume_name + ")";
code = SnapshotLocationStatus.NO_BTRFS_SYSTEM;
return false;
}

// Further check root subvolume configuration.
// We already know App.root_subvolume_name is not empty, but we still need to check if
// the key exists (it might not if there is no mount path associated to it).
var has_root_mount_path = mount_paths.has_key(App.root_subvolume_name);
// root_path can still be null if has_root_mount_path is false.
var root_path = path_combine(mount_paths[App.root_subvolume_name], App.root_subvolume_name);

log_debug("root_path=%s".printf(root_path));
log_debug("has_root_mount_path=%s".printf(has_root_mount_path.to_string()));

// If we don't have a mount_path for root, or root_path is null, or the root_path
// directory doesn't exist, the configuration is invalid.
// Technically, we don't need to explicitly check root_path here, it's implied by
// !has_root_mount_path.
if (!has_root_mount_path || !dir_exists(root_path)){
title = _("Root subvolume configuration is invalid");
msg = _("Root subvolume is configured but the path does not exist. Select BTRFS system disk with root subvolume ") + " (" + App.home_subvolume_name + ")";
code = SnapshotLocationStatus.NO_BTRFS_SYSTEM;
return false;
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/Core/Subvolume.vala
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ public class Subvolume : GLib.Object{
public bool remove(){

if (is_system_subvolume){
if (name == "@"){
path = path_combine(App.mount_point_app + "/backup", "@");
if (name == App.root_subvolume_name){
path = path_combine(App.mount_point_app + "/backup", App.root_subvolume_name);
}
else if (name == "@home"){
path = path_combine(App.mount_point_app + "/backup-home", "@home");
else if (name == App.home_subvolume_name){
path = path_combine(App.mount_point_app + "/backup-home", App.home_subvolume_name);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/Gtk/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,13 @@ class MainWindow : Gtk.Window{


private void restore(){

ui_sensitive(false);

if (App.btrfs_mode && (App.check_btrfs_layout_system(this) == false)){
ui_sensitive(true);
return;
}

TreeIter iter;
TreeSelection sel;
Expand Down Expand Up @@ -827,6 +834,7 @@ class MainWindow : Gtk.Window{
App.dry_run = false;
App.repo.load_snapshots();
refresh_all();
ui_sensitive(true);
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/Gtk/RestoreWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class RestoreWindow : Gtk.Window{

if (App.btrfs_mode){

if (App.snapshot_to_restore.subvolumes.has_key("@home")){
if (App.snapshot_to_restore.subvolumes.has_key(App.home_subvolume_name)){

notebook.page = Tabs.USERS;
}
Expand Down
Loading