ee845e15b3
Add background themes and custom images Add shell command logging toggle Add UTF-8 locale guarantee for PTY Add Claude hook settings injection Add hotkey system for GUI Add glass panel styling Add search disabled state for agent panels Add zoom toggle command Add device report filtering Add entitlements for notarization Update version to 0.1.27
220 lines
10 KiB
Makefile
220 lines
10 KiB
Makefile
# spacesh — local build helpers (macOS).
|
|
# `make` or `make help` lists targets.
|
|
|
|
APP_DIR := app
|
|
TAURI_TARGET := universal-apple-darwin
|
|
DMG_DIR := $(APP_DIR)/src-tauri/target/$(TAURI_TARGET)/release/bundle/dmg
|
|
NATIVE_DMG_DIR := $(APP_DIR)/src-tauri/target/release/bundle/dmg
|
|
NATIVE_TRIPLE := $(shell rustc -vV 2>/dev/null | awk '/^host:/{print $$2}')
|
|
SIDECAR_DIR := $(APP_DIR)/src-tauri/bin
|
|
ENTITLEMENTS := $(APP_DIR)/src-tauri/Entitlements.plist
|
|
BUNDLE_CONFIG := src-tauri/tauri.bundle.conf.json
|
|
APP_BUNDLE := $(APP_DIR)/src-tauri/target/$(TAURI_TARGET)/release/bundle/macos/spaceshell.app
|
|
NATIVE_APP_BUNDLE := $(APP_DIR)/src-tauri/target/release/bundle/macos/spaceshell.app
|
|
APP_VERSION := $(shell node -p "require('./$(APP_DIR)/src-tauri/tauri.conf.json').version" 2>/dev/null || echo 0.0.0)
|
|
|
|
LANDING_IMAGE := spacesh-landing
|
|
LANDING_VERSION := $(shell cat landing/VERSION 2>/dev/null || echo 0.0.0)
|
|
REGISTRY ?= git.realmanual.ru
|
|
REPO ?= spacesh
|
|
|
|
# Stable code-signing identity. Without a STABLE signature the app is ad-hoc
|
|
# signed and its code identity changes every build, so macOS attributes child
|
|
# processes (the daemon → Claude Code) to a different "responsible app" each time:
|
|
# TCC permissions reset and agents lose their Keychain login on every rebuild.
|
|
# Defaults to the Developer ID (Team 3PNKDC6L42) — a stable designated requirement
|
|
# (anchor apple generic + TeamID) that Keychain/TCC trust survives across rebuilds.
|
|
# Override with `SIGN_IDENTITY="<cert name>" make reinstall`, or `SIGN_IDENTITY=`
|
|
# to fall back to ad-hoc. Tauri reads APPLE_SIGNING_IDENTITY for the bundle + sidecar.
|
|
SIGN_IDENTITY ?= Developer ID Application: Vassiliy Yegorov (3PNKDC6L42)
|
|
ifneq ($(strip $(SIGN_IDENTITY)),)
|
|
export APPLE_SIGNING_IDENTITY := $(SIGN_IDENTITY)
|
|
endif
|
|
|
|
# Notarization (required to distribute the DMG — Gatekeeper blocks un-notarized apps
|
|
# on other Macs). Secrets: put them in a gitignored `.signing.env` (make syntax,
|
|
# e.g. `APPLE_ID := you@example.com`) or pass on the CLI. NEVER commit them.
|
|
# APPLE_ID — your Apple ID email
|
|
# APPLE_PASSWORD — an app-specific password (appleid.apple.com → App-Specific Passwords)
|
|
# APPLE_TEAM_ID — 3PNKDC6L42 (defaulted below)
|
|
# When all three are present, `tauri build` auto-notarizes + staples the bundle.
|
|
-include .signing.env
|
|
APPLE_ID ?=
|
|
APPLE_PASSWORD ?=
|
|
APPLE_TEAM_ID ?= 3PNKDC6L42
|
|
ifneq ($(strip $(APPLE_ID)),)
|
|
export APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID
|
|
endif
|
|
|
|
# ---- Gitea generic package registry (versioned .dmg downloads) ----
|
|
GITEA_URL ?= https://git.realmanual.ru
|
|
GITEA_OWNER ?= pub
|
|
GITEA_PKG ?= spacesh
|
|
GITEA_TOKEN ?= # token with package:write; pass via env/CLI, never commit
|
|
|
|
# ---- Prod deploy (SSH) ----
|
|
SSH_HOST ?= 192.168.8.5
|
|
SSH_USER ?= root
|
|
SSH_REMOTE_DIR ?= /srv/spaceshell
|
|
SSH_KEY ?= $(HOME)/.ssh/id_rsa
|
|
SSH_OPTS := -i $(SSH_KEY) -o StrictHostKeyChecking=accept-new
|
|
|
|
.DEFAULT_GOAL := help
|
|
|
|
.PHONY: help
|
|
help: ## show this help
|
|
@echo "spacesh — make targets (app v$(APP_VERSION)):"
|
|
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | \
|
|
awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
|
|
|
# ---- App / DMG (macOS) ----
|
|
|
|
.PHONY: deps
|
|
deps: ## install frontend deps (npm ci)
|
|
cd $(APP_DIR) && npm ci
|
|
|
|
.PHONY: targets
|
|
targets: ## add rust targets for the universal build
|
|
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
|
|
|
.PHONY: bump
|
|
bump: ## increment the patch version for BOTH the GUI (tauri.conf.json) and the daemon (workspace Cargo.toml)
|
|
@node scripts/bump_version.mjs
|
|
|
|
.PHONY: dmg
|
|
dmg: bump targets ## bump version + build universal .dmg (signed; notarized if .signing.env set)
|
|
# Tauri's universal build needs BOTH the per-arch sidecars (resolved during each
|
|
# arch sub-build) AND a fat spaceshd-universal-apple-darwin (copied into the final
|
|
# bundle — Tauri does not lipo sidecars itself). spaceshd ships inside
|
|
# spacesh.app/Contents/MacOS else the GUI is offline.
|
|
cargo build --release -p spaceshd --target aarch64-apple-darwin
|
|
cargo build --release -p spaceshd --target x86_64-apple-darwin
|
|
rm -rf $(SIDECAR_DIR) && mkdir -p $(SIDECAR_DIR) # avoid stale sidecars poisoning the bundle
|
|
cp target/aarch64-apple-darwin/release/spaceshd $(SIDECAR_DIR)/spaceshd-aarch64-apple-darwin
|
|
cp target/x86_64-apple-darwin/release/spaceshd $(SIDECAR_DIR)/spaceshd-x86_64-apple-darwin
|
|
lipo -create -output $(SIDECAR_DIR)/spaceshd-universal-apple-darwin \
|
|
target/aarch64-apple-darwin/release/spaceshd \
|
|
target/x86_64-apple-darwin/release/spaceshd
|
|
cd $(APP_DIR) && npm run tauri build -- --target $(TAURI_TARGET) --config $(BUNDLE_CONFIG)
|
|
@echo "→ $(DMG_DIR)" && ls -lh $(DMG_DIR)/*.dmg
|
|
|
|
.PHONY: dmg-native
|
|
dmg-native: bump ## bump version + build a .dmg for the current arch only (faster)
|
|
cargo build --release -p spaceshd
|
|
rm -rf $(SIDECAR_DIR) && mkdir -p $(SIDECAR_DIR) # avoid stale sidecars poisoning the bundle
|
|
cp target/release/spaceshd $(SIDECAR_DIR)/spaceshd-$(NATIVE_TRIPLE)
|
|
cd $(APP_DIR) && npm run tauri build -- --config $(BUNDLE_CONFIG)
|
|
@ls -lh $(NATIVE_DMG_DIR)/*.dmg
|
|
|
|
.PHONY: app-bundle
|
|
app-bundle: ## build just the native .app (no .dmg/hdiutil — fast, for self-install)
|
|
cargo build --release -p spaceshd
|
|
rm -rf $(SIDECAR_DIR) && mkdir -p $(SIDECAR_DIR)
|
|
cp target/release/spaceshd $(SIDECAR_DIR)/spaceshd-$(NATIVE_TRIPLE)
|
|
cd $(APP_DIR) && npm run tauri build -- --bundles app --config $(BUNDLE_CONFIG)
|
|
|
|
.PHONY: dev
|
|
dev: ## run the app in dev mode (tauri dev)
|
|
cd $(APP_DIR) && npm run tauri dev
|
|
|
|
.PHONY: daemon
|
|
daemon: ## build & run the daemon
|
|
cargo run -p spaceshd
|
|
|
|
.PHONY: kill-daemon
|
|
kill-daemon: ## stop a running spaceshd so a freshly-built one takes over
|
|
-pkill -x spaceshd
|
|
-rm -f $$HOME/.spacesh/sock
|
|
|
|
.PHONY: install
|
|
install: kill-daemon ## install the native .app to /Applications, restart daemon, clear quarantine
|
|
rm -rf /Applications/spacesh.app /Applications/spaceshell.app # drop the pre-rename app too
|
|
cp -R "$(NATIVE_APP_BUNDLE)" /Applications/
|
|
xattr -dr com.apple.quarantine /Applications/spaceshell.app
|
|
ifneq ($(strip $(SIGN_IDENTITY)),)
|
|
# Belt-and-suspenders: re-sign inside-out with the stable identity so neither the
|
|
# embedded daemon nor the app is left ad-hoc if Tauri skipped the sidecar.
|
|
codesign --force --options runtime --timestamp --entitlements "$(ENTITLEMENTS)" --sign "$(SIGN_IDENTITY)" /Applications/spaceshell.app/Contents/MacOS/spaceshd
|
|
codesign --force --options runtime --timestamp --entitlements "$(ENTITLEMENTS)" --sign "$(SIGN_IDENTITY)" /Applications/spaceshell.app
|
|
@codesign -dvv /Applications/spaceshell.app 2>&1 | grep -E "TeamIdentifier|Signature" || true
|
|
endif
|
|
@echo "Installed (native). Quit & relaunch spaceshell; the bundled daemon restarts."
|
|
@echo "Tip: on first launch grant Full Disk Access (System Settings → Privacy & Security)"
|
|
@echo " so terminals inside the app can run tmutil / reach protected folders."
|
|
|
|
.PHONY: install-universal
|
|
install-universal: kill-daemon ## install the universal .app to /Applications
|
|
rm -rf /Applications/spacesh.app /Applications/spaceshell.app
|
|
cp -R "$(APP_BUNDLE)" /Applications/
|
|
xattr -dr com.apple.quarantine /Applications/spaceshell.app
|
|
|
|
.PHONY: reinstall
|
|
reinstall: app-bundle install ## fast self-update: build .app (no dmg), reinstall, restart daemon
|
|
|
|
# ---- Tests ----
|
|
|
|
.PHONY: test
|
|
test: ## run all tests (cargo + tsc)
|
|
cargo test
|
|
cd $(APP_DIR) && npx tsc --noEmit
|
|
|
|
# ---- Landing ----
|
|
|
|
.PHONY: landing-image
|
|
landing-image: ## build the landing nginx image
|
|
docker build -t $(LANDING_IMAGE):$(LANDING_VERSION) -t $(LANDING_IMAGE):latest landing
|
|
|
|
.PHONY: landing-run
|
|
landing-run: landing-image ## serve the landing locally on http://localhost:8088
|
|
docker run --rm -p 8088:80 $(LANDING_IMAGE):latest
|
|
|
|
.PHONY: landing-push
|
|
landing-push: landing-image ## tag & push the landing image to the registry
|
|
docker tag $(LANDING_IMAGE):latest $(REGISTRY)/$(REPO)/$(LANDING_IMAGE):$(LANDING_VERSION)
|
|
docker tag $(LANDING_IMAGE):latest $(REGISTRY)/$(REPO)/$(LANDING_IMAGE):latest
|
|
docker push $(REGISTRY)/$(REPO)/$(LANDING_IMAGE):$(LANDING_VERSION)
|
|
docker push $(REGISTRY)/$(REPO)/$(LANDING_IMAGE):latest
|
|
|
|
# ---- Prod deploy ----
|
|
|
|
.PHONY: deploy-dmg
|
|
deploy-dmg: dmg ## upload .dmg + manifest to prod, and publish the versioned .dmg to Gitea Packages
|
|
@VER=$$(node -p "require('./$(APP_DIR)/src-tauri/tauri.conf.json').version"); \
|
|
printf '{"version":"%s","url":"https://spaceshell.ru/download/spacesh.dmg"}\n' "$$VER" > /tmp/spacesh-latest.json; \
|
|
echo "manifest version → $$VER"
|
|
ssh $(SSH_OPTS) $(SSH_USER)@$(SSH_HOST) "mkdir -p $(SSH_REMOTE_DIR)/download"
|
|
scp $(SSH_OPTS) $(DMG_DIR)/*.dmg "$(SSH_USER)@$(SSH_HOST):$(SSH_REMOTE_DIR)/download/spacesh.dmg"
|
|
scp $(SSH_OPTS) /tmp/spacesh-latest.json "$(SSH_USER)@$(SSH_HOST):$(SSH_REMOTE_DIR)/download/latest.json"
|
|
@echo "Uploaded → https://spaceshell.ru/download/spacesh.dmg + latest.json"
|
|
@$(MAKE) --no-print-directory _publish-dmg
|
|
|
|
.PHONY: publish-dmg
|
|
publish-dmg: dmg _publish-dmg ## build + publish the versioned .dmg to the Gitea package registry
|
|
|
|
# Internal: upload the most recently built .dmg to Gitea's generic registry under
|
|
# the current version. No build dependency, so deploy-dmg can call it without a
|
|
# second bump/rebuild. Skips (doesn't fail) when GITEA_TOKEN is unset.
|
|
.PHONY: _publish-dmg
|
|
_publish-dmg:
|
|
@if [ -z "$(GITEA_TOKEN)" ]; then echo "GITEA_TOKEN unset — skipping Gitea Packages publish"; exit 0; fi; \
|
|
VER=$$(node -p "require('./$(APP_DIR)/src-tauri/tauri.conf.json').version"); \
|
|
DMG=$$(ls -t $(DMG_DIR)/*.dmg 2>/dev/null | head -1); \
|
|
if [ -z "$$DMG" ]; then echo "no .dmg in $(DMG_DIR) — run make dmg first"; exit 1; fi; \
|
|
URL="$(GITEA_URL)/api/packages/$(GITEA_OWNER)/generic/$(GITEA_PKG)/$$VER/spaceshell-$$VER.dmg"; \
|
|
echo "Publishing $$DMG → $$URL"; \
|
|
curl --fail-with-body -sS -H "Authorization: token $(GITEA_TOKEN)" --upload-file "$$DMG" "$$URL" && \
|
|
echo "Published spaceshell-$$VER.dmg to Gitea Packages ($(GITEA_OWNER)/$(GITEA_PKG)@$$VER)"
|
|
|
|
.PHONY: deploy-stack
|
|
deploy-stack: ## sync compose+proxy.conf to prod and pull/up (manual; CI does this on push)
|
|
ssh $(SSH_OPTS) $(SSH_USER)@$(SSH_HOST) "mkdir -p $(SSH_REMOTE_DIR)/download"
|
|
scp $(SSH_OPTS) deploy/docker-compose.yaml deploy/proxy.conf "$(SSH_USER)@$(SSH_HOST):$(SSH_REMOTE_DIR)/"
|
|
ssh $(SSH_OPTS) $(SSH_USER)@$(SSH_HOST) "cd $(SSH_REMOTE_DIR) && docker compose pull && docker compose up -d"
|
|
|
|
# ---- Clean ----
|
|
|
|
.PHONY: clean
|
|
clean: ## remove build artifacts
|
|
cargo clean
|
|
rm -rf $(APP_DIR)/dist
|