1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import (
	"flag"
	"fmt"
	"os"
)

// =============================================================================
// hosty start
// =============================================================================

func cmdStart(args []string) error {
	fs := flag.NewFlagSet("start", flag.ExitOnError)
	fs.Usage = func() {
		fmt.Print(`Usage: hosty start [-file FILE.hosty] [-name NAME] [-port PORT] [-state-dir DIR] [-ignore-version-check] [-system]

Install and start a hosty service.
Without -system: runs as a --user systemd unit (unsandboxed, quick local dev).
With -system:     attaches as a root portable service (sandboxed via portablectl,
                  state under /var/lib/hosty). Requires root.
-file is optional if a data.hosty already exists for the given -name.
If both -file and -name are given, -name overrides the name in the file.
If -port is omitted, a free port is chosen automatically.
If -file has a lower version than the installed one, start refuses unless -ignore-version-check is set.

Options:
`)
		fs.PrintDefaults()
	}
	file := fs.String("file", "", "Path to .hosty file (optional if -name app is already installed)")
	name := fs.String("name", "", "App name (overrides name in -file)")
	port := fs.String("port", "", "Port to pass to the service via HOSTY_PORT (default: free port)")
	stateDir := fs.String("state-dir", "", "State directory (default: $XDG_STATE_HOME/hosty, or /var/lib/hosty with -system)")
	ignoreVersionCheck := fs.Bool("ignore-version-check", false, "Allow installing an older version over a newer one")
	system := fs.Bool("system", false, "Attach as a root portable service instead of a --user unit")

	if err := fs.Parse(args); err != nil {
		return err
	}

	sd, err := resolveStateDir(*stateDir, *system)
	if err != nil {
		return err
	}

	rs, err := setup(setupConfig{
		hostyFile:          *file,
		name:               *name,
		port:               *port,
		stateDir:           sd,
		ignoreVersionCheck: *ignoreVersionCheck,
		system:             *system,
	})
	if err != nil {
		fs.Usage()
		return err
	}

	if rs.system {
		// portablectl attach --enable already installed + enabled the units.
		// Restart the app; BindsTo pulls in the FUSE unit first (ordered by After).
		if err := runCmd("systemctl", "restart", rs.name+".service"); err != nil {
			return fmt.Errorf("starting service: %w", err)
		}
	} else {
		// Enable and start the fs companion service first, then the app service.
		if err := runCmd("systemctl", "--user", "enable", rs.name+"-fs.service"); err != nil {
			return fmt.Errorf("enabling fs service: %w", err)
		}
		if err := runCmd("systemctl", "--user", "restart", rs.name+"-fs.service"); err != nil {
			return fmt.Errorf("starting fs service: %w", err)
		}
		// Enable and restart so any updated ExecStart takes effect.
		if err := runCmd("systemctl", "--user", "enable", rs.name+".service"); err != nil {
			return fmt.Errorf("enabling service: %w", err)
		}
		if err := runCmd("systemctl", "--user", "restart", rs.name+".service"); err != nil {
			return fmt.Errorf("starting service: %w", err)
		}
	}
	fmt.Fprintf(os.Stderr, "hosty: %s started on %s\n", rs.name, termLink("http://localhost:"+rs.port, "http://localhost:"+rs.port))
	return nil
}