Skip to content

Commit 5da7d54

Browse files
authored
Update main.go
1 parent 1b7ee82 commit 5da7d54

File tree

1 file changed

+83
-67
lines changed

1 file changed

+83
-67
lines changed

source-code/main.go

Lines changed: 83 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
package main
22

33
import (
4-
"flag"
54
"fmt"
65
"os"
76
"os/exec"
87
"path/filepath"
9-
"strconv"
108
"strings"
119
"time"
1210
)
1311

1412
const (
15-
mountPoint = "/mnt/hroot"
16-
snapshotPrefix = "@pre-update-"
13+
mountPoint = "/mnt/hroot"
14+
snapshotPrefix = "-pre-update-" // teraz używamy tej stałej
1715
updateSnapshot = "@update"
1816
btrfsDevice = "/dev/sda1" // TODO: Detect or configure the Btrfs device
1917
rootSubvolume = "@"
@@ -34,11 +32,11 @@ func main() {
3432
case "switch":
3533
switchCmd()
3634
case "rollback":
37-
rollbackCmd(flag.Args()[1:])
35+
rollbackCmd(os.Args[2:])
3836
case "install":
39-
installCmd(flag.Args()[1:])
37+
installCmd(os.Args[2:])
4038
case "remove":
41-
removeCmd(flag.Args()[1:])
39+
removeCmd(os.Args[2:])
4240
case "clean":
4341
cleanCmd()
4442
case "status":
@@ -54,14 +52,14 @@ func usage() {
5452
Usage: hroot <command> [args]
5553
5654
Commands:
57-
snapshot Create a read-only snapshot of the current root
58-
update Create and update a new snapshot offline
59-
switch Switch to the updated snapshot
60-
rollback <name> Rollback to a specific snapshot by name (e.g., @pre-update-20251130-2013)
61-
install <pkg> Install a package in the current root (non-atomic, for simplicity)
62-
remove <pkg> Remove a package from the current root (non-atomic)
63-
clean Clean up temporary files and unused snapshots
64-
status List available snapshots`)
55+
snapshot Create a read-only snapshot of the current root
56+
update Create and update a new snapshot offline
57+
switch Switch to the updated snapshot (@update → default)
58+
rollback <name> Rollback to a specific snapshot (e.g. @pre-update-20251130-2013)
59+
install <pkg>... Install package(s) in the current root (non-atomic)
60+
remove <pkg>... Remove package(s) from the current root (non-atomic)
61+
clean Clean apt cache (snapshots must be deleted manually)
62+
status List available snapshots`)
6563
}
6664

6765
func runCommand(name string, args ...string) error {
@@ -72,12 +70,32 @@ func runCommand(name string, args ...string) error {
7270
}
7371

7472
func getSnapshotName() string {
75-
return rootSubvolume + "pre-update-" + time.Now().Format("20060102-1504")
73+
return rootSubvolume + snapshotPrefix + time.Now().Format("20060102-1504")
74+
}
75+
76+
// Pobiera Subvolume ID dla podanej względnej ścieżki (np. @update, @pre-update-...)
77+
func getSubvolumeID(subvol string) (string, error) {
78+
path := "/" + subvol // /@update, /@pre-update-20251130-2013 itd.
79+
output, err := exec.Command("btrfs", "subvolume", "show", path).CombinedOutput()
80+
if err != nil {
81+
return "", fmt.Errorf("btrfs subvolume show %s failed: %v\n%s", path, err, output)
82+
}
83+
84+
for _, line := range strings.Split(string(output), "\n") {
85+
line = strings.TrimSpace(line)
86+
if strings.HasPrefix(line, "Subvolume ID:") {
87+
parts := strings.Fields(line)
88+
if len(parts) >= 3 {
89+
return parts[2], nil
90+
}
91+
}
92+
}
93+
return "", fmt.Errorf("Subvolume ID not found for %s", path)
7694
}
7795

7896
func snapshotCmd() {
7997
snapshotName := getSnapshotName()
80-
fmt.Printf("Creating snapshot: %s\n", snapshotName)
98+
fmt.Printf("Creating read-only snapshot: %s\n", snapshotName)
8199
if err := runCommand("btrfs", "subvolume", "snapshot", "-r", "/", snapshotName); err != nil {
82100
fmt.Fprintf(os.Stderr, "Error creating snapshot: %v\n", err)
83101
os.Exit(1)
@@ -86,22 +104,22 @@ func snapshotCmd() {
86104
}
87105

88106
func updateCmd() {
89-
// Step 1: Create writable snapshot for update
107+
// Krok 1: Tworzymy writable snapshot do aktualizacji
90108
fmt.Println("Creating update snapshot:", updateSnapshot)
91109
if err := runCommand("btrfs", "subvolume", "snapshot", "/", updateSnapshot); err != nil {
92110
fmt.Fprintf(os.Stderr, "Error creating update snapshot: %v\n", err)
93111
os.Exit(1)
94112
}
95113

96-
// Step 2: Mount the snapshot
114+
// Krok 2: Montujemy snapshot
97115
os.MkdirAll(mountPoint, 0755)
98116
if err := runCommand("mount", btrfsDevice, mountPoint, "-o", "subvol="+updateSnapshot); err != nil {
99117
fmt.Fprintf(os.Stderr, "Error mounting update snapshot: %v\n", err)
100118
os.Exit(1)
101119
}
102-
defer runCommand("umount", mountPoint) // Clean up on exit
120+
defer runCommand("umount", mountPoint)
103121

104-
// Bind mount necessary filesystems
122+
// Bind mount niezbędnych systemów plików
105123
bindMounts := []string{"/proc", "/sys", "/dev", "/run"}
106124
for _, m := range bindMounts {
107125
target := filepath.Join(mountPoint, m[1:])
@@ -113,115 +131,113 @@ func updateCmd() {
113131
defer runCommand("umount", target)
114132
}
115133

116-
// Step 3: Chroot and perform update
134+
// Krok 3: Chroot + aktualizacja
117135
fmt.Println("Performing system update in chroot...")
118-
chrootCmd := []string{"chroot", mountPoint, "apt", "update"}
119-
if err := runCommand(chrootCmd[0], chrootCmd[1:]...); err != nil {
136+
if err := runCommand("chroot", mountPoint, "apt", "update"); err != nil {
120137
fmt.Fprintf(os.Stderr, "Error running apt update: %v\n", err)
121138
os.Exit(1)
122139
}
123-
chrootCmd = []string{"chroot", mountPoint, "apt", "upgrade", "-y"}
124-
if err := runCommand(chrootCmd[0], chrootCmd[1:]...); err != nil {
140+
if err := runCommand("chroot", mountPoint, "apt", "upgrade", "-y"); err != nil {
125141
fmt.Fprintf(os.Stderr, "Error running apt upgrade: %v\n", err)
126142
os.Exit(1)
127143
}
128144

129-
fmt.Println("Update completed in snapshot.")
145+
fmt.Println("Update completed successfully in snapshot:", updateSnapshot)
146+
fmt.Println("Run 'hroot switch' and reboot to apply.")
130147
}
131148

132149
func switchCmd() {
133-
// Get ID of update snapshot
134-
out, err := exec.Command("btrfs", "subvolume", "find-new", "/", "9999999").CombinedOutput() // Hack to get current ID, but better to list
150+
id, err := getSubvolumeID(updateSnapshot)
135151
if err != nil {
136-
fmt.Fprintf(os.Stderr, "Error finding subvolume ID: %v\n", err)
152+
fmt.Fprintf(os.Stderr, "Cannot get subvolume ID for %s: %v\n", updateSnapshot, err)
137153
os.Exit(1)
138154
}
139-
// Parse ID from output - this is simplistic, assume we know path
140-
fmt.Println("Switching default subvolume to", updateSnapshot)
141-
if err := runCommand("btrfs", "subvolume", "set-default", updateSnapshot); err != nil {
155+
156+
fmt.Printf("Setting default subvolume to %s (ID: %s)\n", updateSnapshot, id)
157+
if err := runCommand("btrfs", "subvolume", "set-default", id, "/"); err != nil {
142158
fmt.Fprintf(os.Stderr, "Error setting default subvolume: %v\n", err)
143159
os.Exit(1)
144160
}
145-
fmt.Println("Default subvolume switched. Reboot to apply.")
161+
fmt.Println("Default subvolume changed. Reboot required.")
146162
}
147163

148164
func rollbackCmd(args []string) {
149165
if len(args) == 0 {
150-
fmt.Fprintf(os.Stderr, "Rollback requires snapshot name\n")
166+
fmt.Fprintf(os.Stderr, "Usage: hroot rollback <snapshot-name>\n")
151167
os.Exit(1)
152168
}
169+
153170
snapshotName := args[0]
154-
fmt.Printf("Rolling back to %s\n", snapshotName)
155-
if err := runCommand("btrfs", "subvolume", "set-default", snapshotName); err != nil {
171+
id, err := getSubvolumeID(snapshotName)
172+
if err != nil {
173+
fmt.Fprintf(os.Stderr, "Cannot get subvolume ID for %s: %v\n", snapshotName, err)
174+
os.Exit(1)
175+
}
176+
177+
fmt.Printf("Rolling back to %s (ID: %s)\n", snapshotName, id)
178+
if err := runCommand("btrfs", "subvolume", "set-default", id, "/"); err != nil {
156179
fmt.Fprintf(os.Stderr, "Error setting default subvolume: %v\n", err)
157180
os.Exit(1)
158181
}
159-
fmt.Println("Rollback set. Reboot to apply.")
182+
fmt.Println("Rollback successful. Reboot required.")
160183
}
161184

162-
func installCmd(packages []string) {
163-
if len(packages) == 0 {
164-
fmt.Fprintf(os.Stderr, "Install requires package names\n")
185+
func installCmd(pkgs []string) {
186+
if len(pkgs) == 0 {
187+
fmt.Fprintf(os.Stderr, "Usage: hroot install <package>...\n")
165188
os.Exit(1)
166189
}
167-
fmt.Printf("Installing packages: %v\n", packages)
168-
args := append([]string{"install", "-y"}, packages...)
190+
fmt.Printf("Installing packages (live system): %v\n", pkgs)
191+
args := append([]string{"install", "-y"}, pkgs...)
169192
if err := runCommand("apt", args...); err != nil {
170193
fmt.Fprintf(os.Stderr, "Error installing packages: %v\n", err)
171194
os.Exit(1)
172195
}
173196
}
174197

175-
func removeCmd(packages []string) {
176-
if len(packages) == 0 {
177-
fmt.Fprintf(os.Stderr, "Remove requires package names\n")
198+
func removeCmd(pkgs []string) {
199+
if len(pkgs) == 0 {
200+
fmt.Fprintf(os.Stderr, "Usage: hroot remove <package>...\n")
178201
os.Exit(1)
179202
}
180-
fmt.Printf("Removing packages: %v\n", packages)
181-
args := append([]string{"remove", "-y"}, packages...)
203+
fmt.Printf("Removing packages (live system): %v\n", pkgs)
204+
args := append([]string{"remove", "-y"}, pkgs...)
182205
if err := runCommand("apt", args...); err != nil {
183206
fmt.Fprintf(os.Stderr, "Error removing packages: %v\n", err)
184207
os.Exit(1)
185208
}
186209
}
187210

188211
func cleanCmd() {
189-
fmt.Println("Cleaning temporary files...")
212+
fmt.Println("Cleaning apt cache...")
190213
if err := runCommand("apt", "clean"); err != nil {
191214
fmt.Fprintf(os.Stderr, "Error cleaning apt cache: %v\n", err)
192215
}
193-
// Optionally delete old snapshots - manual for now
194-
fmt.Println("List snapshots with 'hroot status' and delete manually if needed.")
216+
fmt.Println("Done. Delete old snapshots manually with 'btrfs subvolume delete /<name>'")
195217
}
196218

197219
func statusCmd() {
198-
out, err := exec.Command("btrfs", "subvolume", "list", "-p", "/").CombinedOutput()
220+
output, err := exec.Command("btrfs", "subvolume", "list", "-p", "/").CombinedOutput()
199221
if err != nil {
200222
fmt.Fprintf(os.Stderr, "Error listing subvolumes: %v\n", err)
201223
os.Exit(1)
202224
}
203-
lines := strings.Split(string(out), "\n")
225+
204226
fmt.Println("Available snapshots:")
227+
lines := strings.Split(string(output), "\n")
205228
for _, line := range lines {
206-
if strings.Contains(line, rootSubvolume) {
229+
if strings.Contains(line, rootSubvolume) || strings.Contains(line, updateSnapshot) {
207230
fields := strings.Fields(line)
208-
if len(fields) > 8 {
231+
if len(fields) >= 9 {
209232
id := fields[1]
210-
path := fields[8]
211-
if strings.HasPrefix(path, rootSubvolume) {
212-
fmt.Printf("ID: %s, Path: %s\n", id, path)
213-
}
233+
path := fields[len(fields)-1]
234+
fmt.Printf(" ID %-6s → %s\n", id, path)
214235
}
215236
}
216237
}
217238

218-
// Get default
219-
defOut, defErr := exec.Command("btrfs", "subvolume", "get-default", "/").CombinedOutput()
220-
if defErr == nil {
221-
defFields := strings.Fields(string(defOut))
222-
if len(defFields) > 1 {
223-
defID := defFields[1]
224-
fmt.Printf("Current default ID: %s\n", defID)
225-
}
239+
defOutput, err := exec.Command("btrfs", "subvolume", "get-default", "/").CombinedOutput()
240+
if err == nil {
241+
fmt.Printf("\nCurrent default: %s", defOutput)
226242
}
227243
}

0 commit comments

Comments
 (0)