Add unexport and export.

Signed-off-by: Hadi Chokr <hadichokr@icloud.com>
This commit is contained in:
Hadi Chokr 2025-07-12 17:24:44 +02:00
parent e3ce0bc457
commit 1dad3c86fe
2 changed files with 264 additions and 0 deletions

143
src/cmd/export.go Normal file
View File

@ -0,0 +1,143 @@
package cmd
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var (
exportBin string
exportApp string
exportContainer string
)
var exportCmd = &cobra.Command{
Use: "export",
Short: "Export binaries or applications from a toolbox container",
RunE: runExport,
}
func init() {
exportCmd.Flags().StringVar(&exportBin, "bin", "", "Path or name of binary to export")
exportCmd.Flags().StringVar(&exportApp, "app", "", "Path or name of application to export")
exportCmd.Flags().StringVar(&exportContainer, "container", "", "Name of the toolbox container")
rootCmd.AddCommand(exportCmd)
}
func runExport(cmd *cobra.Command, args []string) error {
if exportBin == "" && exportApp == "" {
return errors.New("must specify either --bin or --app")
}
if exportContainer == "" {
return errors.New("must specify --container")
}
if exportBin != "" {
return exportBinary(exportBin, exportContainer)
} else if exportApp != "" {
return exportApplication(exportApp, exportContainer)
}
return nil
}
func exportBinary(binName, containerName string) error {
// Find the binary's full path inside the container
checkCmd := fmt.Sprintf("toolbox run -c %s which %s", containerName, binName)
out, err := exec.Command("sh", "-c", checkCmd).Output()
if err != nil || strings.TrimSpace(string(out)) == "" {
return fmt.Errorf("binary %s not found in container %s", binName, containerName)
}
binPath := strings.TrimSpace(string(out))
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
exportedBinPath := filepath.Join(homeDir, ".local", "bin", binName)
script := fmt.Sprintf(`#!/bin/sh
# toolbox_binary
# name: %s
exec toolbox run -c %s %s "$@"
`, containerName, containerName, binPath)
if err := os.WriteFile(exportedBinPath, []byte(script), 0755); err != nil {
return fmt.Errorf("failed to create wrapper: %v", err)
}
fmt.Printf("Successfully exported %s from container %s to %s\n", binName, containerName, exportedBinPath)
return nil
}
func exportApplication(appName, containerName string) error {
// Find the desktop file inside the container
findCmd := fmt.Sprintf("toolbox run -c %s sh -c 'find /usr/share/applications -name \"*%s*.desktop\" | head -1'", containerName, appName)
out, err := exec.Command("sh", "-c", findCmd).Output()
if err != nil || strings.TrimSpace(string(out)) == "" {
return fmt.Errorf("application %s not found in container %s", appName, containerName)
}
desktopFile := strings.TrimSpace(string(out))
// Read the desktop file content
catCmd := fmt.Sprintf("toolbox run -c %s cat %s", containerName, desktopFile)
content, err := exec.Command("sh", "-c", catCmd).Output()
if err != nil {
return fmt.Errorf("failed to read desktop file: %v", err)
}
lines := strings.Split(string(content), "\n")
var newLines []string
hasNameTranslations := false
for _, line := range lines {
if strings.HasPrefix(line, "Exec=") {
execCmd := line[5:]
line = fmt.Sprintf("Exec=toolbox run -c %s %s", containerName, execCmd)
} else if strings.HasPrefix(line, "Name=") {
line = fmt.Sprintf("Name=%s (on %s)", line[5:], containerName)
} else if strings.HasPrefix(line, "Name[") {
hasNameTranslations = true
} else if strings.HasPrefix(line, "GenericName=") {
line = fmt.Sprintf("GenericName=%s (on %s)", line[12:], containerName)
} else if strings.HasPrefix(line, "TryExec=") || line == "DBusActivatable=true" {
continue
}
newLines = append(newLines, line)
}
if hasNameTranslations {
for i, line := range newLines {
if strings.HasPrefix(line, "Name[") {
lang := line[5:strings.Index(line, "]")]
value := line[strings.Index(line, "=")+1:]
newLines[i] = fmt.Sprintf("Name[%s]=%s (on %s)", lang, value, containerName)
}
}
}
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
appsPath := filepath.Join(homeDir, ".local", "share", "applications")
exportedPath := filepath.Join(appsPath, filepath.Base(desktopFile))
exportedPath = strings.TrimSuffix(exportedPath, ".desktop") + "-" + containerName + ".desktop"
if err := os.MkdirAll(appsPath, 0755); err != nil {
return fmt.Errorf("failed to create applications directory: %v", err)
}
if err := os.WriteFile(exportedPath, []byte(strings.Join(newLines, "\n")), 0644); err != nil {
return fmt.Errorf("failed to create desktop file: %v", err)
}
// Update desktop database
exec.Command("update-desktop-database", appsPath).Run()
fmt.Printf("Successfully exported %s from container %s to %s\n", appName, containerName, exportedPath)
return nil
}

121
src/cmd/unexport.go Normal file
View File

@ -0,0 +1,121 @@
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var (
unexportContainer string
unexportBin string
unexportApp string
unexportAll bool
)
var unexportCmd = &cobra.Command{
Use: "unexport",
Short: "Remove exported binaries and applications for a specific toolbox container",
RunE: runUnexport,
}
func init() {
unexportCmd.Flags().StringVar(&unexportContainer, "container", "", "Name of the toolbox container")
unexportCmd.Flags().StringVar(&unexportBin, "bin", "", "Name of the exported binary to remove")
unexportCmd.Flags().StringVar(&unexportApp, "app", "", "Name of the exported application to remove")
unexportCmd.Flags().BoolVar(&unexportAll, "all", false, "Remove all exported binaries and applications for the container")
rootCmd.AddCommand(unexportCmd)
}
func runUnexport(cmd *cobra.Command, args []string) error {
if unexportContainer == "" {
return errors.New("must specify --container")
}
if !unexportAll && unexportBin == "" && unexportApp == "" {
return errors.New("must specify --bin, --app, or --all")
}
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
binDir := filepath.Join(homeDir, ".local", "bin")
appsDir := filepath.Join(homeDir, ".local", "share", "applications")
removedBins := []string{}
removedApps := []string{}
if unexportBin != "" {
path := filepath.Join(binDir, unexportBin)
if fileContainsContainer(path, unexportContainer) {
if err := os.Remove(path); err == nil {
removedBins = append(removedBins, path)
}
}
}
if unexportApp != "" {
// Remove .desktop file that matches app name and container
matches, _ := filepath.Glob(filepath.Join(appsDir, fmt.Sprintf("*%s-%s.desktop", unexportApp, unexportContainer)))
for _, path := range matches {
if err := os.Remove(path); err == nil {
removedApps = append(removedApps, path)
}
}
}
if unexportAll {
// Remove all binaries for this container in .local/bin
binFiles, _ := os.ReadDir(binDir)
for _, f := range binFiles {
if f.IsDir() {
continue
}
path := filepath.Join(binDir, f.Name())
if fileContainsContainer(path, unexportContainer) {
if err := os.Remove(path); err == nil {
removedBins = append(removedBins, path)
}
}
}
// Remove all .desktop files for this container in .local/share/applications
appFiles, _ := os.ReadDir(appsDir)
for _, f := range appFiles {
name := f.Name()
if strings.HasSuffix(name, "-"+unexportContainer+".desktop") {
path := filepath.Join(appsDir, name)
if err := os.Remove(path); err == nil {
removedApps = append(removedApps, path)
}
}
}
}
fmt.Printf("Removed binaries:\n")
for _, b := range removedBins {
fmt.Printf(" %s\n", b)
}
fmt.Printf("Removed desktop files:\n")
for _, a := range removedApps {
fmt.Printf(" %s\n", a)
}
if len(removedBins) == 0 && len(removedApps) == 0 {
fmt.Println("No exported binaries or desktop files found to remove for container", unexportContainer)
}
return nil
}
// fileContainsContainer returns true if the file exists and has a toolbox_binary comment with name: <container>
func fileContainsContainer(path, container string) bool {
content, err := os.ReadFile(path)
if err != nil {
return false
}
return strings.Contains(string(content), "# toolbox_binary") && strings.Contains(string(content), fmt.Sprintf("name: %s", container))
}