Skip to content

Commit 211b7d9

Browse files
committed
Add unexport and export.
1 parent 2099190 commit 211b7d9

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

src/cmd/export.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
exportBin string
16+
exportApp string
17+
exportContainer string
18+
)
19+
20+
var exportCmd = &cobra.Command{
21+
Use: "export",
22+
Short: "Export binaries or applications from a toolbox container",
23+
RunE: runExport,
24+
}
25+
26+
func init() {
27+
exportCmd.Flags().StringVar(&exportBin, "bin", "", "Path or name of binary to export")
28+
exportCmd.Flags().StringVar(&exportApp, "app", "", "Path or name of application to export")
29+
exportCmd.Flags().StringVar(&exportContainer, "container", "", "Name of the toolbox container")
30+
rootCmd.AddCommand(exportCmd)
31+
}
32+
33+
func runExport(cmd *cobra.Command, args []string) error {
34+
if exportBin == "" && exportApp == "" {
35+
return errors.New("must specify either --bin or --app")
36+
}
37+
if exportContainer == "" {
38+
return errors.New("must specify --container")
39+
}
40+
41+
if exportBin != "" {
42+
return exportBinary(exportBin, exportContainer)
43+
} else if exportApp != "" {
44+
return exportApplication(exportApp, exportContainer)
45+
}
46+
return nil
47+
}
48+
49+
func exportBinary(binName, containerName string) error {
50+
// Find the binary's full path inside the container
51+
checkCmd := fmt.Sprintf("toolbox run -c %s which %s", containerName, binName)
52+
out, err := exec.Command("sh", "-c", checkCmd).Output()
53+
if err != nil || strings.TrimSpace(string(out)) == "" {
54+
return fmt.Errorf("binary %s not found in container %s", binName, containerName)
55+
}
56+
binPath := strings.TrimSpace(string(out))
57+
58+
homeDir, err := os.UserHomeDir()
59+
if err != nil {
60+
return err
61+
}
62+
exportedBinPath := filepath.Join(homeDir, ".local", "bin", binName)
63+
64+
script := fmt.Sprintf(`#!/bin/sh
65+
# toolbox_binary
66+
# name: %s
67+
exec toolbox run -c %s %s "$@"
68+
`, containerName, containerName, binPath)
69+
70+
if err := os.WriteFile(exportedBinPath, []byte(script), 0755); err != nil {
71+
return fmt.Errorf("failed to create wrapper: %v", err)
72+
}
73+
74+
fmt.Printf("Successfully exported %s from container %s to %s\n", binName, containerName, exportedBinPath)
75+
return nil
76+
}
77+
78+
func exportApplication(appName, containerName string) error {
79+
// Find the desktop file inside the container
80+
findCmd := fmt.Sprintf("toolbox run -c %s sh -c 'find /usr/share/applications -name \"*%s*.desktop\" | head -1'", containerName, appName)
81+
out, err := exec.Command("sh", "-c", findCmd).Output()
82+
if err != nil || strings.TrimSpace(string(out)) == "" {
83+
return fmt.Errorf("application %s not found in container %s", appName, containerName)
84+
}
85+
desktopFile := strings.TrimSpace(string(out))
86+
87+
// Read the desktop file content
88+
catCmd := fmt.Sprintf("toolbox run -c %s cat %s", containerName, desktopFile)
89+
content, err := exec.Command("sh", "-c", catCmd).Output()
90+
if err != nil {
91+
return fmt.Errorf("failed to read desktop file: %v", err)
92+
}
93+
lines := strings.Split(string(content), "\n")
94+
var newLines []string
95+
hasNameTranslations := false
96+
97+
for _, line := range lines {
98+
if strings.HasPrefix(line, "Exec=") {
99+
execCmd := line[5:]
100+
line = fmt.Sprintf("Exec=toolbox run -c %s %s", containerName, execCmd)
101+
} else if strings.HasPrefix(line, "Name=") {
102+
line = fmt.Sprintf("Name=%s (on %s)", line[5:], containerName)
103+
} else if strings.HasPrefix(line, "Name[") {
104+
hasNameTranslations = true
105+
} else if strings.HasPrefix(line, "GenericName=") {
106+
line = fmt.Sprintf("GenericName=%s (on %s)", line[12:], containerName)
107+
} else if strings.HasPrefix(line, "TryExec=") || line == "DBusActivatable=true" {
108+
continue
109+
}
110+
newLines = append(newLines, line)
111+
}
112+
113+
if hasNameTranslations {
114+
for i, line := range newLines {
115+
if strings.HasPrefix(line, "Name[") {
116+
lang := line[5:strings.Index(line, "]")]
117+
value := line[strings.Index(line, "=")+1:]
118+
newLines[i] = fmt.Sprintf("Name[%s]=%s (on %s)", lang, value, containerName)
119+
}
120+
}
121+
}
122+
123+
homeDir, err := os.UserHomeDir()
124+
if err != nil {
125+
return err
126+
}
127+
appsPath := filepath.Join(homeDir, ".local", "share", "applications")
128+
exportedPath := filepath.Join(appsPath, filepath.Base(desktopFile))
129+
exportedPath = strings.TrimSuffix(exportedPath, ".desktop") + "-" + containerName + ".desktop"
130+
131+
if err := os.MkdirAll(appsPath, 0755); err != nil {
132+
return fmt.Errorf("failed to create applications directory: %v", err)
133+
}
134+
if err := os.WriteFile(exportedPath, []byte(strings.Join(newLines, "\n")), 0644); err != nil {
135+
return fmt.Errorf("failed to create desktop file: %v", err)
136+
}
137+
138+
// Update desktop database
139+
exec.Command("update-desktop-database", appsPath).Run()
140+
141+
fmt.Printf("Successfully exported %s from container %s to %s\n", appName, containerName, exportedPath)
142+
return nil
143+
}

src/cmd/unexport.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var (
14+
unexportContainer string
15+
unexportBin string
16+
unexportApp string
17+
unexportAll bool
18+
)
19+
20+
var unexportCmd = &cobra.Command{
21+
Use: "unexport",
22+
Short: "Remove exported binaries and applications for a specific toolbox container",
23+
RunE: runUnexport,
24+
}
25+
26+
func init() {
27+
unexportCmd.Flags().StringVar(&unexportContainer, "container", "", "Name of the toolbox container")
28+
unexportCmd.Flags().StringVar(&unexportBin, "bin", "", "Name of the exported binary to remove")
29+
unexportCmd.Flags().StringVar(&unexportApp, "app", "", "Name of the exported application to remove")
30+
unexportCmd.Flags().BoolVar(&unexportAll, "all", false, "Remove all exported binaries and applications for the container")
31+
rootCmd.AddCommand(unexportCmd)
32+
}
33+
34+
func runUnexport(cmd *cobra.Command, args []string) error {
35+
if unexportContainer == "" {
36+
return errors.New("must specify --container")
37+
}
38+
39+
if !unexportAll && unexportBin == "" && unexportApp == "" {
40+
return errors.New("must specify --bin, --app, or --all")
41+
}
42+
43+
homeDir, err := os.UserHomeDir()
44+
if err != nil {
45+
return err
46+
}
47+
binDir := filepath.Join(homeDir, ".local", "bin")
48+
appsDir := filepath.Join(homeDir, ".local", "share", "applications")
49+
50+
removedBins := []string{}
51+
removedApps := []string{}
52+
53+
if unexportBin != "" {
54+
path := filepath.Join(binDir, unexportBin)
55+
if fileContainsContainer(path, unexportContainer) {
56+
if err := os.Remove(path); err == nil {
57+
removedBins = append(removedBins, path)
58+
}
59+
}
60+
}
61+
62+
if unexportApp != "" {
63+
// Remove .desktop file that matches app name and container
64+
matches, _ := filepath.Glob(filepath.Join(appsDir, fmt.Sprintf("*%s-%s.desktop", unexportApp, unexportContainer)))
65+
for _, path := range matches {
66+
if err := os.Remove(path); err == nil {
67+
removedApps = append(removedApps, path)
68+
}
69+
}
70+
}
71+
72+
if unexportAll {
73+
// Remove all binaries for this container in .local/bin
74+
binFiles, _ := os.ReadDir(binDir)
75+
for _, f := range binFiles {
76+
if f.IsDir() {
77+
continue
78+
}
79+
path := filepath.Join(binDir, f.Name())
80+
if fileContainsContainer(path, unexportContainer) {
81+
if err := os.Remove(path); err == nil {
82+
removedBins = append(removedBins, path)
83+
}
84+
}
85+
}
86+
87+
// Remove all .desktop files for this container in .local/share/applications
88+
appFiles, _ := os.ReadDir(appsDir)
89+
for _, f := range appFiles {
90+
name := f.Name()
91+
if strings.HasSuffix(name, "-"+unexportContainer+".desktop") {
92+
path := filepath.Join(appsDir, name)
93+
if err := os.Remove(path); err == nil {
94+
removedApps = append(removedApps, path)
95+
}
96+
}
97+
}
98+
}
99+
100+
fmt.Printf("Removed binaries:\n")
101+
for _, b := range removedBins {
102+
fmt.Printf(" %s\n", b)
103+
}
104+
fmt.Printf("Removed desktop files:\n")
105+
for _, a := range removedApps {
106+
fmt.Printf(" %s\n", a)
107+
}
108+
if len(removedBins) == 0 && len(removedApps) == 0 {
109+
fmt.Println("No exported binaries or desktop files found to remove for container", unexportContainer)
110+
}
111+
return nil
112+
}
113+
114+
// fileContainsContainer returns true if the file exists and has a toolbox_binary comment with name: <container>
115+
func fileContainsContainer(path, container string) bool {
116+
content, err := os.ReadFile(path)
117+
if err != nil {
118+
return false
119+
}
120+
return strings.Contains(string(content), "# toolbox_binary") && strings.Contains(string(content), fmt.Sprintf("name: %s", container))
121+
}

0 commit comments

Comments
 (0)