mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-25 17:12:10 -04:00
Compare commits
1 Commits
feat/darwi
...
feat/macos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
762dad91c0 |
31
.github/backend-matrix.yml
vendored
31
.github/backend-matrix.yml
vendored
@@ -4922,37 +4922,6 @@ includeDarwin:
|
||||
tag-suffix: "-metal-darwin-arm64-vibevoice-cpp"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
# Vision/utility C++/ggml backends (go+cgo). Their Makefiles already carry a
|
||||
# Darwin/Metal path (GGML_METAL=ON when build-type=metal); this just builds and
|
||||
# publishes the metal image so Apple Silicon can install them.
|
||||
- backend: "depth-anything-cpp"
|
||||
tag-suffix: "-metal-darwin-arm64-depth-anything-cpp"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
- backend: "locate-anything-cpp"
|
||||
tag-suffix: "-metal-darwin-arm64-locate-anything-cpp"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
- backend: "rfdetr-cpp"
|
||||
tag-suffix: "-metal-darwin-arm64-rfdetr-cpp"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
- backend: "sam3-cpp"
|
||||
tag-suffix: "-metal-darwin-arm64-sam3-cpp"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
# privacy-filter (PII/NER) is a C++/ggml backend built by a bespoke darwin
|
||||
# script (make backends/privacy-filter-darwin); ggml defaults Metal ON on Apple
|
||||
# so the build is Metal-enabled. lang=go drives runner/toolchain selection only.
|
||||
- backend: "privacy-filter"
|
||||
tag-suffix: "-metal-darwin-arm64-privacy-filter"
|
||||
lang: "go"
|
||||
# LocalVQE has no Metal path; on Apple Silicon it builds CPU-only (GGML_METAL
|
||||
# OFF) but is still a native arm64 image. Uses the darwin/metal build profile.
|
||||
- backend: "localvqe"
|
||||
tag-suffix: "-metal-darwin-arm64-localvqe"
|
||||
build-type: "metal"
|
||||
lang: "go"
|
||||
- backend: "voxtral"
|
||||
tag-suffix: "-metal-darwin-arm64-voxtral"
|
||||
build-type: "metal"
|
||||
|
||||
11
.github/workflows/backend_build_darwin.yml
vendored
11
.github/workflows/backend_build_darwin.yml
vendored
@@ -228,17 +228,8 @@ jobs:
|
||||
run: |
|
||||
make backends/ds4-darwin
|
||||
|
||||
# privacy-filter is a C++/ggml backend like ds4 - a single grpc-server with
|
||||
# otool dylib bundling - so it gets its own bespoke darwin script rather than
|
||||
# the generic build-darwin-go-backend path.
|
||||
- name: Build privacy-filter backend (Darwin Metal)
|
||||
if: inputs.backend == 'privacy-filter'
|
||||
run: |
|
||||
make protogen-go
|
||||
make backends/privacy-filter-darwin
|
||||
|
||||
- name: Build ${{ inputs.backend }}-darwin
|
||||
if: inputs.backend != 'llama-cpp' && inputs.backend != 'ds4' && inputs.backend != 'privacy-filter'
|
||||
if: inputs.backend != 'llama-cpp' && inputs.backend != 'ds4'
|
||||
run: |
|
||||
make protogen-go
|
||||
BACKEND=${{ inputs.backend }} BUILD_TYPE=${{ inputs.build-type }} USE_PIP=${{ inputs.use-pip }} make build-darwin-${{ inputs.lang }}-backend
|
||||
|
||||
21
.github/workflows/release.yaml
vendored
21
.github/workflows/release.yaml
vendored
@@ -24,6 +24,11 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MACOS_SIGN_P12: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||
launcher-build-darwin:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
@@ -35,9 +40,19 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23
|
||||
- name: Build launcher for macOS ARM64
|
||||
run: |
|
||||
make build-launcher-darwin
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.MACOS_CI_KEYCHAIN_PWD }}
|
||||
run: bash contrib/macos/sign-and-notarize.sh import-cert
|
||||
- name: Build, sign and notarize the DMG
|
||||
env:
|
||||
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||
run: make release-launcher-darwin
|
||||
- name: Upload DMG to Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -94,3 +94,6 @@ core/http/react-ui/test-results/
|
||||
|
||||
# SDD / brainstorm scratch (agent-driven development)
|
||||
.superpowers/
|
||||
|
||||
# Local Apple signing material (never commit)
|
||||
.certs/
|
||||
|
||||
@@ -9,7 +9,8 @@ source:
|
||||
enabled: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Tag }}-source'
|
||||
builds:
|
||||
- main: ./cmd/local-ai
|
||||
- id: local-ai
|
||||
main: ./cmd/local-ai
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
@@ -35,3 +36,19 @@ snapshot:
|
||||
version_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
use: github-native
|
||||
# Sign + notarize the macOS server binary via the quill backend (runs on Linux,
|
||||
# no macOS runner needed). Disabled automatically when MACOS_SIGN_P12 is unset
|
||||
# (forks / PRs), so those builds stay unsigned and green.
|
||||
notarize:
|
||||
macos:
|
||||
- enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
|
||||
ids:
|
||||
- local-ai
|
||||
sign:
|
||||
certificate: "{{.Env.MACOS_SIGN_P12}}"
|
||||
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
|
||||
notarize:
|
||||
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
|
||||
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
|
||||
key: "{{.Env.MACOS_NOTARY_KEY}}"
|
||||
wait: true
|
||||
|
||||
41
Makefile
41
Makefile
@@ -1,5 +1,5 @@
|
||||
# Disable parallel execution for backend builds
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/crispasr backends/parakeet-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/omnivoice-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio backends/supertonic backends/depth-anything-cpp backends/privacy-filter backends/privacy-filter-darwin
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/crispasr backends/parakeet-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/omnivoice-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio backends/supertonic backends/depth-anything-cpp backends/privacy-filter
|
||||
|
||||
GOCMD=go
|
||||
GOTEST=$(GOCMD) test
|
||||
@@ -1129,10 +1129,6 @@ backends/ds4-darwin: build
|
||||
bash ./scripts/build/ds4-darwin.sh
|
||||
./local-ai backends install "ocifile://$(abspath ./backend-images/ds4.tar)"
|
||||
|
||||
backends/privacy-filter-darwin: build
|
||||
bash ./scripts/build/privacy-filter-darwin.sh
|
||||
./local-ai backends install "ocifile://$(abspath ./backend-images/privacy-filter.tar)"
|
||||
|
||||
build-darwin-python-backend: build
|
||||
bash ./scripts/build/python-darwin.sh
|
||||
|
||||
@@ -1453,13 +1449,32 @@ docs: docs/static/gallery.html
|
||||
########################################################
|
||||
|
||||
## fyne cross-platform build
|
||||
build-launcher-darwin: build-launcher
|
||||
go run github.com/tiagomelo/macos-dmg-creator/cmd/createdmg@latest \
|
||||
--appName "LocalAI" \
|
||||
--appBinaryPath "$(LAUNCHER_BINARY_NAME)" \
|
||||
--bundleIdentifier "com.localai.launcher" \
|
||||
--iconPath "core/http/static/logo.png" \
|
||||
--outputDir "dist/"
|
||||
# Build LocalAI.app from the launcher via fyne (metadata read from cmd/launcher/FyneApp.toml).
|
||||
# Signing happens via contrib/macos/sign-and-notarize.sh, which is a no-op when the signing
|
||||
# secrets are unset, so unsigned local/fork builds keep working.
|
||||
build-launcher-darwin:
|
||||
rm -rf dist/LocalAI.app cmd/launcher/LocalAI.app
|
||||
mkdir -p dist
|
||||
cd cmd/launcher && go run fyne.io/tools/cmd/fyne@latest package -os darwin -icon ../../core/http/static/logo.png --executable $(LAUNCHER_BINARY_NAME)
|
||||
mv cmd/launcher/LocalAI.app dist/LocalAI.app
|
||||
bash contrib/macos/sign-and-notarize.sh sign dist/LocalAI.app
|
||||
|
||||
# Wrap the (signed) app into a drag-to-Applications DMG via hdiutil, then sign the DMG.
|
||||
dmg-launcher-darwin: build-launcher-darwin
|
||||
rm -rf dist/dmg dist/LocalAI.dmg
|
||||
mkdir -p dist/dmg
|
||||
cp -R dist/LocalAI.app dist/dmg/LocalAI.app
|
||||
ln -s /Applications dist/dmg/Applications
|
||||
hdiutil create -volname "LocalAI" -srcfolder dist/dmg -ov -format UDZO dist/LocalAI.dmg
|
||||
bash contrib/macos/sign-and-notarize.sh sign dist/LocalAI.dmg
|
||||
|
||||
# Submit the DMG to Apple notarization and staple the ticket (no-op without notary secrets).
|
||||
notarize-launcher-darwin: dmg-launcher-darwin
|
||||
bash contrib/macos/sign-and-notarize.sh notarize dist/LocalAI.dmg
|
||||
|
||||
# Single entrypoint for CI: build -> sign app -> dmg -> sign dmg -> notarize -> staple.
|
||||
release-launcher-darwin: notarize-launcher-darwin
|
||||
@echo "dist/LocalAI.dmg is ready"
|
||||
|
||||
build-launcher-linux:
|
||||
cd cmd/launcher && go run fyne.io/tools/cmd/fyne@latest package -os linux -icon ../../core/http/static/logo.png --executable $(LAUNCHER_BINARY_NAME)-linux && mv launcher.tar.xz ../../$(LAUNCHER_BINARY_NAME)-linux.tar.xz
|
||||
cd cmd/launcher && go run fyne.io/tools/cmd/fyne@latest package -os linux -icon ../../core/http/static/logo.png --executable $(LAUNCHER_BINARY_NAME)-linux && mv LocalAI.tar.xz ../../$(LAUNCHER_BINARY_NAME)-linux.tar.xz
|
||||
|
||||
@@ -51,14 +51,6 @@ add_library(hw_grpc_proto STATIC
|
||||
${HW_GRPC_SRCS} ${HW_GRPC_HDRS}
|
||||
${HW_PROTO_SRCS} ${HW_PROTO_HDRS})
|
||||
target_include_directories(hw_grpc_proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
# The generated proto/grpc sources include protobuf and grpc++ headers, so this
|
||||
# library must see their include dirs. Linking the imported targets propagates
|
||||
# them. On Linux the apt headers live in /usr/include (default search path) so
|
||||
# this was a no-op; on macOS the Homebrew headers are under /opt/homebrew and
|
||||
# would otherwise be missed (runtime_version.h not found).
|
||||
target_link_libraries(hw_grpc_proto PUBLIC
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc++)
|
||||
|
||||
# Build only the pf static lib (+ ggml) from the engine tree — no CLI/bench/tests.
|
||||
# PF_VULKAN is honored when passed on the cmake command line (it lands in the
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
# Entry point for the privacy-filter backend image / BACKEND_BINARY mode.
|
||||
set -e
|
||||
CURDIR=$(dirname "$(realpath "$0")")
|
||||
# macOS has no bundled ld.so; the darwin package ships only dylibs under lib/,
|
||||
# resolved via DYLD_LIBRARY_PATH (the ld.so branch below is skipped there).
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
export DYLD_LIBRARY_PATH="$CURDIR/lib:$DYLD_LIBRARY_PATH"
|
||||
else
|
||||
export LD_LIBRARY_PATH="$CURDIR/lib:$LD_LIBRARY_PATH"
|
||||
fi
|
||||
export LD_LIBRARY_PATH="$CURDIR/lib:$LD_LIBRARY_PATH"
|
||||
if [ -f "$CURDIR/lib/ld.so" ]; then
|
||||
exec "$CURDIR/lib/ld.so" "$CURDIR/grpc-server" "$@"
|
||||
fi
|
||||
|
||||
@@ -40,8 +40,6 @@ else ifeq ($(BUILD_TYPE),hipblas)
|
||||
else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DGGML_VULKAN=ON -DDA_GGML_VULKAN=ON
|
||||
else ifeq ($(OS),Darwin)
|
||||
# macOS/Metal: built + published as an OCI image by CI (includeDarwin in
|
||||
# .github/backend-matrix.yml) so Apple Silicon users can install this backend.
|
||||
ifneq ($(BUILD_TYPE),metal)
|
||||
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||
else
|
||||
|
||||
@@ -32,8 +32,6 @@ endif
|
||||
ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DGGML_VULKAN=ON -DLOCALVQE_VULKAN=ON
|
||||
else ifeq ($(OS),Darwin)
|
||||
# Apple Silicon: CPU-only (no Metal upstream); built + published as an arm64
|
||||
# image by CI (includeDarwin in .github/backend-matrix.yml) for macOS install.
|
||||
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||
endif
|
||||
|
||||
|
||||
@@ -33,8 +33,6 @@ else ifeq ($(BUILD_TYPE),hipblas)
|
||||
else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DGGML_VULKAN=ON -DLA_GGML_VULKAN=ON
|
||||
else ifeq ($(OS),Darwin)
|
||||
# macOS/Metal: built + published as an OCI image by CI (includeDarwin in
|
||||
# .github/backend-matrix.yml) so Apple Silicon users can install this backend.
|
||||
ifneq ($(BUILD_TYPE),metal)
|
||||
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||
else
|
||||
|
||||
@@ -34,8 +34,6 @@ else ifeq ($(BUILD_TYPE),hipblas)
|
||||
else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DGGML_VULKAN=ON -DRFDETR_GGML_VULKAN=ON
|
||||
else ifeq ($(OS),Darwin)
|
||||
# macOS/Metal: built + published as an OCI image by CI (includeDarwin in
|
||||
# .github/backend-matrix.yml) so Apple Silicon users can install this backend.
|
||||
ifneq ($(BUILD_TYPE),metal)
|
||||
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||
else
|
||||
|
||||
@@ -31,8 +31,6 @@ else ifeq ($(BUILD_TYPE),hipblas)
|
||||
else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DGGML_VULKAN=ON
|
||||
else ifeq ($(OS),Darwin)
|
||||
# macOS/Metal: built + published as an OCI image by CI (includeDarwin in
|
||||
# .github/backend-matrix.yml) so Apple Silicon users can install this backend.
|
||||
ifneq ($(BUILD_TYPE),metal)
|
||||
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||
else
|
||||
|
||||
@@ -340,7 +340,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-sam3-cpp"
|
||||
intel: "intel-sycl-f32-sam3-cpp"
|
||||
vulkan: "vulkan-sam3-cpp"
|
||||
metal: "metal-sam3-cpp"
|
||||
- &rfdetrcpp
|
||||
name: "rfdetr-cpp"
|
||||
alias: "rfdetr-cpp"
|
||||
@@ -369,7 +368,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-rfdetr-cpp"
|
||||
intel: "intel-sycl-f32-rfdetr-cpp"
|
||||
vulkan: "vulkan-rfdetr-cpp"
|
||||
metal: "metal-rfdetr-cpp"
|
||||
- &locateanything
|
||||
name: "locate-anything"
|
||||
alias: "locate-anything"
|
||||
@@ -399,7 +397,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-locate-anything-cpp"
|
||||
intel: "intel-sycl-f32-locate-anything-cpp"
|
||||
vulkan: "vulkan-locate-anything-cpp"
|
||||
metal: "metal-locate-anything-cpp"
|
||||
- !!merge <<: *locateanything
|
||||
name: "locate-anything-development"
|
||||
capabilities:
|
||||
@@ -412,7 +409,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-locate-anything-cpp-development"
|
||||
intel: "intel-sycl-f32-locate-anything-cpp-development"
|
||||
vulkan: "vulkan-locate-anything-cpp-development"
|
||||
metal: "metal-locate-anything-cpp-development"
|
||||
- !!merge <<: *locateanything
|
||||
name: "cpu-locate-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-locate-anything-cpp"
|
||||
@@ -423,16 +419,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-locate-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-cpu-locate-anything-cpp
|
||||
- !!merge <<: *locateanything
|
||||
name: "metal-locate-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-locate-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-locate-anything-cpp
|
||||
- !!merge <<: *locateanything
|
||||
name: "metal-locate-anything-cpp-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-locate-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-locate-anything-cpp
|
||||
- !!merge <<: *locateanything
|
||||
name: "cuda12-locate-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-locate-anything-cpp"
|
||||
@@ -531,7 +517,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-depth-anything-cpp"
|
||||
intel: "intel-sycl-f32-depth-anything-cpp"
|
||||
vulkan: "vulkan-depth-anything-cpp"
|
||||
metal: "metal-depth-anything-cpp"
|
||||
- !!merge <<: *depthanything
|
||||
name: "depth-anything-development"
|
||||
capabilities:
|
||||
@@ -544,7 +529,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-depth-anything-cpp-development"
|
||||
intel: "intel-sycl-f32-depth-anything-cpp-development"
|
||||
vulkan: "vulkan-depth-anything-cpp-development"
|
||||
metal: "metal-depth-anything-cpp-development"
|
||||
- !!merge <<: *depthanything
|
||||
name: "cpu-depth-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-depth-anything-cpp"
|
||||
@@ -555,16 +539,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-depth-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-cpu-depth-anything-cpp
|
||||
- !!merge <<: *depthanything
|
||||
name: "metal-depth-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-depth-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-depth-anything-cpp
|
||||
- !!merge <<: *depthanything
|
||||
name: "metal-depth-anything-cpp-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-depth-anything-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-depth-anything-cpp
|
||||
- !!merge <<: *depthanything
|
||||
name: "cuda12-depth-anything-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-depth-anything-cpp"
|
||||
@@ -1057,8 +1031,6 @@
|
||||
nvidia-l4t: "vulkan-localvqe"
|
||||
nvidia-l4t-cuda-12: "vulkan-localvqe"
|
||||
nvidia-l4t-cuda-13: "vulkan-localvqe"
|
||||
# Apple Silicon: CPU build (LocalVQE has no Metal path); still arm64-native.
|
||||
metal: "metal-localvqe"
|
||||
- &privacyfilter
|
||||
name: "privacy-filter"
|
||||
alias: "privacy-filter"
|
||||
@@ -1095,7 +1067,6 @@
|
||||
amd: "vulkan-privacy-filter"
|
||||
intel: "vulkan-privacy-filter"
|
||||
vulkan: "vulkan-privacy-filter"
|
||||
metal: "metal-privacy-filter"
|
||||
- &faster-whisper
|
||||
icon: https://avatars.githubusercontent.com/u/1520500?s=200&v=4
|
||||
description: |
|
||||
@@ -2938,16 +2909,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-vulkan-privacy-filter"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-vulkan-privacy-filter
|
||||
- !!merge <<: *privacyfilter
|
||||
name: "metal-privacy-filter"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-privacy-filter"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-privacy-filter
|
||||
- !!merge <<: *privacyfilter
|
||||
name: "metal-privacy-filter-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-privacy-filter"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-privacy-filter
|
||||
- !!merge <<: *privacyfilter
|
||||
name: "cuda13-privacy-filter"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-privacy-filter"
|
||||
@@ -3259,7 +3220,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-sam3-cpp-development"
|
||||
intel: "intel-sycl-f32-sam3-cpp-development"
|
||||
vulkan: "vulkan-sam3-cpp-development"
|
||||
metal: "metal-sam3-cpp-development"
|
||||
- !!merge <<: *sam3cpp
|
||||
name: "cpu-sam3-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-sam3-cpp"
|
||||
@@ -3270,16 +3230,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-sam3-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-cpu-sam3-cpp
|
||||
- !!merge <<: *sam3cpp
|
||||
name: "metal-sam3-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-sam3-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-sam3-cpp
|
||||
- !!merge <<: *sam3cpp
|
||||
name: "metal-sam3-cpp-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-sam3-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-sam3-cpp
|
||||
- !!merge <<: *sam3cpp
|
||||
name: "cuda12-sam3-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-sam3-cpp"
|
||||
@@ -3353,7 +3303,6 @@
|
||||
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-rfdetr-cpp-development"
|
||||
intel: "intel-sycl-f32-rfdetr-cpp-development"
|
||||
vulkan: "vulkan-rfdetr-cpp-development"
|
||||
metal: "metal-rfdetr-cpp-development"
|
||||
- !!merge <<: *rfdetrcpp
|
||||
name: "cpu-rfdetr-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-rfdetr-cpp"
|
||||
@@ -3364,16 +3313,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-rfdetr-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-cpu-rfdetr-cpp
|
||||
- !!merge <<: *rfdetrcpp
|
||||
name: "metal-rfdetr-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-rfdetr-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-rfdetr-cpp
|
||||
- !!merge <<: *rfdetrcpp
|
||||
name: "metal-rfdetr-cpp-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-rfdetr-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-rfdetr-cpp
|
||||
- !!merge <<: *rfdetrcpp
|
||||
name: "cuda12-rfdetr-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-rfdetr-cpp"
|
||||
@@ -4162,16 +4101,6 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-vulkan-localvqe"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-vulkan-localvqe
|
||||
- !!merge <<: *localvqecpp
|
||||
name: "metal-localvqe"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-localvqe"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-localvqe
|
||||
- !!merge <<: *localvqecpp
|
||||
name: "metal-localvqe-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-localvqe"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-localvqe
|
||||
## kokoro
|
||||
- !!merge <<: *kokoro
|
||||
name: "kokoro-development"
|
||||
|
||||
8
cmd/launcher/FyneApp.toml
Normal file
8
cmd/launcher/FyneApp.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
Website = "https://localai.io"
|
||||
|
||||
[Details]
|
||||
Icon = "../../core/http/static/logo.png"
|
||||
Name = "LocalAI"
|
||||
ID = "com.localai.launcher"
|
||||
Version = "0.0.0"
|
||||
Build = 1
|
||||
14
contrib/macos/Launcher.entitlements
Normal file
14
contrib/macos/Launcher.entitlements
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
84
contrib/macos/sign-and-notarize.sh
Executable file
84
contrib/macos/sign-and-notarize.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
# Code-sign and notarize macOS artifacts for LocalAI.
|
||||
# Every sub-command is a no-op (exit 0) when its required secret is unset,
|
||||
# so unsigned builds (forks, local dev, PRs) keep working.
|
||||
set -euo pipefail
|
||||
|
||||
ENTITLEMENTS="contrib/macos/Launcher.entitlements"
|
||||
KEYCHAIN="localai-ci.keychain-db"
|
||||
|
||||
cmd_import_cert() {
|
||||
if [ -z "${MACOS_CERTIFICATE:-}" ]; then
|
||||
echo "[sign] MACOS_CERTIFICATE unset: skipping cert import (unsigned build)"
|
||||
return 0
|
||||
fi
|
||||
local certfile keychain_pwd default_keychain
|
||||
certfile="$(mktemp).p12"
|
||||
keychain_pwd="${MACOS_CI_KEYCHAIN_PWD:?MACOS_CI_KEYCHAIN_PWD required when signing}"
|
||||
echo "$MACOS_CERTIFICATE" | base64 --decode > "$certfile"
|
||||
security create-keychain -p "$keychain_pwd" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$keychain_pwd" "$KEYCHAIN"
|
||||
security import "$certfile" -k "$KEYCHAIN" -P "${MACOS_CERTIFICATE_PWD:?}" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
||||
-s -k "$keychain_pwd" "$KEYCHAIN" >/dev/null
|
||||
default_keychain="$(security default-keychain | tr -d ' "')"
|
||||
security list-keychains -d user -s "$KEYCHAIN" "$default_keychain"
|
||||
rm -f "$certfile"
|
||||
echo "[sign] certificate imported into $KEYCHAIN"
|
||||
}
|
||||
|
||||
cmd_sign() {
|
||||
local target="$1"
|
||||
if [ -z "${MACOS_SIGN_IDENTITY:-}" ]; then
|
||||
echo "[sign] MACOS_SIGN_IDENTITY unset: skipping codesign of $target"
|
||||
return 0
|
||||
fi
|
||||
case "$target" in
|
||||
*.app)
|
||||
# Hardened runtime + entitlements are required for notarizing the app bundle.
|
||||
codesign --deep --force --options runtime --timestamp \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$MACOS_SIGN_IDENTITY" "$target"
|
||||
;;
|
||||
*)
|
||||
# A disk image carries no entitlements/runtime; just sign the container.
|
||||
codesign --force --timestamp --sign "$MACOS_SIGN_IDENTITY" "$target"
|
||||
;;
|
||||
esac
|
||||
codesign --verify --strict --verbose=2 "$target"
|
||||
echo "[sign] signed $target"
|
||||
}
|
||||
|
||||
cmd_notarize() {
|
||||
local dmg="$1"
|
||||
if [ -z "${MACOS_NOTARY_KEY:-}" ]; then
|
||||
echo "[notarize] MACOS_NOTARY_KEY unset: skipping notarization of $dmg"
|
||||
return 0
|
||||
fi
|
||||
local keyfile
|
||||
keyfile="$(mktemp).p8"
|
||||
echo "$MACOS_NOTARY_KEY" | base64 --decode > "$keyfile"
|
||||
xcrun notarytool submit "$dmg" \
|
||||
--key "$keyfile" \
|
||||
--key-id "${MACOS_NOTARY_KEY_ID:?}" \
|
||||
--issuer "${MACOS_NOTARY_ISSUER_ID:?}" \
|
||||
--wait
|
||||
rm -f "$keyfile"
|
||||
xcrun stapler staple "$dmg"
|
||||
xcrun stapler validate "$dmg"
|
||||
echo "[notarize] notarized and stapled $dmg"
|
||||
}
|
||||
|
||||
main() {
|
||||
local sub="${1:-}"; shift || true
|
||||
case "$sub" in
|
||||
import-cert) cmd_import_cert ;;
|
||||
sign) cmd_sign "$@" ;;
|
||||
notarize) cmd_notarize "$@" ;;
|
||||
*) echo "usage: $0 {import-cert|sign <path>|notarize <dmg>}" >&2; exit 2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -3,51 +3,10 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func openSQLiteDialector(path string) (gorm.Dialector, error) {
|
||||
return sqlite.Open(buildSQLiteDSN(path)), nil
|
||||
}
|
||||
|
||||
// buildSQLiteDSN augments a SQLite file path with connection pragmas that make
|
||||
// the auth DB resilient on slow or contended storage.
|
||||
//
|
||||
// - _busy_timeout=5000 makes SQLite retry for up to 5s on SQLITE_BUSY instead
|
||||
// of failing immediately. Network-backed storage (SMB/CIFS/NFS, e.g. Azure
|
||||
// Files) is prone to transient lock contention during migration (see #10506).
|
||||
// - _txlock=immediate takes the write lock at BEGIN, avoiding deadlocks when a
|
||||
// read transaction later upgrades to a write during AutoMigrate.
|
||||
//
|
||||
// We deliberately do NOT set WAL journal mode: WAL relies on a shared-memory
|
||||
// mmap that does not work over SMB/NFS, which is exactly the failing case here.
|
||||
//
|
||||
// Caller-supplied values for either pragma are preserved.
|
||||
func buildSQLiteDSN(path string) string {
|
||||
base := path
|
||||
rawQuery := ""
|
||||
if i := strings.IndexByte(path, '?'); i >= 0 {
|
||||
base = path[:i]
|
||||
rawQuery = path[i+1:]
|
||||
}
|
||||
|
||||
values, err := url.ParseQuery(rawQuery)
|
||||
if err != nil {
|
||||
// An unparseable query string means a hand-crafted DSN we should not
|
||||
// risk corrupting; leave it untouched.
|
||||
return path
|
||||
}
|
||||
|
||||
if values.Get("_busy_timeout") == "" {
|
||||
values.Set("_busy_timeout", "5000")
|
||||
}
|
||||
if values.Get("_txlock") == "" {
|
||||
values.Set("_txlock", "immediate")
|
||||
}
|
||||
|
||||
return base + "?" + values.Encode()
|
||||
return sqlite.Open(path), nil
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//go:build auth
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// parseDSN splits a "base?query" DSN into its base and decoded query values so
|
||||
// assertions don't depend on url.Values.Encode()'s key ordering.
|
||||
func parseDSN(dsn string) (string, url.Values) {
|
||||
base := dsn
|
||||
rawQuery := ""
|
||||
if i := strings.IndexByte(dsn, '?'); i >= 0 {
|
||||
base = dsn[:i]
|
||||
rawQuery = dsn[i+1:]
|
||||
}
|
||||
values, err := url.ParseQuery(rawQuery)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
return base, values
|
||||
}
|
||||
|
||||
var _ = Describe("buildSQLiteDSN", func() {
|
||||
It("adds busy_timeout and txlock to a plain file path", func() {
|
||||
base, values := parseDSN(buildSQLiteDSN("/data/database.db"))
|
||||
Expect(base).To(Equal("/data/database.db"))
|
||||
Expect(values.Get("_busy_timeout")).To(Equal("5000"))
|
||||
Expect(values.Get("_txlock")).To(Equal("immediate"))
|
||||
})
|
||||
|
||||
It("adds pragmas to an in-memory database", func() {
|
||||
base, values := parseDSN(buildSQLiteDSN(":memory:"))
|
||||
Expect(base).To(Equal(":memory:"))
|
||||
Expect(values.Get("_busy_timeout")).To(Equal("5000"))
|
||||
Expect(values.Get("_txlock")).To(Equal("immediate"))
|
||||
})
|
||||
|
||||
It("preserves an existing query string", func() {
|
||||
base, values := parseDSN(buildSQLiteDSN("/data/database.db?cache=shared"))
|
||||
Expect(base).To(Equal("/data/database.db"))
|
||||
Expect(values.Get("cache")).To(Equal("shared"))
|
||||
Expect(values.Get("_busy_timeout")).To(Equal("5000"))
|
||||
Expect(values.Get("_txlock")).To(Equal("immediate"))
|
||||
})
|
||||
|
||||
It("does not override a caller-supplied busy_timeout or txlock", func() {
|
||||
_, values := parseDSN(buildSQLiteDSN("/data/database.db?_busy_timeout=1000&_txlock=deferred"))
|
||||
Expect(values["_busy_timeout"]).To(HaveLen(1), "_busy_timeout should not be duplicated")
|
||||
Expect(values.Get("_busy_timeout")).To(Equal("1000"))
|
||||
Expect(values["_txlock"]).To(HaveLen(1), "_txlock should not be duplicated")
|
||||
Expect(values.Get("_txlock")).To(Equal("deferred"))
|
||||
})
|
||||
})
|
||||
@@ -4,59 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// localLocks holds one buffered channel (capacity 1) per lock key, used as an
|
||||
// in-process mutex for non-PostgreSQL dialects (SQLite). A SQLite auth DB is
|
||||
// effectively single-process, so serializing guarded sections within this
|
||||
// process is sufficient - we cannot and need not coordinate across processes
|
||||
// the way a PostgreSQL advisory lock does.
|
||||
var (
|
||||
localLocksMu sync.Mutex
|
||||
localLocks = map[int64]chan struct{}{}
|
||||
)
|
||||
|
||||
// localLockChan returns the per-key buffered channel, creating it on first use.
|
||||
func localLockChan(key int64) chan struct{} {
|
||||
localLocksMu.Lock()
|
||||
defer localLocksMu.Unlock()
|
||||
ch, ok := localLocks[key]
|
||||
if !ok {
|
||||
ch = make(chan struct{}, 1)
|
||||
localLocks[key] = ch
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
// isPostgres reports whether the gorm dialect is PostgreSQL. Anything else
|
||||
// (SQLite and any non-postgres dialect) uses the in-process fallback, because
|
||||
// the pg_* advisory lock functions only exist on PostgreSQL.
|
||||
func isPostgres(db *gorm.DB) bool {
|
||||
return strings.Contains(db.Dialector.Name(), "postgres")
|
||||
}
|
||||
|
||||
// TryWithLockCtx attempts to acquire a lock and run fn without blocking.
|
||||
// Returns (true, nil) if the lock was acquired and fn executed, (false, nil) if
|
||||
// the lock was already held, or (false, error) on failure.
|
||||
//
|
||||
// On PostgreSQL it uses pg_try_advisory_lock (cross-process). On other dialects
|
||||
// (SQLite) it uses a non-blocking in-process lock keyed by key.
|
||||
// TryWithLockCtx attempts to acquire a PostgreSQL advisory lock using the provided context.
|
||||
// Returns (true, nil) if the lock was acquired and fn executed, (false, nil) if the lock
|
||||
// was already held, or (false, error) on failure.
|
||||
func TryWithLockCtx(ctx context.Context, db *gorm.DB, key int64, fn func() error) (bool, error) {
|
||||
if !isPostgres(db) {
|
||||
ch := localLockChan(key)
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
defer func() { <-ch }()
|
||||
return true, fn()
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get sql.DB: %w", err)
|
||||
@@ -95,31 +50,9 @@ func KeyFromString(s string) int64 {
|
||||
return int64(h.Sum64()>>1) | 0x100000000
|
||||
}
|
||||
|
||||
// WithLockCtx acquires a lock for key, runs fn, then releases it, respecting
|
||||
// context cancellation. If ctx is cancelled while waiting for the lock, the
|
||||
// function returns ctx.Err().
|
||||
//
|
||||
// On PostgreSQL it uses pg_advisory_lock (cross-process). On other dialects
|
||||
// (SQLite) it falls back to a blocking in-process lock keyed by key, which is
|
||||
// sufficient because a SQLite auth DB is effectively single-process.
|
||||
// WithLockCtx is like WithLock but respects context cancellation.
|
||||
// If ctx is cancelled while waiting for the lock, the function returns ctx.Err().
|
||||
func WithLockCtx(ctx context.Context, db *gorm.DB, key int64, fn func() error) error {
|
||||
if !isPostgres(db) {
|
||||
// Honor an already-cancelled context before attempting acquisition:
|
||||
// select picks a ready case at random, so without this an already-free
|
||||
// lock could be taken despite a cancelled ctx.
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
ch := localLockChan(key)
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
defer func() { <-ch }()
|
||||
return fn()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("advisorylock: getting sql.DB: %w", err)
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package advisorylock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// These specs run against an in-memory SQLite DB and therefore do NOT require
|
||||
// Docker, unlike the PostgreSQL testcontainer specs.
|
||||
var _ = Describe("AdvisoryLock (SQLite fallback)", Label("sqlite"), func() {
|
||||
var db *gorm.DB
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
db, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(db.Dialector.Name()).To(ContainSubstring("sqlite"))
|
||||
})
|
||||
|
||||
It("WithLockCtx executes fn and returns no error on SQLite", func() {
|
||||
const lockKey int64 = 12001
|
||||
executed := false
|
||||
|
||||
err := WithLockCtx(context.Background(), db, lockKey, func() error {
|
||||
executed = true
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(executed).To(BeTrue(), "function should have run under the in-process lock")
|
||||
})
|
||||
|
||||
It("WithLockCtx serializes concurrent goroutines on the same key", func() {
|
||||
const lockKey int64 = 12002
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
maxRunning int32
|
||||
running int32
|
||||
concurrency int32
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for range 2 {
|
||||
wg.Go(func() {
|
||||
defer GinkgoRecover()
|
||||
err := WithLockCtx(context.Background(), db, lockKey, func() error {
|
||||
cur := atomic.AddInt32(&running, 1)
|
||||
mu.Lock()
|
||||
if cur > maxRunning {
|
||||
maxRunning = cur
|
||||
}
|
||||
if cur > 1 {
|
||||
atomic.AddInt32(&concurrency, 1)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
atomic.AddInt32(&running, -1)
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
Expect(maxRunning).To(BeNumerically("<=", 1), "expected max 1 goroutine inside lock at a time")
|
||||
Expect(concurrency).To(BeZero(), "detected concurrent execution inside advisory lock")
|
||||
})
|
||||
|
||||
It("WithLockCtx returns an error and does not run fn with an already-cancelled context", func() {
|
||||
const lockKey int64 = 12003
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err := WithLockCtx(ctx, db, lockKey, func() error {
|
||||
Fail("function should not run with a cancelled context")
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("TryWithLockCtx returns (true, nil) when free and (false, nil) when held", func() {
|
||||
const lockKey int64 = 12004
|
||||
|
||||
acquired, err := TryWithLockCtx(context.Background(), db, lockKey, func() error {
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(acquired).To(BeTrue(), "expected TryWithLockCtx to acquire the free lock")
|
||||
|
||||
// Hold the lock in one goroutine while a concurrent TryWithLockCtx
|
||||
// attempts to acquire the same key.
|
||||
held := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Go(func() {
|
||||
defer GinkgoRecover()
|
||||
ok, err := TryWithLockCtx(context.Background(), db, lockKey, func() error {
|
||||
close(held)
|
||||
<-release
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ok).To(BeTrue())
|
||||
})
|
||||
|
||||
<-held
|
||||
ok, err := TryWithLockCtx(context.Background(), db, lockKey, func() error {
|
||||
Fail("function should not run while lock is held")
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ok).To(BeFalse(), "expected TryWithLockCtx to fail to acquire a held lock")
|
||||
|
||||
close(release)
|
||||
wg.Wait()
|
||||
})
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
//go:build auth
|
||||
|
||||
package jobs_test
|
||||
|
||||
import (
|
||||
"github.com/mudler/LocalAI/core/http/auth"
|
||||
"github.com/mudler/LocalAI/core/services/jobs"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// Reproduces the #10506 caller chain: auth.InitDB(sqlite) -> jobs.NewJobStore,
|
||||
// which previously failed with "no such function: pg_advisory_lock".
|
||||
var _ = Describe("NewJobStore on a SQLite auth DB (#10506)", func() {
|
||||
It("migrates without pg_advisory_lock errors", func() {
|
||||
db, err := auth.InitDB(":memory:")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
store, err := jobs.NewJobStore(db)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(store).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
@@ -85,8 +85,6 @@ localai run
|
||||
| `LOCALAI_REGISTRATION_MODE` | `approval` | Registration mode: `open`, `approval`, or `invite` |
|
||||
| `LOCALAI_DISABLE_LOCAL_AUTH` | `false` | Disable local email/password registration and login (for OAuth/OIDC-only deployments) |
|
||||
|
||||
> **Note: network-backed storage.** File-based SQLite relies on POSIX file locking, which is unreliable over network filesystems (SMB/CIFS/NFS, e.g. Azure Files / Azure Container Apps shared volumes). On such storage the auth DB can fail to migrate with `database is locked`. Use PostgreSQL (`LOCALAI_AUTH_DATABASE_URL=postgres://...`) when the data directory lives on shared or network storage, or place `database.db` on a local volume.
|
||||
|
||||
### Disabling Local Authentication
|
||||
|
||||
If you want to enforce OAuth/OIDC-only login and prevent users from registering or logging in with email/password, set `LOCALAI_DISABLE_LOCAL_AUTH=true` (or pass `--disable-local-auth`):
|
||||
|
||||
@@ -22,13 +22,16 @@ Download the latest DMG from GitHub releases:
|
||||
3. Drag the LocalAI application to your Applications folder
|
||||
4. Launch LocalAI from your Applications folder
|
||||
|
||||
## Known Issues
|
||||
## Verification
|
||||
|
||||
> **Note**: The DMGs are not signed by Apple and may show as quarantined.
|
||||
>
|
||||
> **Workaround**: See [this issue](https://github.com/mudler/LocalAI/issues/6268) for details on how to bypass the quarantine.
|
||||
>
|
||||
> **Fix tracking**: The signing issue is being tracked in [this issue](https://github.com/mudler/LocalAI/issues/6244).
|
||||
The `LocalAI.dmg` (and the app inside it) and the `local-ai` server binary are
|
||||
signed with an Apple Developer ID and notarized by Apple, so they launch with no
|
||||
quarantine prompt or workaround. To inspect the signature yourself:
|
||||
|
||||
```bash
|
||||
spctl --assess --type open --context context:primary-signature -v /Applications/LocalAI.app
|
||||
codesign --verify --deep --strict --verbose=2 /Applications/LocalAI.app
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Darwin/Metal build for the privacy-filter backend. Mirrors ds4-darwin.sh:
|
||||
# native make of the single grpc-server, otool -L dylib bundling, then assemble
|
||||
# an OCI tar that `local-ai backends install` can consume.
|
||||
#
|
||||
# privacy-filter.cpp pulls ggml, which defaults GGML_METAL=ON on Apple - the
|
||||
# engine's CMake never forces it off, so a plain Darwin build is Metal-enabled.
|
||||
# grpc++/protobuf are resolved from Homebrew via find_package(... CONFIG).
|
||||
set -ex
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME:-localai/privacy-filter-darwin}"
|
||||
|
||||
pushd backend/cpp/privacy-filter
|
||||
make grpc-server
|
||||
popd
|
||||
|
||||
mkdir -p build/darwin
|
||||
mkdir -p build/darwin/lib
|
||||
mkdir -p backend-images
|
||||
|
||||
cp -rf backend/cpp/privacy-filter/grpc-server build/darwin/
|
||||
cp -rf backend/cpp/privacy-filter/run.sh build/darwin/
|
||||
|
||||
# Apple Silicon: pick up Homebrew-installed protobuf utf8_validity if present
|
||||
# (same as ds4-darwin.sh - it is a transitive dep otool may not surface).
|
||||
if [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; then
|
||||
ADDITIONAL_LIBS=${ADDITIONAL_LIBS:-$(ls /opt/homebrew/Cellar/protobuf/**/lib/libutf8_validity*.dylib 2>/dev/null)}
|
||||
else
|
||||
ADDITIONAL_LIBS=${ADDITIONAL_LIBS:-""}
|
||||
fi
|
||||
for file in $ADDITIONAL_LIBS; do
|
||||
cp -rfv "$file" build/darwin/lib
|
||||
done
|
||||
|
||||
# Bundle the ggml shared libs the binary @rpath-links (libggml, -cpu, -blas,
|
||||
# -metal). The engine builds ggml shared, scattered under the build tree; flatten
|
||||
# them (with their version symlinks) into lib/, resolved at runtime by leaf name
|
||||
# via run.sh's DYLD_LIBRARY_PATH=lib. Without this the packaged binary can't find
|
||||
# libggml*.dylib once the build dir is gone.
|
||||
GGML_SRC="backend/cpp/privacy-filter/build/privacy-filter.cpp/ggml/src"
|
||||
find "$GGML_SRC" -name 'libggml*.dylib' -exec cp -a {} build/darwin/lib/ \;
|
||||
|
||||
# Walk dylibs via otool -L and bundle anything that isn't a system framework.
|
||||
for file in build/darwin/grpc-server; do
|
||||
LIBS="$(otool -L "$file" | awk 'NR > 1 { system("echo " $1) } ' | xargs echo)"
|
||||
for lib in $LIBS; do
|
||||
if [[ "$lib" == *.dylib ]] && [[ -e "$lib" ]]; then
|
||||
cp -rvf "$lib" build/darwin/lib
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo "Bundled libraries:"
|
||||
ls -la build/darwin/lib
|
||||
|
||||
# Build an OCI image tar (with manifest.json) that `local-ai backends install`
|
||||
# can consume - mirrors ds4-darwin.sh.
|
||||
PLATFORMARCH="${PLATFORMARCH:-darwin/arm64}"
|
||||
|
||||
./local-ai util create-oci-image \
|
||||
build/darwin/. \
|
||||
--output ./backend-images/privacy-filter.tar \
|
||||
--image-name "$IMAGE_NAME" \
|
||||
--platform "$PLATFORMARCH"
|
||||
|
||||
rm -rf build/darwin
|
||||
@@ -71,11 +71,6 @@ function inferBackendPathDarwin(item) {
|
||||
if (item.backend === "ds4") {
|
||||
return `backend/cpp/ds4/`;
|
||||
}
|
||||
// privacy-filter is C++ too (built via `make backends/privacy-filter-darwin`);
|
||||
// same lang=go-for-runner convention, source under backend/cpp.
|
||||
if (item.backend === "privacy-filter") {
|
||||
return `backend/cpp/privacy-filter/`;
|
||||
}
|
||||
if (!item.lang) {
|
||||
return `backend/python/${item.backend}/`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user