Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
97 changes: 55 additions & 42 deletions src/Core/Main.vala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public class Main : GLib.Object{
public bool include_btrfs_home_for_backup = false;
public bool include_btrfs_home_for_restore = false;
public static bool btrfs_version__can_recursive_delete = false;

public string root_subvolume_name = "@";
public string home_subvolume_name = "@home";

public bool stop_cron_emails = true;

Expand Down Expand Up @@ -482,9 +485,9 @@ public class Main : GLib.Object{

log_debug("check_btrfs_layout_system()");

bool supported = sys_subvolumes.has_key("@");
bool supported = sys_subvolumes.has_key(root_subvolume_name);
if (include_btrfs_home_for_backup){
supported = supported && sys_subvolumes.has_key("@home");
supported = supported && sys_subvolumes.has_key(home_subvolume_name);
}

if (!supported){
Expand Down Expand Up @@ -515,18 +518,18 @@ public class Main : GLib.Object{

if (dev_home != dev_root){

supported = supported && check_btrfs_volume(dev_root, "@", unlock);
supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock);

if (include_btrfs_home_for_backup){
supported = supported && check_btrfs_volume(dev_home, "@home", unlock);
supported = supported && check_btrfs_volume(dev_home, home_subvolume_name, unlock);
}
}
else{
if (include_btrfs_home_for_backup){
supported = supported && check_btrfs_volume(dev_root, "@,@home", unlock);
}
else{
supported = supported && check_btrfs_volume(dev_root, "@", unlock);
supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock);
}
}
}
Expand Down Expand Up @@ -1714,9 +1717,9 @@ public class Main : GLib.Object{

log_msg(_("Creating new backup...") + "(BTRFS)");

log_msg(_("Saving to device") + ": %s".printf(repo.device.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths["@"]));
log_msg(_("Saving to device") + ": %s".printf(repo.device.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths[root_subvolume_name]));
if ((repo.device_home != null) && (repo.device_home.uuid != repo.device.uuid)){
log_msg(_("Saving to device") + ": %s".printf(repo.device_home.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths["@home"]));
log_msg(_("Saving to device") + ": %s".printf(repo.device_home.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths[home_subvolume_name]));
}

// take new backup ---------------------------------
Expand All @@ -1733,11 +1736,11 @@ public class Main : GLib.Object{

// create subvolume snapshots

var subvol_names = new string[] { "@" };
var subvol_names = new string[] { root_subvolume_name };

if (include_btrfs_home_for_backup){

subvol_names = new string[] { "@","@home" };
subvol_names = new string[] { root_subvolume_name,home_subvolume_name };
}

foreach(var subvol_name in subvol_names){
Expand Down Expand Up @@ -1780,7 +1783,7 @@ public class Main : GLib.Object{

//log_msg(_("Writing control file..."));

snapshot_path = path_combine(repo.mount_paths["@"], "timeshift-btrfs/snapshots/%s".printf(snapshot_name));
snapshot_path = path_combine(repo.mount_paths[root_subvolume_name], "timeshift-btrfs/snapshots/%s".printf(snapshot_name));

string initial_tags = (tag == "ondemand") ? "" : tag;

Expand Down Expand Up @@ -2338,11 +2341,11 @@ public class Main : GLib.Object{
// final check - check if target root device is mounted

if (btrfs_mode){
if (repo.mount_paths["@"].length == 0){
if (repo.mount_paths[root_subvolume_name].length == 0){
log_error(_("BTRFS device is not mounted") + ": @");
return false;
}
if (include_btrfs_home_for_restore && (repo.mount_paths["@home"].length == 0)){
if (include_btrfs_home_for_restore && (repo.mount_paths[home_subvolume_name].length == 0)){
log_error(_("BTRFS device is not mounted") + ": @home");
return false;
}
Expand Down Expand Up @@ -2424,7 +2427,7 @@ public class Main : GLib.Object{

if (!App.snapshot_to_restore.subvolumes.has_key(entry.subvolume_name())){ continue; }

if ((entry.subvolume_name() == "@home") && !include_btrfs_home_for_restore){ continue; }
if ((entry.subvolume_name() == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; }
}

string dev_name = entry.device.full_name_with_parent;
Expand Down Expand Up @@ -2460,7 +2463,7 @@ public class Main : GLib.Object{

if (!App.snapshot_to_restore.subvolumes.has_key(entry.subvolume_name())){ continue; }

if ((entry.subvolume_name() == "@home") && !include_btrfs_home_for_restore){ continue; }
if ((entry.subvolume_name() == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; }
}

string dev_name = entry.device.full_name_with_parent;
Expand Down Expand Up @@ -3152,7 +3155,7 @@ public class Main : GLib.Object{

foreach(var subvol in snapshot_to_restore.subvolumes.values){

if ((subvol.name == "@home") && !include_btrfs_home_for_restore){ continue; }
if ((subvol.name == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; }

subvol.restore();
}
Expand Down Expand Up @@ -3216,12 +3219,12 @@ public class Main : GLib.Object{

if (found){
//delete system subvolumes
if (sys_subvolumes.has_key("@") && snapshot_to_restore.subvolumes.has_key("@")){
sys_subvolumes["@"].remove();
if (sys_subvolumes.has_key(root_subvolume_name) && snapshot_to_restore.subvolumes.has_key(root_subvolume_name)){
sys_subvolumes[root_subvolume_name].remove();
log_msg(_("Deleted subvolume") + ": @");
}
if (include_btrfs_home_for_restore && sys_subvolumes.has_key("@home") && snapshot_to_restore.subvolumes.has_key("@home")){
sys_subvolumes["@home"].remove();
if (include_btrfs_home_for_restore && sys_subvolumes.has_key(home_subvolume_name) && snapshot_to_restore.subvolumes.has_key(home_subvolume_name)){
sys_subvolumes[home_subvolume_name].remove();
log_msg(_("Deleted subvolume") + ": @home");
}

Expand Down Expand Up @@ -3249,9 +3252,9 @@ public class Main : GLib.Object{

var subvol_list = new Gee.ArrayList<Subvolume>();

var subvol_names = new string[] { "@" };
var subvol_names = new string[] { root_subvolume_name };
if (include_btrfs_home_for_restore){
subvol_names = new string[] { "@","@home" };
subvol_names = new string[] { root_subvolume_name,home_subvolume_name };
}

foreach(string subvol_name in subvol_names){
Expand Down Expand Up @@ -3280,7 +3283,7 @@ public class Main : GLib.Object{
return false;
}
else{
var subvol_dev = (subvol_name == "@") ? repo.device : repo.device_home;
var subvol_dev = (subvol_name == root_subvolume_name) ? repo.device : repo.device_home;
subvol_list.add(new Subvolume(subvol_name, dst_path, subvol_dev.uuid, repo));

log_msg(_("Moved system subvolume to snapshot directory") + ": %s".printf(subvol_name));
Expand All @@ -3294,11 +3297,11 @@ public class Main : GLib.Object{
else{
// write control file -----------

snapshot_path = path_combine(repo.mount_paths["@"], "timeshift-btrfs/snapshots/%s".printf(snapshot_name));
snapshot_path = path_combine(repo.mount_paths[root_subvolume_name], "timeshift-btrfs/snapshots/%s".printf(snapshot_name));

var snap = Snapshot.write_control_file(
snapshot_path, dt_created, repo.device.uuid,
LinuxDistro.get_dist_info(path_combine(snapshot_path,"@")).full_name(),
LinuxDistro.get_dist_info(path_combine(snapshot_path,root_subvolume_name)).full_name(),
"ondemand", "", 0, true, false, repo);

snap.description = "Before restoring '%s'".printf(snapshot_to_restore.date_formatted);
Expand Down Expand Up @@ -3343,6 +3346,9 @@ public class Main : GLib.Object{
config.set_string_member("parent_device_uuid", backup_parent_uuid);
}

config.set_string_member("root_subvolume_name", root_subvolume_name);
config.set_string_member("home_subvolume_name", home_subvolume_name);

config.set_string_member("do_first_run", false.to_string());
config.set_string_member("btrfs_mode", btrfs_mode.to_string());
config.set_string_member("include_btrfs_home_for_backup", include_btrfs_home_for_backup.to_string());
Expand Down Expand Up @@ -3462,6 +3468,9 @@ public class Main : GLib.Object{
if (cmd_btrfs_mode != null){
btrfs_mode = cmd_btrfs_mode; //override
}

root_subvolume_name = json_get_string(config,"root_subvolume_name", root_subvolume_name);
home_subvolume_name = json_get_string(config,"home_subvolume_name", home_subvolume_name);

backup_uuid = json_get_string(config,"backup_device_uuid", backup_uuid);
backup_parent_uuid = json_get_string(config,"parent_device_uuid", backup_parent_uuid);
Expand Down Expand Up @@ -3530,7 +3539,7 @@ public class Main : GLib.Object{

// load some defaults for first-run based on user's system type

bool supported = sys_subvolumes.has_key("@") && cmd_exists("btrfs"); // && sys_subvolumes.has_key("@home")
bool supported = sys_subvolumes.has_key(root_subvolume_name) && cmd_exists("btrfs"); // && sys_subvolumes.has_key(home_subvolume_name)
if (supported || (cmd_btrfs_mode == true)){
log_msg(_("Selected default snapshot type") + ": %s".printf("BTRFS"));
btrfs_mode = true;
Expand Down Expand Up @@ -3945,8 +3954,8 @@ public class Main : GLib.Object{
update_partitions();

// In BTRFS mode, select the system disk if system disk is BTRFS
if (btrfs_mode && sys_subvolumes.has_key("@")){
var subvol_root = sys_subvolumes["@"];
if (btrfs_mode && sys_subvolumes.has_key(root_subvolume_name)){
var subvol_root = sys_subvolumes[root_subvolume_name];
repo = new SnapshotRepo.from_device(subvol_root.get_device(), parent_win, btrfs_mode);
return;
}
Expand All @@ -3969,7 +3978,7 @@ public class Main : GLib.Object{
if (dev.has_children()) { return false; }

if (btrfs_mode && ((dev.fstype == "btrfs")||(dev.fstype == "luks"))){
if (check_btrfs_volume(dev, "@", unlock)){
if (check_btrfs_volume(dev, root_subvolume_name, unlock)){
return true;
}
}
Expand Down Expand Up @@ -4154,16 +4163,20 @@ public class Main : GLib.Object{
}

public bool query_subvolume_ids(){
bool ok = query_subvolume_id("@");
bool ok = query_subvolume_id(root_subvolume_name);
if ((repo.device_home != null) && (repo.device.uuid != repo.device_home.uuid)){
ok = ok && query_subvolume_id("@home");
ok = ok && query_subvolume_id(home_subvolume_name);
}
return ok;
}

public bool query_subvolume_id(string subvol_name){

log_debug("query_subvolume_id():%s".printf(subvol_name));

// Early out when configured subvolume name != actual.
if(sys_subvolumes[root_subvolume_name] == null || sys_subvolumes[home_subvolume_name] == null)
Comment thread
fused0 marked this conversation as resolved.
Outdated
return;
Comment thread
fused0 marked this conversation as resolved.
Outdated

string cmd = "";
string std_out;
Expand Down Expand Up @@ -4194,14 +4207,14 @@ public class Main : GLib.Object{

Subvolume subvol = null;

if ((sys_subvolumes.size > 0) && line.has_suffix(sys_subvolumes["@"].path.replace(repo.mount_paths["@"] + "/"," "))){
subvol = sys_subvolumes["@"];
if ((sys_subvolumes.size > 0) && line.has_suffix(sys_subvolumes[root_subvolume_name].path.replace(repo.mount_paths[root_subvolume_name] + "/"," "))){
subvol = sys_subvolumes[root_subvolume_name];
}
else if ((sys_subvolumes.size > 0)
&& sys_subvolumes.has_key("@home")
&& line.has_suffix(sys_subvolumes["@home"].path.replace(repo.mount_paths["@home"] + "/"," "))){
&& sys_subvolumes.has_key(home_subvolume_name)
&& line.has_suffix(sys_subvolumes[home_subvolume_name].path.replace(repo.mount_paths[home_subvolume_name] + "/"," "))){

Comment thread
fused0 marked this conversation as resolved.
subvol = sys_subvolumes["@home"];
subvol = sys_subvolumes[home_subvolume_name];
}
else {
foreach(var bak in repo.snapshots){
Expand All @@ -4223,9 +4236,9 @@ public class Main : GLib.Object{
}

public bool query_subvolume_quotas(){
bool ok = query_subvolume_quota("@");
bool ok = query_subvolume_quota(root_subvolume_name);
if (repo.device.uuid != repo.device_home.uuid){
ok = ok && query_subvolume_quota("@home");
ok = ok && query_subvolume_quota(home_subvolume_name);
}
return ok;
}
Expand Down Expand Up @@ -4286,15 +4299,15 @@ public class Main : GLib.Object{

Subvolume subvol = null;

if ((sys_subvolumes.size > 0) && (sys_subvolumes["@"].id == subvol_id)){
if ((sys_subvolumes.size > 0) && (sys_subvolumes[root_subvolume_name].id == subvol_id)){

subvol = sys_subvolumes["@"];
subvol = sys_subvolumes[root_subvolume_name];
}
else if ((sys_subvolumes.size > 0)
&& sys_subvolumes.has_key("@home")
&& (sys_subvolumes["@home"].id == subvol_id)){
&& sys_subvolumes.has_key(home_subvolume_name)
&& (sys_subvolumes[home_subvolume_name].id == subvol_id)){

subvol = sys_subvolumes["@home"];
subvol = sys_subvolumes[home_subvolume_name];
Comment thread
fused0 marked this conversation as resolved.
}
else {
foreach(var bak in repo.snapshots){
Expand Down
4 changes: 2 additions & 2 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; }
Comment thread
fused0 marked this conversation as resolved.

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

Expand Down
20 changes: 10 additions & 10 deletions src/Core/SnapshotRepo.vala
Original file line number Diff line number Diff line change
Expand Up @@ -193,31 +193,31 @@ public class SnapshotRepo : GLib.Object{
}

// rsync
mount_paths["@"] = "";
mount_paths["@home"] = "";
mount_paths[App.root_subvolume_name] = "";
mount_paths[App.home_subvolume_name] = "";

if (btrfs_mode){

mount_paths["@"] = mount_path;
mount_paths["@home"] = mount_path; //default
mount_paths[App.root_subvolume_name] = mount_path;
mount_paths[App.home_subvolume_name] = mount_path; //default
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 (repo_subvolumes.has_key(App.home_subvolume_name)){
Comment thread
fused0 marked this conversation as resolved.
Outdated

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 @@ -505,7 +505,7 @@ public class SnapshotRepo : GLib.Object{

log_debug("SnapshotRepo: has_btrfs_system()");

var root_path = path_combine(mount_paths["@"],"@");
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("btrfs_mode=%s".printf(btrfs_mode.to_string()));
if (btrfs_mode){
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){
Comment thread
fused0 marked this conversation as resolved.
path = path_combine(App.mount_point_app + "/backup-home", App.home_subvolume_name);
}
}

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)){
Comment thread
fused0 marked this conversation as resolved.

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