Files
spaceshell/Makefile
T
vasyansk ee845e15b3 Add full disk access checks and settings
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
2026-06-15 22:26:06 +07:00

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 SettingsPrivacy & 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