Compare commits
4 Commits
v0.4.4-dem
...
v0.4.7-dem
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ac08e5a85 | |||
| 7c74431fcf | |||
| 01f1c17c7f | |||
| 448fba8770 |
@@ -92,6 +92,12 @@ func run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if running := behavisionRunning(); running != "" {
|
||||||
|
return fmt.Errorf("%s is running. Quit Behavision from the tray icon first, then run setup again.\n\n"+
|
||||||
|
"Setting up underneath a running copy starts a second engine on the same port and, in a demo,\n"+
|
||||||
|
"re-claims the shop while the open app still holds the old credentials.", running)
|
||||||
|
}
|
||||||
|
|
||||||
py, ver, err := findPython()
|
py, ver, err := findPython()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -136,6 +142,16 @@ func run() error {
|
|||||||
// Proving it starts is the point. An installer that reports success and
|
// Proving it starts is the point. An installer that reports success and
|
||||||
// leaves a shop with an engine that will not run has done worse than
|
// leaves a shop with an engine that will not run has done worse than
|
||||||
// failing: the failure surfaces later, to someone who did not install it.
|
// failing: the failure surfaces later, to someone who did not install it.
|
||||||
|
// Joining a shop: head office supplies the cameras, so any left on this PC
|
||||||
|
// from an earlier install go first. Otherwise the reconciler offers them UP
|
||||||
|
// to head office - without their passwords, which the engine never returns
|
||||||
|
// - and the shop ends up with the same lens listed twice, one copy of which
|
||||||
|
// can never be pushed to another PC. Measured on the first claimed demo.
|
||||||
|
if bundle != nil && bundle.EnrolCode != "" {
|
||||||
|
if err := os.Remove(paths.CamerasFile()); err == nil {
|
||||||
|
step("Earlier cameras", "removed - head office supplies them now")
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := smokeTest(vpy, demoCams); err != nil {
|
if err := smokeTest(vpy, demoCams); err != nil {
|
||||||
return fmt.Errorf("the engine installed but would not start: %w", err)
|
return fmt.Errorf("the engine installed but would not start: %w", err)
|
||||||
}
|
}
|
||||||
@@ -200,6 +216,22 @@ func engineSource() (string, error) {
|
|||||||
// `py -3` first on Windows: the launcher is what the official installer puts
|
// `py -3` first on Windows: the launcher is what the official installer puts
|
||||||
// on PATH, and `python` there is often the Microsoft Store stub that prints an
|
// on PATH, and `python` there is often the Microsoft Store stub that prints an
|
||||||
// advert and exits 9009 instead of running anything.
|
// advert and exits 9009 instead of running anything.
|
||||||
|
// behavisionRunning names a Behavision process if one is up. Windows only -
|
||||||
|
// that is the platform setup ships on - and by image name via tasklist, which
|
||||||
|
// needs no extra privilege.
|
||||||
|
func behavisionRunning() string {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, name := range []string{"Behavision.exe", "behavision-agent.exe"} {
|
||||||
|
out, err := exec.Command("tasklist", "/FI", "IMAGENAME eq "+name, "/NH").Output()
|
||||||
|
if err == nil && strings.Contains(strings.ToLower(string(out)), strings.ToLower(name)) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func findPython() (string, string, error) {
|
func findPython() (string, string, error) {
|
||||||
type cand struct {
|
type cand struct {
|
||||||
exe string
|
exe string
|
||||||
@@ -523,6 +555,7 @@ func claimShop(code, base string) (string, error) {
|
|||||||
cfg.AgentToken = b.AgentToken
|
cfg.AgentToken = b.AgentToken
|
||||||
cfg.CloudBase = base
|
cfg.CloudBase = base
|
||||||
cfg.Standalone = false
|
cfg.Standalone = false
|
||||||
|
cfg.SessionToken, cfg.SessionRefresh, cfg.SessionEmail = "", "", ""
|
||||||
caPath, err := enrol.SaveCA(b.CACert, paths.BrokerCA())
|
caPath, err := enrol.SaveCA(b.CACert, paths.BrokerCA())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ func cmdClaim(args []string) error {
|
|||||||
cfg.BrokerPassword = b.MQTTPass
|
cfg.BrokerPassword = b.MQTTPass
|
||||||
cfg.AgentToken = b.AgentToken
|
cfg.AgentToken = b.AgentToken
|
||||||
cfg.CloudBase = base
|
cfg.CloudBase = base
|
||||||
|
cfg.SessionToken, cfg.SessionRefresh, cfg.SessionEmail = "", "", ""
|
||||||
caPath, err := enrol.SaveCA(b.CACert, paths.BrokerCA())
|
caPath, err := enrol.SaveCA(b.CACert, paths.BrokerCA())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -218,13 +219,26 @@ func (s *Supervisor) runOnce(ctx context.Context) error {
|
|||||||
kill = k
|
kill = k
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
|
// The last few lines the engine printed travel with the failure, because
|
||||||
|
// "engine exited: exit status 1" sends somebody to a log file on a shop
|
||||||
|
// PC, and the one line that matters - "port 8010 is already in use" - was
|
||||||
|
// right there.
|
||||||
|
var tailMu sync.Mutex
|
||||||
|
var tail []string
|
||||||
pumped := make(chan struct{})
|
pumped := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(pumped)
|
defer close(pumped)
|
||||||
sc := bufio.NewScanner(stdout)
|
sc := bufio.NewScanner(stdout)
|
||||||
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||||
for sc.Scan() {
|
for sc.Scan() {
|
||||||
fmt.Fprintln(s.opts.LogWriter, sc.Text())
|
line := sc.Text()
|
||||||
|
fmt.Fprintln(s.opts.LogWriter, line)
|
||||||
|
tailMu.Lock()
|
||||||
|
tail = append(tail, line)
|
||||||
|
if len(tail) > 12 {
|
||||||
|
tail = tail[1:]
|
||||||
|
}
|
||||||
|
tailMu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -238,6 +252,12 @@ func (s *Supervisor) runOnce(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if waitErr != nil {
|
if waitErr != nil {
|
||||||
|
tailMu.Lock()
|
||||||
|
reason := explain(tail)
|
||||||
|
tailMu.Unlock()
|
||||||
|
if reason != "" {
|
||||||
|
return fmt.Errorf("%s (%v)", reason, waitErr)
|
||||||
|
}
|
||||||
return fmt.Errorf("engine exited: %w", waitErr)
|
return fmt.Errorf("engine exited: %w", waitErr)
|
||||||
}
|
}
|
||||||
return errors.New("engine exited unexpectedly with status 0")
|
return errors.New("engine exited unexpectedly with status 0")
|
||||||
@@ -373,3 +393,28 @@ func LogFile(path string) (*os.File, error) {
|
|||||||
}
|
}
|
||||||
return os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
return os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// explain turns the engine's last output into the sentence the tray shows.
|
||||||
|
// The cases are the ones seen on real installs; anything else shows the last
|
||||||
|
// non-empty line verbatim.
|
||||||
|
func explain(tail []string) string {
|
||||||
|
last := ""
|
||||||
|
for _, l := range tail {
|
||||||
|
low := strings.ToLower(l)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(low, "address already in use") || strings.Contains(low, "only one usage of each socket address"):
|
||||||
|
return "port 8010 is already in use - another Behavision or its engine is still running"
|
||||||
|
case strings.Contains(low, "no module named behavision"):
|
||||||
|
return "the engine is not installed in this Python - run behavision-setup again"
|
||||||
|
case strings.Contains(low, "modulenotfounderror") || strings.Contains(low, "importerror"):
|
||||||
|
return "the engine is missing a library - run behavision-setup again"
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(l) != "" {
|
||||||
|
last = strings.TrimSpace(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(last) > 120 {
|
||||||
|
last = last[:120] + "…"
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ func APICredentials() string {
|
|||||||
return filepath.Join(StateRoot(), "data", "api_credentials.txt")
|
return filepath.Join(StateRoot(), "data", "api_credentials.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CamerasFile is the engine's own camera store. The agent never edits it -
|
||||||
|
// cameras go through the engine's API so passwords are sealed - but setup
|
||||||
|
// removes it when a PC joins a shop, because head office is the source of
|
||||||
|
// truth from then on.
|
||||||
|
func CamerasFile() string { return filepath.Join(StateRoot(), "data", "cameras.json") }
|
||||||
|
|
||||||
// EnsureState creates the writable tree. Called before anything opens a file
|
// EnsureState creates the writable tree. Called before anything opens a file
|
||||||
// under it, so a first run on a fresh machine does not fail on a missing dir.
|
// under it, so a first run on a fresh machine does not fail on a missing dir.
|
||||||
func EnsureState() error {
|
func EnsureState() error {
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ func (a *App) startup(ctx context.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
a.startPipeline(ctx)
|
a.startPipeline(ctx)
|
||||||
|
go a.watchConfig(ctx)
|
||||||
|
|
||||||
// Recognition starts with the app. Until this, the engine only ever
|
// Recognition starts with the app. Until this, the engine only ever
|
||||||
// started when somebody pressed Start - which meant a till that rebooted
|
// started when somebody pressed Start - which meant a till that rebooted
|
||||||
@@ -435,6 +436,9 @@ func (a *App) Claim(code string) (SessionInfo, error) {
|
|||||||
a.cfg.BrokerPassword = b.MQTTPass
|
a.cfg.BrokerPassword = b.MQTTPass
|
||||||
a.cfg.AgentToken = b.AgentToken
|
a.cfg.AgentToken = b.AgentToken
|
||||||
a.cfg.CloudBase = a.cloud.Base
|
a.cfg.CloudBase = a.cloud.Base
|
||||||
|
// A new head office: whoever was signed in was signed in somewhere else.
|
||||||
|
a.cfg.SessionToken, a.cfg.SessionRefresh, a.cfg.SessionEmail = "", "", ""
|
||||||
|
a.cloud.Clear()
|
||||||
caPath, err := enrol.SaveCA(b.CACert, agentpaths.BrokerCA())
|
caPath, err := enrol.SaveCA(b.CACert, agentpaths.BrokerCA())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SessionInfo{}, err
|
return SessionInfo{}, err
|
||||||
@@ -464,6 +468,62 @@ func (a *App) Claim(code string) (SessionInfo, error) {
|
|||||||
// the current config. Only Claim needs it today; it exists as its own method
|
// the current config. Only Claim needs it today; it exists as its own method
|
||||||
// because "stop everything that reads the config, then start it" is the part
|
// because "stop everything that reads the config, then start it" is the part
|
||||||
// that is easy to get half right.
|
// that is easy to get half right.
|
||||||
|
// watchConfig reloads agent.json when something else writes it.
|
||||||
|
//
|
||||||
|
// behavision-setup re-run on a PC with the app open re-claims the shop and
|
||||||
|
// rotates its API token; the running app kept the old one and every camera
|
||||||
|
// sync was refused from then on - heartbeats still flowed, so head office
|
||||||
|
// looked fine while the cameras went stale. A claim from `behavision-agent
|
||||||
|
// claim` does the same. Rather than ask people to restart the app, the app
|
||||||
|
// watches the file and picks the new credentials up itself.
|
||||||
|
func (a *App) watchConfig(ctx context.Context) {
|
||||||
|
path := agentpaths.AgentConfig()
|
||||||
|
last := mtime(path)
|
||||||
|
t := time.NewTicker(10 * time.Second)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
}
|
||||||
|
now := mtime(path)
|
||||||
|
if now.IsZero() || now.Equal(last) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
last = now
|
||||||
|
fresh, err := agentcfg.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fresh = fresh.WithEngineCredentials(agentpaths.APICredentials())
|
||||||
|
a.mu.Lock()
|
||||||
|
changed := fresh.AgentToken != a.cfg.AgentToken || fresh.SiteID != a.cfg.SiteID ||
|
||||||
|
fresh.BrokerPassword != a.cfg.BrokerPassword || fresh.CloudBase != a.cfg.CloudBase ||
|
||||||
|
fresh.Standalone != a.cfg.Standalone
|
||||||
|
if changed {
|
||||||
|
// Keep this process's live session; a claim clears it in the file
|
||||||
|
// deliberately, and that is honoured too.
|
||||||
|
a.cfg = fresh
|
||||||
|
if fresh.SessionToken == "" {
|
||||||
|
a.cloud.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.mu.Unlock()
|
||||||
|
if changed {
|
||||||
|
a.restartPipeline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mtime(path string) time.Time {
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return st.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) restartPipeline() {
|
func (a *App) restartPipeline() {
|
||||||
if a.stopBridge != nil {
|
if a.stopBridge != nil {
|
||||||
a.stopBridge()
|
a.stopBridge()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
desktop/frontend/dist/index.html
vendored
2
desktop/frontend/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Behavision</title>
|
<title>Behavision</title>
|
||||||
<script type="module" crossorigin src="./assets/index-upywadx9.js"></script>
|
<script type="module" crossorigin src="./assets/index-C-oYbwC6.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-Z_jL3Bie.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-Z_jL3Bie.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -40,10 +40,20 @@ export default function App() {
|
|||||||
const [helping, setHelping] = useState(false)
|
const [helping, setHelping] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
let alive = true
|
||||||
try { setSession(await api.session()) } catch { setSession(null) }
|
const load = async () => {
|
||||||
setBooting(false)
|
try {
|
||||||
})()
|
const s = await api.session()
|
||||||
|
if (alive) setSession(prev => JSON.stringify(prev) === JSON.stringify(s) ? prev : s)
|
||||||
|
} catch { if (alive) setSession(null) }
|
||||||
|
if (alive) setBooting(false)
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
// Re-read every few seconds: a session the server has ended - or one
|
||||||
|
// that never belonged to this head office - must put Login back on
|
||||||
|
// screen, not leave "session expired" banners on every page.
|
||||||
|
const id = setInterval(load, 8000)
|
||||||
|
return () => { alive = false; clearInterval(id) }
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!isDesktop()) {
|
if (!isDesktop()) {
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ export default function Assistant({ session, onClose }) {
|
|||||||
const a = await api.ask(history.map(t => ({ role: t.role, text: t.text })))
|
const a = await api.ask(history.map(t => ({ role: t.role, text: t.text })))
|
||||||
setTurns([...history, { role: 'assistant', text: a.text, used: a.used ?? [] }])
|
setTurns([...history, { role: 'assistant', text: a.text, used: a.used ?? [] }])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(message(e))
|
const m = message(e)
|
||||||
|
// Her only failure that is not hers: the login is gone. Say what to do,
|
||||||
|
// not "session expired" - the shell returns to Login within seconds.
|
||||||
|
setError(/session expired|unauthori[sz]ed/i.test(m)
|
||||||
|
? 'You’re signed out of head office, so I can’t look anything up. Sign in again and ask me once more.'
|
||||||
|
: m)
|
||||||
setTurns(turns) // the question stays in the box, not in the transcript
|
setTurns(turns) // the question stays in the box, not in the transcript
|
||||||
setDraft(q)
|
setDraft(q)
|
||||||
} finally { setBusy(false) }
|
} finally { setBusy(false) }
|
||||||
|
|||||||
@@ -108,8 +108,16 @@ func (c *Client) do(ctx context.Context, method, path string, body, out any) err
|
|||||||
}
|
}
|
||||||
if rerr := c.Refresh(ctx); rerr != nil {
|
if rerr := c.Refresh(ctx); rerr != nil {
|
||||||
// The refresh token is gone too, so this really is a sign-in, not a
|
// The refresh token is gone too, so this really is a sign-in, not a
|
||||||
// transient failure. Report it as such so the UI shows the login sheet
|
// transient failure. Forget the session - in memory AND on disk, through
|
||||||
// rather than an error dialog.
|
// the same callback that persists rotations - so the app goes back to
|
||||||
|
// Login instead of showing "session expired" on every screen until
|
||||||
|
// somebody finds Sign out. Seen on a PC that had been claimed against a
|
||||||
|
// demo head office and then re-claimed against the real one: the old
|
||||||
|
// login sat there, dead, for the whole session.
|
||||||
|
c.Clear()
|
||||||
|
if c.onRefresh != nil {
|
||||||
|
c.onRefresh(Session{})
|
||||||
|
}
|
||||||
return ErrUnauthorized
|
return ErrUnauthorized
|
||||||
}
|
}
|
||||||
return c.send(ctx, method, path, raw, out)
|
return c.send(ctx, method, path, raw, out)
|
||||||
|
|||||||
@@ -101,9 +101,11 @@ func (t *tray) onReady(ctx context.Context) {
|
|||||||
case <-t.mOpen.ClickedCh:
|
case <-t.mOpen.ClickedCh:
|
||||||
runtime.Show(ctx)
|
runtime.Show(ctx)
|
||||||
case <-t.mStart.ClickedCh:
|
case <-t.mStart.ClickedCh:
|
||||||
t.app.StartEngine()
|
// Never on the menu loop itself: a stop waits for the process to
|
||||||
|
// exit, and a menu that is deaf for the duration looks broken.
|
||||||
|
go func() { t.app.StartEngine(); t.refresh() }()
|
||||||
case <-t.mStop.ClickedCh:
|
case <-t.mStop.ClickedCh:
|
||||||
t.app.StopEngine()
|
go func() { t.app.StopEngine(); t.refresh() }()
|
||||||
case <-t.mLogs.ClickedCh:
|
case <-t.mLogs.ClickedCh:
|
||||||
runtime.BrowserOpenURL(ctx, "file://"+logsDir())
|
runtime.BrowserOpenURL(ctx, "file://"+logsDir())
|
||||||
case <-t.mQuit.ClickedCh:
|
case <-t.mQuit.ClickedCh:
|
||||||
@@ -129,23 +131,29 @@ func (t *tray) poll(ctx context.Context) {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-tick.C:
|
case <-tick.C:
|
||||||
s := t.app.EngineStatus()
|
t.refresh()
|
||||||
state, label := describe(s)
|
}
|
||||||
systray.SetIcon(iconFor(state))
|
}
|
||||||
systray.SetTooltip("Behavision — " + label)
|
}
|
||||||
if t.mStatus != nil {
|
|
||||||
t.mStatus.SetTitle(label)
|
// refresh redraws the icon and the menu from EngineStatus - the same source
|
||||||
}
|
// the window reads, so the two cannot disagree.
|
||||||
running := s.State == "running"
|
func (t *tray) refresh() {
|
||||||
if t.mStart != nil && t.mStop != nil {
|
s := t.app.EngineStatus()
|
||||||
if running {
|
state, label := describe(s)
|
||||||
t.mStart.Disable()
|
systray.SetIcon(iconFor(state))
|
||||||
t.mStop.Enable()
|
systray.SetTooltip("Behavision — " + label)
|
||||||
} else {
|
if t.mStatus != nil {
|
||||||
t.mStart.Enable()
|
t.mStatus.SetTitle(label)
|
||||||
t.mStop.Disable()
|
}
|
||||||
}
|
running := s.State == "running" || s.State == "starting" || s.State == "backoff"
|
||||||
}
|
if t.mStart != nil && t.mStop != nil {
|
||||||
|
if running {
|
||||||
|
t.mStart.Disable()
|
||||||
|
t.mStop.Enable()
|
||||||
|
} else {
|
||||||
|
t.mStart.Enable()
|
||||||
|
t.mStop.Disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +168,9 @@ func describe(s EngineStatus) (state, label string) {
|
|||||||
case s.State == "stopped":
|
case s.State == "stopped":
|
||||||
return "stopped", "Stopped"
|
return "stopped", "Stopped"
|
||||||
case s.State == "failed":
|
case s.State == "failed":
|
||||||
return "error", "Failed — " + firstLine(s.Error)
|
// The supervisor's error is already a sentence (port in use, missing
|
||||||
|
// library); show it whole, because it is the thing to act on.
|
||||||
|
return "error", "Not running — " + firstLine(s.Error)
|
||||||
case s.State == "backoff":
|
case s.State == "backoff":
|
||||||
return "error", fmt.Sprintf("Restarting (%d attempts)", s.Restarts)
|
return "error", fmt.Sprintf("Restarting (%d attempts)", s.Restarts)
|
||||||
case !s.Reachable:
|
case !s.Reachable:
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ Push-Location (Join-Path $root "desktop")
|
|||||||
# from brand/loyaly-icon-512.png), and `wails build` would add a second copy
|
# from brand/loyaly-icon-512.png), and `wails build` would add a second copy
|
||||||
# of both and fail the link with duplicate resources.
|
# of both and fail the link with duplicate resources.
|
||||||
$env:CGO_ENABLED = "0"
|
$env:CGO_ENABLED = "0"
|
||||||
go build -ldflags "-H windowsgui -X main.version=$Version" -o (Join-Path $root "desktop\build\bin\Behavision.exe") .
|
go build -tags desktop,production -ldflags "-H windowsgui -X main.version=$Version" -o (Join-Path $root "desktop\build\bin\Behavision.exe") .
|
||||||
if ($LASTEXITCODE -ne 0) { throw "desktop build failed" }
|
if ($LASTEXITCODE -ne 0) { throw "desktop build failed" }
|
||||||
Pop-Location
|
Pop-Location
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ fi
|
|||||||
step "1. Desktop app (Wails, pure-Go Windows target)"
|
step "1. Desktop app (Wails, pure-Go Windows target)"
|
||||||
(cd desktop/frontend && npm run build >/dev/null)
|
(cd desktop/frontend && npm run build >/dev/null)
|
||||||
rm -rf "$STAGE" && mkdir -p "$STAGE/engine-src"
|
rm -rf "$STAGE" && mkdir -p "$STAGE/engine-src"
|
||||||
(cd desktop && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \
|
# -tags desktop,production is what `wails build` passes; without them the
|
||||||
|
# binary starts, shows "Wails applications will not build without the correct
|
||||||
|
# build tags" and exits. Measured on the first Windows install of v0.4.4-demo.
|
||||||
|
(cd desktop && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -tags desktop,production \
|
||||||
-ldflags "-H windowsgui -s -w -X main.version=$TAG" -o "../$STAGE/Behavision.exe" .)
|
-ldflags "-H windowsgui -s -w -X main.version=$TAG" -o "../$STAGE/Behavision.exe" .)
|
||||||
|
|
||||||
step "2. Agent and setup tool"
|
step "2. Agent and setup tool"
|
||||||
|
|||||||
Reference in New Issue
Block a user