mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
Merge branch 'master' into feat/car
This commit is contained in:
@@ -8,11 +8,11 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
- name: Cache go.mod
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
|
||||
@@ -27,15 +27,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
|
||||
- uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
|
||||
- uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
|
||||
- uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3
|
||||
- uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3
|
||||
- uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
allow-licenses: BSD-2-Clause, BSD-3-Clause, MIT, Apache-2.0, MPL-2.0
|
||||
|
||||
@@ -17,14 +17,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check Cache
|
||||
run: |
|
||||
cat hack/.watch_cache || echo "No cache found"
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24"
|
||||
- name: Run ipsw watch WebKit/WebKit (LOCKDOWN MODE)
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
|
||||
@@ -18,13 +18,13 @@ jobs:
|
||||
run:
|
||||
working-directory: www
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: pnpm build --locale en
|
||||
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: www/build
|
||||
|
||||
|
||||
+13
-13
@@ -3,17 +3,17 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- "*.md"
|
||||
- "www/**"
|
||||
- ".github/workflows/apple-meta.yml"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- ".github/workflows/discord.yml"
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/webkit-meta.yml"
|
||||
- ".github/workflows/winget.yml"
|
||||
- ".goreleaser.yml"
|
||||
- "config.example.yml"
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "cmd/**"
|
||||
- "pkg/**"
|
||||
- "internal/**"
|
||||
- "api/**"
|
||||
- "Makefile"
|
||||
- ".github/workflows/go.yml"
|
||||
- ".github/workflows/release.yml"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
@@ -49,11 +49,11 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.22"
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
GOPROXY=${{ secrets.GOPROXY }} go mod tidy
|
||||
|
||||
- name: Actions Status Discord
|
||||
uses: sarisia/actions-status-discord@5ddd3b114a98457dd80a39b2f00b6a998cd69008 # v1.15.3
|
||||
uses: sarisia/actions-status-discord@11a0bfe3b50977e38aa2bd4a4ebd296415e83c19 # v1.15.4
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.GOPROXY_DISCORD_WEBHOOK }}
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch all tags
|
||||
run: git fetch --force --tags
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
|
||||
@@ -4,74 +4,215 @@ on:
|
||||
schedule:
|
||||
- cron: '0 19 * * *' # daily at 11:00 PST (19:00 UTC)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_update:
|
||||
description: 'Force update even if no new IPSWs detected'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
platforms:
|
||||
description: 'Platforms to check (comma-separated: ios,macos)'
|
||||
required: false
|
||||
default: 'ios,macos'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
update-entitlements-db:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.24'
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Build ipsw CLI
|
||||
run: |
|
||||
go build -o ipsw ./cmd/ipsw
|
||||
|
||||
- name: Determine latest IPSW URL
|
||||
id: get-ipsw
|
||||
- name: Determine latest IPSW URLs
|
||||
id: get-ipsws
|
||||
run: |
|
||||
# Get the latest IPSW URL
|
||||
echo "CURRENT_IPSW_URL=$(./ipsw dl ipsw --device iPhone17,1 --latest --urls)" >> $GITHUB_ENV
|
||||
# Determine which platforms to check
|
||||
PLATFORMS="${{ github.event.inputs.platforms || 'ios,macos' }}"
|
||||
echo "Checking platforms: $PLATFORMS"
|
||||
|
||||
# Function to get latest IPSW URL for a platform
|
||||
get_ipsw_url() {
|
||||
local device="$1"
|
||||
local platform_name="$2"
|
||||
|
||||
# Try to get latest IPSW URL, handle errors gracefully
|
||||
if [ "$platform_name" = "macOS" ]; then
|
||||
# macOS doesn't use device IDs, just get latest macOS IPSW
|
||||
url=$(./ipsw dl ipsw --macos --latest --urls 2>/dev/null | head -1 || echo "")
|
||||
else
|
||||
url=$(./ipsw dl ipsw --device "$device" --latest --urls 2>/dev/null | head -1 || echo "")
|
||||
fi
|
||||
|
||||
if [ -n "$url" ] && [ "$url" != "null" ]; then
|
||||
echo "$url"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialize URLs
|
||||
IOS_URL=""
|
||||
MACOS_URL=""
|
||||
|
||||
# Check each requested platform
|
||||
if echo "$PLATFORMS" | grep -q "ios"; then
|
||||
IOS_URL=$(get_ipsw_url "iPhone17,1" "iOS")
|
||||
fi
|
||||
|
||||
if echo "$PLATFORMS" | grep -q "macos"; then
|
||||
MACOS_URL=$(get_ipsw_url "" "macOS")
|
||||
fi
|
||||
|
||||
# Export URLs to environment
|
||||
echo "IOS_URL=$IOS_URL" >> $GITHUB_ENV
|
||||
echo "MACOS_URL=$MACOS_URL" >> $GITHUB_ENV
|
||||
|
||||
- name: Check for new IPSW
|
||||
id: check-ipsw
|
||||
- name: Check for new IPSWs
|
||||
id: check-ipsws
|
||||
run: |
|
||||
LAST=$(jq -r '.latest_ipsw.url // ""' hack/.watch_cache)
|
||||
echo "Last cached IPSW URL: $LAST"
|
||||
echo "Current IPSW URL: $CURRENT_IPSW_URL"
|
||||
if [ "$LAST" = "$CURRENT_IPSW_URL" ]; then
|
||||
echo "No new IPSW found; skipping"
|
||||
echo "should_update=false" >> $GITHUB_OUTPUT
|
||||
# Get cached URLs
|
||||
LAST_IOS=$(jq -r '.latest_ipsw.ios_url // ""' hack/.watch_cache)
|
||||
LAST_MACOS=$(jq -r '.latest_ipsw.macos_url // ""' hack/.watch_cache)
|
||||
|
||||
echo "=== Cached URLs ==="
|
||||
echo "iOS: $LAST_IOS"
|
||||
echo "macOS: $LAST_MACOS"
|
||||
|
||||
echo "=== Current URLs ==="
|
||||
echo "iOS: $IOS_URL"
|
||||
echo "macOS: $MACOS_URL"
|
||||
|
||||
# Check for changes
|
||||
SHOULD_UPDATE_IOS="false"
|
||||
SHOULD_UPDATE_MACOS="false"
|
||||
SHOULD_UPDATE_ANY="false"
|
||||
|
||||
if [ -n "$IOS_URL" ] && [ "$LAST_IOS" != "$IOS_URL" ]; then
|
||||
echo "iOS IPSW changed: $LAST_IOS -> $IOS_URL"
|
||||
SHOULD_UPDATE_IOS="true"
|
||||
SHOULD_UPDATE_ANY="true"
|
||||
fi
|
||||
|
||||
if [ -n "$MACOS_URL" ] && [ "$LAST_MACOS" != "$MACOS_URL" ]; then
|
||||
echo "macOS IPSW changed: $LAST_MACOS -> $MACOS_URL"
|
||||
SHOULD_UPDATE_MACOS="true"
|
||||
SHOULD_UPDATE_ANY="true"
|
||||
fi
|
||||
|
||||
# Force update if requested
|
||||
if [ "${{ github.event.inputs.force_update }}" = "true" ]; then
|
||||
echo "Force update requested"
|
||||
SHOULD_UPDATE_ANY="true"
|
||||
if [ -n "$IOS_URL" ]; then SHOULD_UPDATE_IOS="true"; fi
|
||||
if [ -n "$MACOS_URL" ]; then SHOULD_UPDATE_MACOS="true"; fi
|
||||
fi
|
||||
|
||||
# Export update flags
|
||||
echo "should_update_ios=$SHOULD_UPDATE_IOS" >> $GITHUB_OUTPUT
|
||||
echo "should_update_macos=$SHOULD_UPDATE_MACOS" >> $GITHUB_OUTPUT
|
||||
echo "should_update_any=$SHOULD_UPDATE_ANY" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$SHOULD_UPDATE_ANY" = "true" ]; then
|
||||
echo "Will proceed with database updates"
|
||||
else
|
||||
echo "New IPSW found; proceeding with update"
|
||||
echo "should_update=true" >> $GITHUB_OUTPUT
|
||||
echo "No new IPSWs found; skipping updates"
|
||||
fi
|
||||
|
||||
- name: Download IPSW
|
||||
if: steps.check-ipsw.outputs.should_update == 'true'
|
||||
- name: Download IPSWs
|
||||
if: steps.check-ipsws.outputs.should_update_any == 'true'
|
||||
run: |
|
||||
echo "Downloading IPSW: $CURRENT_IPSW_URL"
|
||||
curl -L "$CURRENT_IPSW_URL" -o latest.ipsw
|
||||
echo "IPSW downloaded: $(ls -lh latest.ipsw)"
|
||||
echo "Downloading new IPSWs..."
|
||||
|
||||
# Download iOS IPSW
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ] && [ -n "$IOS_URL" ]; then
|
||||
echo "Downloading iOS IPSW: $IOS_URL"
|
||||
curl -L "$IOS_URL" -o ios_latest.ipsw
|
||||
echo "iOS IPSW downloaded: $(ls -lh ios_latest.ipsw)"
|
||||
fi
|
||||
|
||||
# Download macOS IPSW
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ] && [ -n "$MACOS_URL" ]; then
|
||||
echo "Downloading macOS IPSW: $MACOS_URL"
|
||||
curl -L "$MACOS_URL" -o macos_latest.ipsw
|
||||
echo "macOS IPSW downloaded: $(ls -lh macos_latest.ipsw)"
|
||||
fi
|
||||
|
||||
- name: Update entitlements database
|
||||
if: steps.check-ipsw.outputs.should_update == 'true'
|
||||
if: steps.check-ipsws.outputs.should_update_any == 'true'
|
||||
run: |
|
||||
echo "Updating Supabase entitlements database..."
|
||||
./ipsw ent \
|
||||
--pg-host ${{ secrets.SUPABASE_HOST }} \
|
||||
--pg-port 6543 \
|
||||
--pg-user postgres \
|
||||
--pg-password "${{ secrets.SUPABASE_PASSWORD }}" \
|
||||
--pg-database postgres \
|
||||
--pg-sslmode require \
|
||||
--ipsw latest.ipsw
|
||||
echo "Database update completed successfully"
|
||||
echo "Updating Supabase entitlements database with replacement support..."
|
||||
|
||||
# Function to update database for a platform
|
||||
update_platform() {
|
||||
local platform="$1"
|
||||
local ipsw_file="$2"
|
||||
|
||||
if [ -f "$ipsw_file" ]; then
|
||||
echo "Processing $platform IPSW: $ipsw_file"
|
||||
./ipsw ent --ipsw "$ipsw_file" --replace \
|
||||
--pg-host "${{ secrets.SUPABASE_HOST }}" \
|
||||
--pg-port 5432 \
|
||||
--pg-user "${{ secrets.SUPABASE_USER }}" \
|
||||
--pg-password "${{ secrets.SUPABASE_PASSWORD }}" \
|
||||
--pg-database postgres \
|
||||
--pg-sslmode require \
|
||||
--pg-poolmode session
|
||||
echo "$platform database update completed successfully"
|
||||
else
|
||||
echo "Skipping $platform (no IPSW file: $ipsw_file)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Process each platform that needs updating
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ]; then
|
||||
update_platform "iOS" "ios_latest.ipsw"
|
||||
fi
|
||||
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ]; then
|
||||
update_platform "macOS" "macos_latest.ipsw"
|
||||
fi
|
||||
|
||||
echo "All database updates completed successfully"
|
||||
|
||||
- name: Update cache and commit
|
||||
if: steps.check-ipsw.outputs.should_update == 'true'
|
||||
if: steps.check-ipsws.outputs.should_update_any == 'true'
|
||||
run: |
|
||||
# Update the cache file with the new IPSW URL
|
||||
jq --arg url "$CURRENT_IPSW_URL" '.latest_ipsw = {"url": $url}' hack/.watch_cache > hack/.watch_cache.tmp
|
||||
# Update the cache file with new IPSW URLs
|
||||
echo "Updating cache with new IPSW URLs..."
|
||||
|
||||
# Create temporary cache with current URLs
|
||||
jq --arg ios_url "$IOS_URL" \
|
||||
--arg macos_url "$MACOS_URL" \
|
||||
'.latest_ipsw = {
|
||||
"ios_url": (if $ios_url != "" then $ios_url else .latest_ipsw.ios_url // "" end),
|
||||
"macos_url": (if $macos_url != "" then $macos_url else .latest_ipsw.macos_url // "" end),
|
||||
"url": (if $ios_url != "" then $ios_url else .latest_ipsw.url // "" end)
|
||||
}' hack/.watch_cache > hack/.watch_cache.tmp
|
||||
mv hack/.watch_cache.tmp hack/.watch_cache
|
||||
|
||||
# Configure git
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Commit only the cache file (no more SQLite DB to commit)
|
||||
# Create commit message with updated platforms
|
||||
UPDATED_PLATFORMS=""
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ]; then
|
||||
UPDATED_PLATFORMS="${UPDATED_PLATFORMS}iOS "
|
||||
fi
|
||||
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ]; then
|
||||
UPDATED_PLATFORMS="${UPDATED_PLATFORMS}macOS "
|
||||
fi
|
||||
|
||||
COMMIT_MSG="chore(ents): update entitlements DB for ${UPDATED_PLATFORMS}[skip ci]"
|
||||
|
||||
# Commit cache file changes
|
||||
git add hack/.watch_cache
|
||||
git commit -m "chore(ents): update entitlements DB to $CURRENT_IPSW_URL [skip ci]" || echo "No changes to commit"
|
||||
git commit -m "$COMMIT_MSG" || echo "No changes to commit"
|
||||
git push
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
update-all-fcs-keys:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.24'
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
- name: Cache go.mod
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Local cache
|
||||
.gocache
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
@@ -25,6 +28,7 @@ __debug_*
|
||||
CLAUDE*.md
|
||||
.gemini
|
||||
GEMINI*.md
|
||||
AGENTS.md
|
||||
|
||||
# Go Work
|
||||
*.work
|
||||
@@ -75,6 +79,7 @@ internal/commands/ida/dscu/data/dscu.py
|
||||
internal/download/data/proto
|
||||
venv/
|
||||
keybag.plist
|
||||
OPC/
|
||||
|
||||
# Unicorn EMU
|
||||
unicorn2/
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ builds:
|
||||
- arm64
|
||||
tags:
|
||||
- libusb
|
||||
- unicorn
|
||||
# - unicorn
|
||||
- objc
|
||||
- sandbox
|
||||
- wallpaper
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
####################################################
|
||||
# GOLANG BUILDER
|
||||
####################################################
|
||||
FROM golang:1.24 AS builder
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
ARG VERSION
|
||||
ARG COMMIT
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
####################################################
|
||||
# GOLANG BUILDER
|
||||
####################################################
|
||||
FROM golang:1.24 AS builder
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
ARG VERSION
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ func getFsFiles(pemDB string) gin.HandlerFunc {
|
||||
pemDbPath = filepath.Clean(pemDB)
|
||||
}
|
||||
}
|
||||
|
||||
mountPointParam, _ := c.GetQuery("mount_point")
|
||||
if mountPointParam != "" {
|
||||
mountPointParam = filepath.Clean(mountPointParam)
|
||||
}
|
||||
|
||||
i, err := info.Parse(ipswPath)
|
||||
if err != nil {
|
||||
@@ -99,7 +104,7 @@ func getFsFiles(pemDB string) gin.HandlerFunc {
|
||||
|
||||
// mount filesystem DMG
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, mountPointParam)
|
||||
if err != nil {
|
||||
if !errors.Is(err, utils.ErrMountResourceBusy) {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, types.GenericError{Error: fmt.Sprintf("failed to mount DMG: %v", err)})
|
||||
|
||||
@@ -50,6 +50,11 @@ func AddRoutes(rg *gin.RouterGroup, pemDB string) {
|
||||
// description: path to AEA pem DB JSON file
|
||||
// required: false
|
||||
// type: string
|
||||
// + name: mount_point
|
||||
// in: query
|
||||
// description: custom mount point path
|
||||
// required: false
|
||||
// type: string
|
||||
// Responses:
|
||||
// 500: genericError
|
||||
// 200: mountReponse
|
||||
@@ -69,12 +74,18 @@ func AddRoutes(rg *gin.RouterGroup, pemDB string) {
|
||||
pemDbPath = filepath.Clean(pemDB)
|
||||
}
|
||||
}
|
||||
|
||||
mountPointParam, _ := c.GetQuery("mount_point")
|
||||
if mountPointParam != "" {
|
||||
mountPointParam = filepath.Clean(mountPointParam)
|
||||
}
|
||||
|
||||
dmgType := c.Param("type")
|
||||
if !slices.Contains([]string{"app", "sys", "fs"}, dmgType) {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid dmg type: must be app, sys, or fs"})
|
||||
return
|
||||
}
|
||||
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath, nil)
|
||||
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath, nil, mountPointParam)
|
||||
if err != nil {
|
||||
if errors.Unwrap(err) == info.ErrorCryptexNotFound {
|
||||
c.AbortWithError(http.StatusNotFound, err)
|
||||
|
||||
+43
-4
@@ -51,6 +51,7 @@ func init() {
|
||||
entCmd.Flags().String("pg-password", "", "PostgreSQL password")
|
||||
entCmd.Flags().String("pg-database", "", "PostgreSQL database name")
|
||||
entCmd.Flags().String("pg-sslmode", "require", "PostgreSQL SSL mode (disable, require, verify-ca, verify-full)")
|
||||
entCmd.Flags().String("pg-poolmode", "", "PostgreSQL pool mode (session, transaction, statement, or empty for no pooling)")
|
||||
|
||||
// Search flags
|
||||
entCmd.Flags().StringP("key", "k", "", "Search for entitlement key pattern")
|
||||
@@ -63,6 +64,11 @@ func init() {
|
||||
entCmd.Flags().Bool("stats", false, "Show database statistics")
|
||||
entCmd.Flags().Int("limit", 100, "Limit number of results")
|
||||
|
||||
// Replacement flags
|
||||
entCmd.Flags().Bool("replace", false, "Replace older builds of the same iOS version with newer builds")
|
||||
entCmd.Flags().String("replace-strategy", "auto", "Replacement strategy: auto, prompt, force")
|
||||
entCmd.Flags().Bool("dry-run", false, "Show what would be replaced without making changes")
|
||||
|
||||
// Viper bindings
|
||||
viper.BindPFlag("ent.ipsw", entCmd.Flags().Lookup("ipsw"))
|
||||
viper.BindPFlag("ent.input", entCmd.Flags().Lookup("input"))
|
||||
@@ -73,6 +79,7 @@ func init() {
|
||||
viper.BindPFlag("ent.pg-password", entCmd.Flags().Lookup("pg-password"))
|
||||
viper.BindPFlag("ent.pg-database", entCmd.Flags().Lookup("pg-database"))
|
||||
viper.BindPFlag("ent.pg-sslmode", entCmd.Flags().Lookup("pg-sslmode"))
|
||||
viper.BindPFlag("ent.pg-poolmode", entCmd.Flags().Lookup("pg-poolmode"))
|
||||
viper.BindPFlag("ent.key", entCmd.Flags().Lookup("key"))
|
||||
viper.BindPFlag("ent.value", entCmd.Flags().Lookup("value"))
|
||||
viper.BindPFlag("ent.file", entCmd.Flags().Lookup("file"))
|
||||
@@ -80,6 +87,9 @@ func init() {
|
||||
viper.BindPFlag("ent.file-only", entCmd.Flags().Lookup("file-only"))
|
||||
viper.BindPFlag("ent.stats", entCmd.Flags().Lookup("stats"))
|
||||
viper.BindPFlag("ent.limit", entCmd.Flags().Lookup("limit"))
|
||||
viper.BindPFlag("ent.replace", entCmd.Flags().Lookup("replace"))
|
||||
viper.BindPFlag("ent.replace-strategy", entCmd.Flags().Lookup("replace-strategy"))
|
||||
viper.BindPFlag("ent.dry-run", entCmd.Flags().Lookup("dry-run"))
|
||||
|
||||
}
|
||||
|
||||
@@ -113,7 +123,13 @@ var entCmd = &cobra.Command{
|
||||
❯ ipsw ent --sqlite entitlements.db --stats
|
||||
|
||||
# Search PostgreSQL database (Supabase)
|
||||
❯ ipsw ent --pg-host db.xyz.supabase.co --pg-user postgres --pg-password your-password --pg-database postgres --key sandbox`),
|
||||
❯ ipsw ent --pg-host db.xyz.supabase.co --pg-user postgres --pg-password your-password --pg-database postgres --key sandbox
|
||||
|
||||
# Replace older iOS builds with newer ones
|
||||
❯ ipsw ent --sqlite entitlements.db --ipsw iPhone16,1_26.0_22G87_Restore.ipsw --replace
|
||||
|
||||
# Preview what would be replaced
|
||||
❯ ipsw ent --sqlite entitlements.db --ipsw iPhone16,1_26.0_22G87_Restore.ipsw --replace --dry-run`),
|
||||
Args: cobra.NoArgs,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -131,6 +147,7 @@ var entCmd = &cobra.Command{
|
||||
pgPassword := viper.GetString("ent.pg-password")
|
||||
pgDatabase := viper.GetString("ent.pg-database")
|
||||
pgSSLMode := viper.GetString("ent.pg-sslmode")
|
||||
pgPoolMode := viper.GetString("ent.pg-poolmode")
|
||||
keyPattern := viper.GetString("ent.key")
|
||||
valuePattern := viper.GetString("ent.value")
|
||||
filePattern := viper.GetString("ent.file")
|
||||
@@ -138,6 +155,9 @@ var entCmd = &cobra.Command{
|
||||
fileOnly := viper.GetBool("ent.file-only")
|
||||
showStats := viper.GetBool("ent.stats")
|
||||
limit := viper.GetInt("ent.limit")
|
||||
replace := viper.GetBool("ent.replace")
|
||||
replaceStrategy := viper.GetString("ent.replace-strategy")
|
||||
dryRun := viper.GetBool("ent.dry-run")
|
||||
|
||||
// Validate required flags
|
||||
if sqliteDB == "" && pgHost == "" {
|
||||
@@ -171,6 +191,19 @@ var entCmd = &cobra.Command{
|
||||
return fmt.Errorf("--key, --value, --file, and --stats are mutually exclusive")
|
||||
}
|
||||
|
||||
// Validate replacement flags
|
||||
if replace && (keyPattern != "" || valuePattern != "" || filePattern != "" || showStats) {
|
||||
return fmt.Errorf("--replace cannot be used with search operations")
|
||||
}
|
||||
|
||||
if replaceStrategy != "auto" && replaceStrategy != "prompt" && replaceStrategy != "force" {
|
||||
return fmt.Errorf("--replace-strategy must be one of: auto, prompt, force")
|
||||
}
|
||||
|
||||
if dryRun && !replace {
|
||||
return fmt.Errorf("--dry-run can only be used with --replace")
|
||||
}
|
||||
|
||||
// Validate PostgreSQL flags if using PostgreSQL
|
||||
if pgHost != "" {
|
||||
if pgUser == "" || pgDatabase == "" {
|
||||
@@ -190,7 +223,13 @@ var entCmd = &cobra.Command{
|
||||
// Handle database creation
|
||||
if len(ipsws) > 0 || len(inputs) > 0 {
|
||||
if pgHost != "" {
|
||||
return ent.CreatePostgreSQLDatabase(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, ipsws, inputs)
|
||||
if replace {
|
||||
return ent.CreatePostgreSQLDatabaseWithReplacement(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, ipsws, inputs, replaceStrategy, dryRun)
|
||||
}
|
||||
return ent.CreatePostgreSQLDatabase(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, ipsws, inputs)
|
||||
}
|
||||
if replace {
|
||||
return ent.CreateSQLiteDatabaseWithReplacement(sqliteDB, ipsws, inputs, replaceStrategy, dryRun)
|
||||
}
|
||||
return ent.CreateSQLiteDatabase(sqliteDB, ipsws, inputs)
|
||||
}
|
||||
@@ -199,14 +238,14 @@ var entCmd = &cobra.Command{
|
||||
|
||||
if showStats {
|
||||
if pgHost != "" {
|
||||
return ent.ShowPostgreSQLStatistics(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode)
|
||||
return ent.ShowPostgreSQLStatistics(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode)
|
||||
}
|
||||
return ent.ShowSQLiteStatistics(sqliteDB)
|
||||
}
|
||||
|
||||
// Perform search
|
||||
if pgHost != "" {
|
||||
return ent.SearchPostgreSQLEntitlements(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
|
||||
return ent.SearchPostgreSQLEntitlements(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
|
||||
}
|
||||
return ent.SearchSQLiteEntitlements(sqliteDB, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
|
||||
},
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ var iaCmd = &cobra.Command{
|
||||
dmgPath := filepath.Join(outDir, "SharedSupport.dmg")
|
||||
|
||||
log.Debugf("Mounting %s", dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ var mdevsCmd = &cobra.Command{
|
||||
}
|
||||
// mount filesystem DMG
|
||||
log.Debugf("Mounting %s", dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@ func init() {
|
||||
mountCmd.Flags().StringP("key", "k", "", "DMG key")
|
||||
mountCmd.Flags().Bool("lookup", false, "Lookup DMG keys on theapplewiki.com")
|
||||
mountCmd.Flags().String("pem-db", "", "AEA pem DB JSON file")
|
||||
mountCmd.Flags().StringP("mount-point", "m", "", "Custom mount point (default: /tmp/<dmg>.mount)")
|
||||
viper.BindPFlag("mount.key", mountCmd.Flags().Lookup("key"))
|
||||
viper.BindPFlag("mount.lookup", mountCmd.Flags().Lookup("lookup"))
|
||||
viper.BindPFlag("mount.pem-db", mountCmd.Flags().Lookup("pem-db"))
|
||||
viper.BindPFlag("mount.mount-point", mountCmd.Flags().Lookup("mount-point"))
|
||||
}
|
||||
|
||||
// mountCmd represents the mount command
|
||||
@@ -69,6 +71,9 @@ var mountCmd = &cobra.Command{
|
||||
|
||||
# Mount dyld shared cache (exc) DMG with AEA pem DB
|
||||
$ ipsw mount exc iPhone.ipsw --pem-db /path/to/pem.json
|
||||
|
||||
# Mount to a custom mount point
|
||||
$ ipsw mount fs iPhone.ipsw --mount-point /mnt/ios-filesystem
|
||||
`),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
@@ -86,6 +91,7 @@ var mountCmd = &cobra.Command{
|
||||
key := viper.GetString("mount.key")
|
||||
lookupKeys := viper.GetBool("mount.lookup")
|
||||
pemDB := viper.GetString("mount.pem-db")
|
||||
mountPoint := viper.GetString("mount.mount-point")
|
||||
// validate flags
|
||||
if len(key) > 0 && lookupKeys {
|
||||
return fmt.Errorf("cannot use --key AND --lookup flags together")
|
||||
@@ -129,7 +135,7 @@ var mountCmd = &cobra.Command{
|
||||
keys = key
|
||||
}
|
||||
|
||||
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB, keys)
|
||||
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB, keys, mountPoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ func init() {
|
||||
otaExtractCmd.Flags().StringP("range", "r", "", "Regex pattern control the payloadv2 file range to search")
|
||||
otaExtractCmd.Flags().BoolP("confirm", "y", false, "Confirm searching for pattern in payloadv2 files")
|
||||
otaExtractCmd.Flags().BoolP("decomp", "x", false, "Decompress pbzx files")
|
||||
otaExtractCmd.Flags().BoolP("flat", "f", false, "Do NOT preserve directory structure when extracting")
|
||||
otaExtractCmd.Flags().StringP("output", "o", "", "Output folder")
|
||||
otaExtractCmd.MarkFlagDirname("output")
|
||||
viper.BindPFlag("ota.extract.cryptex", otaExtractCmd.Flags().Lookup("cryptex"))
|
||||
@@ -64,6 +65,7 @@ func init() {
|
||||
viper.BindPFlag("ota.extract.range", otaExtractCmd.Flags().Lookup("range"))
|
||||
viper.BindPFlag("ota.extract.confirm", otaExtractCmd.Flags().Lookup("confirm"))
|
||||
viper.BindPFlag("ota.extract.decomp", otaExtractCmd.Flags().Lookup("decomp"))
|
||||
viper.BindPFlag("ota.extract.flat", otaExtractCmd.Flags().Lookup("flat"))
|
||||
viper.BindPFlag("ota.extract.output", otaExtractCmd.Flags().Lookup("output"))
|
||||
}
|
||||
|
||||
@@ -79,11 +81,12 @@ var otaExtractCmd = &cobra.Command{
|
||||
// flags
|
||||
decomp := viper.GetBool("ota.extract.decomp")
|
||||
cryptex := viper.GetString("ota.extract.cryptex")
|
||||
flat := viper.GetBool("ota.extract.flat")
|
||||
// validate flags
|
||||
if len(args) > 1 && viper.IsSet("ota.extract.pattern") {
|
||||
if len(args) > 1 && viper.GetString("ota.extract.pattern") != "" {
|
||||
return fmt.Errorf("cannot use both FILENAME and flag for --pattern")
|
||||
}
|
||||
if viper.IsSet("ota.extract.cryptex") && !slices.Contains(validCryptexes, cryptex) {
|
||||
if viper.GetString("ota.extract.cryptex") != "" && !slices.Contains(validCryptexes, cryptex) {
|
||||
return fmt.Errorf("invalid --cryptex: '%s' (must be one of: %s)", cryptex, strings.Join(validCryptexes, ", "))
|
||||
}
|
||||
|
||||
@@ -101,17 +104,17 @@ var otaExtractCmd = &cobra.Command{
|
||||
return fmt.Errorf("failed to get OTA folder: %v", err)
|
||||
}
|
||||
|
||||
if viper.IsSet("ota.extract.output") {
|
||||
if viper.GetString("ota.extract.output") != "" {
|
||||
output = filepath.Join(viper.GetString("ota.extract.output"), output)
|
||||
if err := os.MkdirAll(output, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.IsSet("ota.extract.cryptex") || viper.GetBool("ota.extract.dyld") || viper.GetBool("ota.extract.kernel") || viper.IsSet("ota.extract.pattern") {
|
||||
if viper.GetString("ota.extract.cryptex") != "" || viper.GetBool("ota.extract.dyld") || viper.GetBool("ota.extract.kernel") || viper.GetString("ota.extract.pattern") != "" {
|
||||
cwd, _ := os.Getwd()
|
||||
/* CRYPTEX */
|
||||
if viper.IsSet("ota.extract.cryptex") {
|
||||
if viper.GetString("ota.extract.cryptex") != "" {
|
||||
log.Infof("Extracting %s Cryptex", cryptex)
|
||||
out, err := o.ExtractCryptex(cryptex, output)
|
||||
if err != nil {
|
||||
@@ -147,10 +150,10 @@ var otaExtractCmd = &cobra.Command{
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if re.MatchString(f.Path()) {
|
||||
ff, err := o.Open(f.Path(), false)
|
||||
if re.MatchString(f.Name()) {
|
||||
ff, err := o.Open(f.Name(), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
|
||||
}
|
||||
data, err := io.ReadAll(ff)
|
||||
if err != nil {
|
||||
@@ -165,6 +168,9 @@ var otaExtractCmd = &cobra.Command{
|
||||
return fmt.Errorf("failed to parse kernelcache compressed data: %v", err)
|
||||
}
|
||||
fname := filepath.Join(output, f.Name())
|
||||
if flat {
|
||||
fname = filepath.Join(output, filepath.Base(f.Name()))
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %v", err)
|
||||
}
|
||||
@@ -180,7 +186,7 @@ var otaExtractCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
/* PATTERN */
|
||||
if viper.IsSet("ota.extract.pattern") {
|
||||
if viper.GetString("ota.extract.pattern") != "" {
|
||||
re, err := regexp.Compile(viper.GetString("ota.extract.pattern"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regex pattern '%s': %v", viper.GetString("ota.extract.pattern"), err)
|
||||
@@ -190,12 +196,15 @@ var otaExtractCmd = &cobra.Command{
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if re.MatchString(f.Path()) {
|
||||
ff, err := o.Open(f.Path(), decomp)
|
||||
if re.MatchString(f.Name()) {
|
||||
ff, err := o.Open(f.Name(), decomp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
|
||||
}
|
||||
fname := filepath.Join(output, f.Name())
|
||||
if flat {
|
||||
fname = filepath.Join(output, filepath.Base(f.Name()))
|
||||
}
|
||||
fname := filepath.Join(output, f.Path())
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %v", err)
|
||||
}
|
||||
@@ -215,8 +224,8 @@ var otaExtractCmd = &cobra.Command{
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if re.MatchString(f.Name()) {
|
||||
utils.Indent(log.Warn, 2)(fmt.Sprintf("Found '%s' in post.bom (most likely in payloadv2 files)", f.Name()))
|
||||
if re.MatchString(filepath.Base(f.Name())) {
|
||||
utils.Indent(log.Warn, 2)(fmt.Sprintf("Found '%s' in post.bom (most likely in payloadv2 files)", filepath.Base(f.Name())))
|
||||
bomFound = true
|
||||
}
|
||||
}
|
||||
@@ -242,20 +251,23 @@ var otaExtractCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
/* ALL FILES */
|
||||
if len(args) == 1 && !viper.IsSet("ota.extract.pattern") {
|
||||
if len(args) == 1 && viper.GetString("ota.extract.pattern") == "" {
|
||||
log.Info("Extracting All Files From OTA")
|
||||
for _, f := range o.Files() {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fname := filepath.Join(output, f.Path())
|
||||
fname := filepath.Join(output, f.Name())
|
||||
if flat {
|
||||
fname = filepath.Join(output, filepath.Base(f.Name()))
|
||||
}
|
||||
if _, err := os.Stat(fname); err == nil {
|
||||
log.Warnf("already exists: '%s' ", fname)
|
||||
continue
|
||||
}
|
||||
ff, err := o.Open(f.Path(), decomp)
|
||||
ff, err := o.Open(f.Name(), decomp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
|
||||
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %v", err)
|
||||
|
||||
@@ -89,7 +89,7 @@ var otaLsCmd = &cobra.Command{
|
||||
fmt.Fprintf(w, "- [ OTA ASSETS FILES ] %s\n\n", strings.Repeat("-", 50))
|
||||
for _, f := range ota.Files() {
|
||||
if !f.IsDir() {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", colorMode(f.Mode()), colorModTime(f.ModTime().Format(time.RFC3339)), colorSize(humanize.Bytes(uint64(f.Size()))), colorName(f.Path()))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", colorMode(f.Mode()), colorModTime(f.ModTime().Format(time.RFC3339)), colorSize(humanize.Bytes(uint64(f.Size()))), colorName(f.Name()))
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
@@ -126,7 +126,7 @@ var sbDiffCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting FS %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ var wpCmd = &cobra.Command{
|
||||
pemDB := viper.GetString("wp.pem-db")
|
||||
// output := viper.GetString("wp.output")
|
||||
|
||||
ctx, err := mount.DmgInIPSW(filepath.Clean(args[0]), "fs", pemDB, nil)
|
||||
ctx, err := mount.DmgInIPSW(filepath.Clean(args[0]), "fs", pemDB, nil, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/PuerkitoBio/goquery v1.10.3
|
||||
github.com/alecthomas/chroma/v2 v2.19.0
|
||||
github.com/anthropics/anthropic-sdk-go v1.6.2
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/anthropics/anthropic-sdk-go v1.12.0
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/aymanbagabas/go-udiff v0.3.1
|
||||
github.com/blacktop/arm64-cgo v1.0.58
|
||||
github.com/blacktop/arm64-cgo v1.0.59
|
||||
github.com/blacktop/go-apfs v1.0.27
|
||||
github.com/blacktop/go-dwarf v1.0.14
|
||||
github.com/blacktop/go-macho v1.1.249
|
||||
@@ -33,11 +33,11 @@ require (
|
||||
github.com/caarlos0/ctrlc v1.2.0
|
||||
github.com/caarlos0/env/v8 v8.0.0
|
||||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.6
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/cloudflare/circl v1.6.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/docker/docker v28.3.3+incompatible
|
||||
github.com/docker/docker v28.4.0+incompatible
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fatih/color v1.18.0
|
||||
@@ -45,13 +45,13 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
|
||||
github.com/gen2brain/beeep v0.11.1
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||
github.com/gocolly/colly/v2 v2.2.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
|
||||
github.com/google/gousb v1.1.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
@@ -59,34 +59,34 @@ require (
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/mattn/go-mastodon v0.0.10
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ollama/ollama v0.9.6
|
||||
github.com/openai/openai-go v1.11.1
|
||||
github.com/ollama/ollama v0.12.0
|
||||
github.com/openai/openai-go v1.12.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
|
||||
github.com/spf13/cast v1.9.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15
|
||||
github.com/twmb/murmur3 v1.1.8
|
||||
github.com/ulikunitz/xz v0.6.0-alpha.3
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c
|
||||
github.com/vbauerster/mpb/v8 v8.10.2
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.34.0
|
||||
golang.org/x/term v0.33.0
|
||||
google.golang.org/genai v1.17.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/oauth2 v0.31.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/term v0.35.0
|
||||
google.golang.org/genai v1.25.0
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.1
|
||||
gorm.io/gorm v1.31.0
|
||||
)
|
||||
|
||||
// replace github.com/blacktop/go-macho => ../go-macho
|
||||
@@ -120,11 +120,11 @@ require (
|
||||
github.com/caarlos0/go-version v0.2.1 // indirect
|
||||
github.com/caarlos0/svu/v3 v3.2.3 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/mosaic v0.0.0-20250720010745-3615766e35a0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
@@ -154,9 +154,9 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
@@ -205,10 +205,12 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
||||
@@ -216,8 +218,8 @@ require (
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/soniakeys/quant v1.0.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cobra-cli v1.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||
@@ -240,12 +242,13 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
|
||||
|
||||
@@ -87,10 +87,10 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
|
||||
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -106,8 +106,8 @@ github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fus
|
||||
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4=
|
||||
github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/anthropics/anthropic-sdk-go v1.6.2 h1:oORA212y0/zAxe7OPvdgIbflnn/x5PGk5uwjF60GqXM=
|
||||
github.com/anthropics/anthropic-sdk-go v1.6.2/go.mod h1:3qSNQ5NrAmjC8A2ykuruSQttfqfdEYNZY5o8c0XSHB8=
|
||||
github.com/anthropics/anthropic-sdk-go v1.12.0 h1:xPqlGnq7rWrTiHazIvCiumA0u7mGQnwDQtvA1M82h9U=
|
||||
github.com/anthropics/anthropic-sdk-go v1.12.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
|
||||
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
|
||||
@@ -138,8 +138,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blacktop/arm64-cgo v1.0.58 h1:ANTB5JUcLzFfsKxNsp0obn/lyvLMIBa1tKiNCAc0iMw=
|
||||
github.com/blacktop/arm64-cgo v1.0.58/go.mod h1:3QC9E3mn9o8Xxf8fILONjGOC9Ss/OgutkYnmNnsu+X4=
|
||||
github.com/blacktop/arm64-cgo v1.0.59 h1:svSvVGqvYvhs18qYXDHlduCEZKgWyB5ys/sUmjaleOo=
|
||||
github.com/blacktop/arm64-cgo v1.0.59/go.mod h1:IrNNim89QQyCwVlUbFvz27DCEmy1xVWIblYIu7KjD20=
|
||||
github.com/blacktop/go-apfs v1.0.27 h1:+noNImiLcTaxzDMpOsEwXOxLwgnSMZdcJH+YMNHZsbo=
|
||||
github.com/blacktop/go-apfs v1.0.27/go.mod h1:2IvPE0B/wnekB5QoNXJrDzlTrPnfCQNxTfZaKu4zbYM=
|
||||
github.com/blacktop/go-dwarf v1.0.14 h1:OjmzfSgg/qAKckn2tWFebcgKgJ7HOqCj7bS+CiE1lrY=
|
||||
@@ -164,7 +164,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk=
|
||||
@@ -184,14 +183,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
||||
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
@@ -208,9 +207,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -250,8 +248,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -305,8 +303,8 @@ github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9C
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
@@ -350,6 +348,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/gocolly/colly/v2 v2.2.0 h1:FQGxcqvTdFAvOpMRhk52o20Qsf6KtRU5HSf0bITS38I=
|
||||
github.com/gocolly/colly/v2 v2.2.0/go.mod h1:YOQwv1ofoQOzJiELnkThDd6ObOfl6odUk2i6Czbx3Ws=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||
@@ -358,10 +358,9 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -398,8 +397,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -545,10 +544,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -647,14 +644,14 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nlnwa/whatwg-url v0.6.2 h1:jU61lU2ig4LANydbEJmA2nPrtCGiKdtgT0rmMd2VZ/Q=
|
||||
github.com/nlnwa/whatwg-url v0.6.2/go.mod h1:x0FPXJzzOEieQtsBT/AKvbiBbQ46YlL6Xa7m02M1ECk=
|
||||
github.com/ollama/ollama v0.9.6 h1:HZNJmB52pMt6zLkGkkheBuXBXM5478eiSAj7GR75AMc=
|
||||
github.com/ollama/ollama v0.9.6/go.mod h1:zLwx3iZ3AI4Rc/egsrx3u1w4RU2MHQ/Ylxse48jvyt4=
|
||||
github.com/ollama/ollama v0.12.0 h1:BRry7G2Skz7Mu+E6rz40tzBXNbLTEhheGT8umc1zvxo=
|
||||
github.com/ollama/ollama v0.12.0/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/openai/openai-go v1.11.1 h1:fTQ4Sr9eoRiWFAoHzXiZZpVi6KtLeoTMyGrcOCudjNU=
|
||||
github.com/openai/openai-go v1.11.1/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0=
|
||||
github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -688,6 +685,10 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
@@ -703,8 +704,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
@@ -733,30 +734,30 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h
|
||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
|
||||
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra-cli v1.3.0 h1:Y/qy0X40kDT+k7PCyBQrsjh/qOf9t/ZVScbn0OyZD84=
|
||||
github.com/spf13/cobra-cli v1.3.0/go.mod h1:zq1KeHo/9SQm1tNdbJhwVDd9bVpokbQwuG6MR0TFCdE=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -773,8 +774,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15 h1:aFa2PvtQulG5uVQ8adH84JCwRZ2rjiZnRUU/mWxJRG8=
|
||||
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15/go.mod h1:ZW0m/zWIvFqFSpPdiWRje8xdwyWJqt3Cnt6bVlDti8g=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
@@ -814,8 +815,8 @@ github.com/ulikunitz/lz v0.4.1 h1:H4rI9BePvqjmfmZgNZ6I8SEw+GkGNIFEUJnTxY9RHRU=
|
||||
github.com/ulikunitz/lz v0.4.1/go.mod h1:kOcqmi51jCj0bKudgng90P1ho4uyQLKzkGaoPBhS+pE=
|
||||
github.com/ulikunitz/xz v0.6.0-alpha.3 h1:7IGVhjpaTpgeNBUPQcbtooPhFXDgeGxzhFki5B8E2FY=
|
||||
github.com/ulikunitz/xz v0.6.0-alpha.3/go.mod h1:ndJU5gy7q0Ffe1saQwmPKbMhIhJbwdu4+3t8eTxRF5U=
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420 h1:ITiFpOQk2X3lZJ79r9yTnV5z/INjDryB9f+56u5VxIw=
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420/go.mod h1:mcHBrigWSHlMZYol9QOFnK7sbltIt/OaKP5CQBZsC+4=
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c h1:qdw+wGIifeKs9EDiAVTQNcsLqKxdjlJCOAMFfP9RXfY=
|
||||
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c/go.mod h1:mcHBrigWSHlMZYol9QOFnK7sbltIt/OaKP5CQBZsC+4=
|
||||
github.com/vbauerster/mpb/v7 v7.5.3 h1:BkGfmb6nMrrBQDFECR/Q7RkKCw7ylMetCb4079CGs4w=
|
||||
github.com/vbauerster/mpb/v7 v7.5.3/go.mod h1:i+h4QY6lmLvBNK2ah1fSreiw3ajskRlBp9AhY/PnuOE=
|
||||
github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
|
||||
@@ -866,12 +867,14 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -890,8 +893,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -902,8 +905,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -938,8 +941,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -991,8 +994,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1010,8 +1013,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1029,8 +1032,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1113,8 +1116,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1125,8 +1128,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1144,8 +1147,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1209,8 +1212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1257,8 +1260,8 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genai v1.17.0 h1:lXYSnWShPYjxTouxRj0zF8RsNmSF+SKo7SQ7dM35NlI=
|
||||
google.golang.org/genai v1.17.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
|
||||
google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE=
|
||||
google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -1370,8 +1373,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1399,8 +1402,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1436,7 +1439,6 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
+38
-26
@@ -30,7 +30,15 @@
|
||||
"webkitgtk-2.49.4",
|
||||
"wpewebkit-2.49.4",
|
||||
"webkitgtk-2.48.5",
|
||||
"wpewebkit-2.48.5"
|
||||
"wpewebkit-2.48.5",
|
||||
"webkitgtk-2.49.90",
|
||||
"wpewebkit-2.49.90",
|
||||
"webkitgtk-2.48.6",
|
||||
"wpewebkit-2.48.6",
|
||||
"webkitgtk-2.50.0",
|
||||
"wpewebkit-2.48.7",
|
||||
"webkitgtk-2.48.7",
|
||||
"wpewebkit-2.50.0"
|
||||
]
|
||||
},
|
||||
"apple-oss-distributions/distribution-macOS": {
|
||||
@@ -47,7 +55,8 @@
|
||||
},
|
||||
"tags": [
|
||||
"macos-155",
|
||||
"macos-154"
|
||||
"macos-154",
|
||||
"macos-156"
|
||||
]
|
||||
},
|
||||
"fcs_keys": {
|
||||
@@ -66,37 +75,32 @@
|
||||
"fcs_keys_beta": {
|
||||
"fcs_keys": {
|
||||
"beta": {
|
||||
"fingerprint": "22G6086_24G84_22O6785",
|
||||
"ios_build": "22G6086",
|
||||
"macos_build": "24G84",
|
||||
"updated_at": "2025-08-05T19:09:17Z",
|
||||
"visionos_build": "22O6785"
|
||||
"fingerprint": "22H6020_24G222_22O785",
|
||||
"ios_build": "22H6020",
|
||||
"macos_build": "24G222",
|
||||
"updated_at": "2025-09-20T19:05:17Z",
|
||||
"visionos_build": "22O785"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fcs_keys_rc": {
|
||||
"fcs_keys": {
|
||||
"beta": {
|
||||
"updated_at": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"rc": {
|
||||
"fingerprint": "21H221_24G84_22O785",
|
||||
"ios_build": "21H221",
|
||||
"macos_build": "24G84",
|
||||
"updated_at": "2025-08-05T19:09:17Z",
|
||||
"visionos_build": "22O785"
|
||||
"fingerprint": "23A341_24G222_23M336",
|
||||
"ios_build": "23A341",
|
||||
"macos_build": "24G222",
|
||||
"updated_at": "2025-09-15T19:06:03Z",
|
||||
"visionos_build": "23M336"
|
||||
},
|
||||
"release": {
|
||||
"updated_at": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fcs_keys_stable": {
|
||||
"fcs_keys": {
|
||||
"release": {
|
||||
"fingerprint": "21H221_24G84_22O785",
|
||||
"ios_build": "21H221",
|
||||
"macos_build": "24G84",
|
||||
"updated_at": "2025-08-05T19:09:17Z",
|
||||
"visionos_build": "22O785"
|
||||
}
|
||||
}
|
||||
},
|
||||
"latest_ipsw": {
|
||||
"fcs_keys": {
|
||||
"beta": {
|
||||
"updated_at": "0001-01-01T00:00:00Z"
|
||||
@@ -105,9 +109,17 @@
|
||||
"updated_at": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"release": {
|
||||
"updated_at": "0001-01-01T00:00:00Z"
|
||||
"fingerprint": "23A341_24G222_23M336",
|
||||
"ios_build": "23A341",
|
||||
"macos_build": "24G222",
|
||||
"updated_at": "2025-09-15T19:06:03Z",
|
||||
"visionos_build": "23M336"
|
||||
}
|
||||
},
|
||||
"url": "https://updates.cdn-apple.com/2025SpringFCS/fullrestores/082-45783/9A345601-C46E-4195-A367-4BCA31447055/iPhone17,1_18.5_22F76_Restore.ipsw"
|
||||
}
|
||||
},
|
||||
"latest_ipsw": {
|
||||
"ios_url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-40536/E62697A1-4565-4068-867C-A64017377220/iPhone17,1_26.0_23A341_Restore.ipsw",
|
||||
"macos_url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-37622/CE01FAB2-7F26-48EE-AEE4-5E57A7F6D8BB/UniversalMac_26.0_25A354_Restore.ipsw",
|
||||
"url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-40536/E62697A1-4565-4068-867C-A64017377220/iPhone17,1_26.0_23A341_Restore.ipsw"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func GetDDIInfo(c *DDIConfig) (info *DDIInfo, err error) {
|
||||
if info.ManifestPath == "" {
|
||||
// At this point we have a DMG path that needs to be mounted to find the BuildManifest.plist
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", c.DDIDmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(c.DDIDmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(c.DDIDmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount %s: %w", c.DDIDmgPath, err)
|
||||
}
|
||||
|
||||
@@ -765,7 +765,7 @@ func GetUserAgent(f *dyld.File, sysVer *plist.SystemVersion) (string, error) {
|
||||
}
|
||||
|
||||
func OpenFromIPSW(ipswPath, pemDB string, driverKit, all bool) (*mount.Context, []*dyld.File, error) {
|
||||
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB, nil)
|
||||
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB, nil, "")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to mount IPSW: %v", err)
|
||||
}
|
||||
|
||||
@@ -49,11 +49,15 @@ func (ds *DatabaseService) StoreEntitlements(ipswPath string, entDB map[string]s
|
||||
return fmt.Errorf("failed to parse IPSW info: %v", err)
|
||||
}
|
||||
|
||||
// Detect platform from IPSW
|
||||
platform := DetectPlatformFromIPSW(ipswPath, ipswInfo)
|
||||
|
||||
ipswRecord = &model.Ipsw{
|
||||
ID: generateIPSWID(ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
|
||||
Name: filepath.Base(ipswPath),
|
||||
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
|
||||
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
|
||||
ID: generateIPSWIDWithPlatform(platform, ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
|
||||
Name: filepath.Base(ipswPath),
|
||||
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
|
||||
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
// Add devices
|
||||
@@ -94,9 +98,10 @@ func (ds *DatabaseService) StoreEntitlements(ipswPath string, entDB map[string]s
|
||||
} else {
|
||||
// Create a minimal IPSW record for standalone usage
|
||||
ipswRecord = &model.Ipsw{
|
||||
ID: generateIPSWID("unknown", "unknown"),
|
||||
Version: "unknown",
|
||||
BuildID: "unknown",
|
||||
ID: generateIPSWIDWithPlatform(model.PlatformIOS, "unknown", "unknown"),
|
||||
Version: "unknown",
|
||||
BuildID: "unknown",
|
||||
Platform: model.PlatformIOS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,11 +464,16 @@ func (ds *DatabaseService) storeEntitlement(ipswRecord *model.Ipsw, filePath, en
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateIPSWID creates a unique ID for an IPSW based on version and build
|
||||
// generateIPSWID creates a unique ID for an IPSW based on version and build (legacy)
|
||||
func generateIPSWID(version, build string) string {
|
||||
return fmt.Sprintf("%s_%s", version, build)
|
||||
}
|
||||
|
||||
// generateIPSWIDWithPlatform creates a unique ID for an IPSW based on platform, version and build
|
||||
func generateIPSWIDWithPlatform(platform model.Platform, version, build string) string {
|
||||
return fmt.Sprintf("%s_%s_%s", string(platform), version, build)
|
||||
}
|
||||
|
||||
// createValueHash creates a hash for a value to ensure uniqueness
|
||||
func createValueHash(valueType, value string) string {
|
||||
hashInput := fmt.Sprintf("%s:%s", valueType, value)
|
||||
|
||||
@@ -284,7 +284,7 @@ func scanEnts(ipswPath, dmgPath, dmgType, pemDbPath string) (map[string]string,
|
||||
}
|
||||
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgType, dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/ipsw/internal/db"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
@@ -83,9 +84,9 @@ func CreateSQLiteDatabase(dbPath string, ipsws, inputs []string) error {
|
||||
}
|
||||
|
||||
// CreatePostgreSQLDatabase creates or updates the PostgreSQL database
|
||||
func CreatePostgreSQLDatabase(host, port, user, password, database, sslMode string, ipsws, inputs []string) error {
|
||||
func CreatePostgreSQLDatabase(host, port, user, password, database, sslMode, poolMode string, ipsws, inputs []string) error {
|
||||
// Create PostgreSQL database connection
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
|
||||
}
|
||||
@@ -157,9 +158,9 @@ func SearchSQLiteEntitlements(dbPath, keyPattern, valuePattern, filePattern, ver
|
||||
}
|
||||
|
||||
// SearchPostgreSQLEntitlements searches the PostgreSQL database for entitlements
|
||||
func SearchPostgreSQLEntitlements(host, port, user, password, database, sslMode, keyPattern, valuePattern, filePattern, versionFilter string, fileOnly bool, limit int) error {
|
||||
func SearchPostgreSQLEntitlements(host, port, user, password, database, sslMode, poolMode, keyPattern, valuePattern, filePattern, versionFilter string, fileOnly bool, limit int) error {
|
||||
// Create database connection
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
|
||||
}
|
||||
@@ -265,8 +266,8 @@ func ShowSQLiteStatistics(dbPath string) error {
|
||||
}
|
||||
|
||||
// ShowPostgreSQLStatistics displays PostgreSQL database statistics
|
||||
func ShowPostgreSQLStatistics(host, port, user, password, database, sslMode string) error {
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
|
||||
func ShowPostgreSQLStatistics(host, port, user, password, database, sslMode, poolMode string) error {
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
|
||||
}
|
||||
@@ -353,3 +354,130 @@ func contains(haystack, needle string) bool {
|
||||
}
|
||||
return strings.Contains(strings.ToLower(haystack), strings.ToLower(needle))
|
||||
}
|
||||
|
||||
// CreateSQLiteDatabaseWithReplacement creates or updates SQLite database with replacement support
|
||||
func CreateSQLiteDatabaseWithReplacement(dbPath string, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create database directory: %v", err)
|
||||
}
|
||||
|
||||
// Create SQLite database connection
|
||||
dbConn, err := db.NewSqlite(dbPath, 1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SQLite database: %v", err)
|
||||
}
|
||||
if err := dbConn.Connect(); err != nil {
|
||||
return fmt.Errorf("failed to connect to SQLite database: %v", err)
|
||||
}
|
||||
defer dbConn.Close()
|
||||
|
||||
dbService := NewDatabaseService(dbConn)
|
||||
strategy := NewSQLiteReplacementStrategy(dbService)
|
||||
|
||||
return processIPSWsWithReplacement(strategy, ipsws, inputs, replaceStrategy, dryRun)
|
||||
}
|
||||
|
||||
// CreatePostgreSQLDatabaseWithReplacement creates or updates PostgreSQL database with replacement support
|
||||
func CreatePostgreSQLDatabaseWithReplacement(host, port, user, password, database, sslMode, poolMode string, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
|
||||
// Create PostgreSQL database connection
|
||||
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PostgreSQL database connection: %v", err)
|
||||
}
|
||||
if err := dbConn.Connect(); err != nil {
|
||||
return fmt.Errorf("failed to connect to PostgreSQL database: %v", err)
|
||||
}
|
||||
defer dbConn.Close()
|
||||
|
||||
dbService := NewDatabaseService(dbConn)
|
||||
strategy := NewPostgreSQLReplacementStrategy(dbService)
|
||||
|
||||
return processIPSWsWithReplacement(strategy, ipsws, inputs, replaceStrategy, dryRun)
|
||||
}
|
||||
|
||||
// processIPSWsWithReplacement processes IPSWs with replacement logic
|
||||
func processIPSWsWithReplacement(strategy ReplacementStrategy, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
|
||||
config := &ReplacementConfig{
|
||||
Strategy: replaceStrategy,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
|
||||
// Process IPSWs
|
||||
for _, ipswPath := range ipsws {
|
||||
log.WithField("ipsw", filepath.Base(ipswPath)).Info("Processing IPSW")
|
||||
|
||||
// Parse IPSW info for version comparison
|
||||
ipswInfo, err := info.Parse(ipswPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse IPSW info from %s: %v", ipswPath, err)
|
||||
}
|
||||
|
||||
// Detect platform for replacement logic
|
||||
platform := DetectPlatformFromIPSW(ipswPath, ipswInfo)
|
||||
newIPSW := IPSWInfo{
|
||||
ID: generateIPSWIDWithPlatform(platform, ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
|
||||
Name: filepath.Base(ipswPath),
|
||||
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
|
||||
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
|
||||
Platform: string(platform),
|
||||
}
|
||||
|
||||
// Get existing IPSWs for comparison (platform-aware)
|
||||
existingIPSWs, err := strategy.GetExistingIPSWs(string(platform), newIPSW.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get existing IPSWs: %v", err)
|
||||
}
|
||||
|
||||
// Create replacement plan
|
||||
plan, err := strategy.CreateReplacementPlan(newIPSW, existingIPSWs, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create replacement plan: %v", err)
|
||||
}
|
||||
|
||||
// Execute replacement if needed
|
||||
if len(plan.ToReplace) > 0 || config.DryRun {
|
||||
if err := strategy.ExecuteReplacement(plan); err != nil {
|
||||
return fmt.Errorf("failed to execute replacement: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract and store entitlements (only if not dry run and plan was executed)
|
||||
if !config.DryRun {
|
||||
// Extract entitlements from IPSW
|
||||
entDB, err := GetDatabase(&Config{
|
||||
IPSW: ipswPath,
|
||||
Database: "", // Don't create blob file
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract entitlements from %s: %v", ipswPath, err)
|
||||
}
|
||||
|
||||
// Store entitlements using the strategy's database service
|
||||
sqliteStrat, ok := strategy.(*SQLiteReplacementStrategy)
|
||||
if ok {
|
||||
if err := sqliteStrat.service.StoreEntitlements(ipswPath, entDB); err != nil {
|
||||
return fmt.Errorf("failed to store entitlements: %v", err)
|
||||
}
|
||||
} else if pgStrat, ok := strategy.(*PostgreSQLReplacementStrategy); ok {
|
||||
if err := pgStrat.service.StoreEntitlements(ipswPath, entDB); err != nil {
|
||||
return fmt.Errorf("failed to store entitlements: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process input folders if specified
|
||||
for _, inputPath := range inputs {
|
||||
log.WithField("input", inputPath).Info("Processing input folder")
|
||||
// For inputs, we don't have version info so we use legacy replacement
|
||||
// This would need to be implemented based on your input processing logic
|
||||
return fmt.Errorf("input folder processing with replacement not yet implemented")
|
||||
}
|
||||
|
||||
if !dryRun {
|
||||
fmt.Printf("✓ Database creation/update completed successfully\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright © 2018-2025 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/blacktop/ipsw/internal/model"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
)
|
||||
|
||||
// DetectPlatformFromIPSW detects the platform from IPSW filename and metadata
|
||||
func DetectPlatformFromIPSW(ipswPath string, ipswInfo *info.Info) model.Platform {
|
||||
filename := strings.ToLower(filepath.Base(ipswPath))
|
||||
|
||||
// First, try to detect from filename patterns
|
||||
if platform := detectPlatformFromFilename(filename); platform != "" {
|
||||
return platform
|
||||
}
|
||||
|
||||
// If filename detection fails, try from device types in BuildManifest
|
||||
if ipswInfo != nil && ipswInfo.Plists.BuildManifest != nil {
|
||||
if platform := detectPlatformFromDevices(ipswInfo.Plists.BuildManifest.SupportedProductTypes); platform != "" {
|
||||
return platform
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback to iOS
|
||||
return model.PlatformIOS
|
||||
}
|
||||
|
||||
// detectPlatformFromFilename detects platform from IPSW filename patterns
|
||||
func detectPlatformFromFilename(filename string) model.Platform {
|
||||
// macOS patterns (most specific first)
|
||||
macOSPatterns := []string{
|
||||
"universalmac", "macos", "mac_os", "macbook", "imac", "macmini", "macpro", "macstudio",
|
||||
}
|
||||
|
||||
// visionOS patterns (check before general patterns to avoid conflicts)
|
||||
visionOSPatterns := []string{
|
||||
"apple_vision_pro", "vision_pro", "visionos", "vision_os", "applevision", "realityos", "realitydevice",
|
||||
}
|
||||
|
||||
// watchOS patterns
|
||||
watchOSPatterns := []string{
|
||||
"watchos", "watch_os", "watch7", "watch6", "watch5", "applewatch",
|
||||
}
|
||||
|
||||
// tvOS patterns
|
||||
tvOSPatterns := []string{
|
||||
"tvos", "tv_os", "appletv", "apple_tv",
|
||||
}
|
||||
|
||||
// Check each platform pattern (order matters - most specific first)
|
||||
for _, pattern := range visionOSPatterns {
|
||||
if strings.Contains(filename, pattern) {
|
||||
return model.PlatformVisionOS
|
||||
}
|
||||
}
|
||||
|
||||
for _, pattern := range macOSPatterns {
|
||||
if strings.Contains(filename, pattern) {
|
||||
return model.PlatformMacOS
|
||||
}
|
||||
}
|
||||
|
||||
for _, pattern := range watchOSPatterns {
|
||||
if strings.Contains(filename, pattern) {
|
||||
return model.PlatformWatchOS
|
||||
}
|
||||
}
|
||||
|
||||
for _, pattern := range tvOSPatterns {
|
||||
if strings.Contains(filename, pattern) {
|
||||
return model.PlatformTvOS
|
||||
}
|
||||
}
|
||||
|
||||
// If no patterns match, return empty (caller will try other methods)
|
||||
return ""
|
||||
}
|
||||
|
||||
// detectPlatformFromDevices detects platform from device identifiers
|
||||
func detectPlatformFromDevices(devices []string) model.Platform {
|
||||
if len(devices) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Count device types to determine platform
|
||||
iosCount := 0
|
||||
macOSCount := 0
|
||||
watchOSCount := 0
|
||||
tvOSCount := 0
|
||||
visionOSCount := 0
|
||||
|
||||
for _, device := range devices {
|
||||
deviceLower := strings.ToLower(device)
|
||||
|
||||
// macOS device patterns
|
||||
if strings.Contains(deviceLower, "mac") ||
|
||||
strings.Contains(deviceLower, "vmware") ||
|
||||
strings.Contains(deviceLower, "parallels") {
|
||||
macOSCount++
|
||||
} else if strings.Contains(deviceLower, "watch") {
|
||||
watchOSCount++
|
||||
} else if strings.Contains(deviceLower, "appletv") ||
|
||||
strings.Contains(deviceLower, "atv") {
|
||||
tvOSCount++
|
||||
} else if strings.Contains(deviceLower, "realitydevice") ||
|
||||
strings.Contains(deviceLower, "vision") {
|
||||
visionOSCount++
|
||||
} else if strings.Contains(deviceLower, "iphone") ||
|
||||
strings.Contains(deviceLower, "ipad") ||
|
||||
strings.Contains(deviceLower, "ipod") ||
|
||||
strings.Contains(deviceLower, "simulator") {
|
||||
iosCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Return platform with highest count
|
||||
maxCount := iosCount
|
||||
platform := model.PlatformIOS
|
||||
|
||||
if macOSCount > maxCount {
|
||||
maxCount = macOSCount
|
||||
platform = model.PlatformMacOS
|
||||
}
|
||||
if watchOSCount > maxCount {
|
||||
maxCount = watchOSCount
|
||||
platform = model.PlatformWatchOS
|
||||
}
|
||||
if tvOSCount > maxCount {
|
||||
maxCount = tvOSCount
|
||||
platform = model.PlatformTvOS
|
||||
}
|
||||
if visionOSCount > maxCount {
|
||||
platform = model.PlatformVisionOS
|
||||
}
|
||||
|
||||
return platform
|
||||
}
|
||||
|
||||
// GetAllPlatforms returns all supported platforms
|
||||
func GetAllPlatforms() []model.Platform {
|
||||
return []model.Platform{
|
||||
model.PlatformIOS,
|
||||
model.PlatformMacOS,
|
||||
model.PlatformWatchOS,
|
||||
model.PlatformTvOS,
|
||||
model.PlatformVisionOS,
|
||||
}
|
||||
}
|
||||
|
||||
// FormatPlatformForDisplay formats platform name for user display
|
||||
func FormatPlatformForDisplay(platform model.Platform) string {
|
||||
switch platform {
|
||||
case model.PlatformIOS:
|
||||
return "iOS"
|
||||
case model.PlatformMacOS:
|
||||
return "macOS"
|
||||
case model.PlatformWatchOS:
|
||||
return "watchOS"
|
||||
case model.PlatformTvOS:
|
||||
return "tvOS"
|
||||
case model.PlatformVisionOS:
|
||||
return "visionOS"
|
||||
default:
|
||||
return string(platform)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
Copyright © 2018-2025 blacktop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VersionComparator represents an iOS version for comparison purposes
|
||||
type VersionComparator struct {
|
||||
MajorMinor string // e.g., "26.0", "18.5"
|
||||
Build string // e.g., "22G87", "beta6", "22F76"
|
||||
Raw string // Original version string
|
||||
}
|
||||
|
||||
// ReplacementConfig holds configuration for replacement operations
|
||||
type ReplacementConfig struct {
|
||||
Strategy string // auto, prompt, force
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
// ReplacementPlan represents a planned replacement operation
|
||||
type ReplacementPlan struct {
|
||||
ToReplace []IPSWInfo
|
||||
NewIPSW IPSWInfo
|
||||
Config *ReplacementConfig
|
||||
ReasonMsg string
|
||||
ConflictType string
|
||||
}
|
||||
|
||||
// IPSWInfo represents basic information about an IPSW
|
||||
type IPSWInfo struct {
|
||||
Version string
|
||||
BuildID string
|
||||
ID string // Database ID
|
||||
Name string // Filename
|
||||
Platform string // Platform (iOS, macOS, etc.)
|
||||
}
|
||||
|
||||
// ParseVersionComparator creates a VersionComparator from a version string
|
||||
func ParseVersionComparator(version, build string) VersionComparator {
|
||||
majorMinor := extractMajorMinorVersion(version)
|
||||
return VersionComparator{
|
||||
MajorMinor: majorMinor,
|
||||
Build: build,
|
||||
Raw: version,
|
||||
}
|
||||
}
|
||||
|
||||
// extractMajorMinorVersion extracts major.minor from version string
|
||||
// Examples: "26.0.1" -> "26.0", "18.5" -> "18.5"
|
||||
func extractMajorMinorVersion(version string) string {
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) >= 2 {
|
||||
return parts[0] + "." + parts[1]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// ShouldReplace determines if an existing version should be replaced by a new version
|
||||
func ShouldReplace(existing, new VersionComparator) bool {
|
||||
// Only replace if same major.minor version
|
||||
if existing.MajorMinor != new.MajorMinor {
|
||||
return false
|
||||
}
|
||||
|
||||
// If versions are identical, compare builds
|
||||
if existing.Raw == new.Raw {
|
||||
return CompareBuildVersions(existing.Build, new.Build) < 0
|
||||
}
|
||||
|
||||
// Compare full versions (e.g., 26.0.1 vs 26.0.2)
|
||||
return CompareVersionStrings(existing.Raw, new.Raw) < 0
|
||||
}
|
||||
|
||||
// ShouldReplaceWithPlatform determines if an existing IPSW should be replaced by a new one
|
||||
// considering both platform and version
|
||||
func ShouldReplaceWithPlatform(existingIPSW, newIPSW IPSWInfo) bool {
|
||||
// Only replace if same platform
|
||||
if existingIPSW.Platform != newIPSW.Platform {
|
||||
return false
|
||||
}
|
||||
|
||||
// Use existing version comparison logic
|
||||
existingComparator := ParseVersionComparator(existingIPSW.Version, existingIPSW.BuildID)
|
||||
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
|
||||
|
||||
return ShouldReplace(existingComparator, newComparator)
|
||||
}
|
||||
|
||||
// CompareBuildVersions compares two build version strings
|
||||
// Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
||||
func CompareBuildVersions(a, b string) int {
|
||||
// Handle beta versions
|
||||
if strings.Contains(a, "beta") || strings.Contains(b, "beta") {
|
||||
return compareBetaBuilds(a, b)
|
||||
}
|
||||
|
||||
// Handle numeric build IDs (e.g., 22G87 vs 22G89)
|
||||
if isNumericBuild(a) && isNumericBuild(b) {
|
||||
return compareNumericBuilds(a, b)
|
||||
}
|
||||
|
||||
// Fallback to string comparison
|
||||
if a < b {
|
||||
return -1
|
||||
} else if a > b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// CompareVersionStrings compares two version strings (e.g., "26.0.1" vs "26.0.2")
|
||||
func CompareVersionStrings(a, b string) int {
|
||||
aParts := parseVersionParts(a)
|
||||
bParts := parseVersionParts(b)
|
||||
|
||||
maxLen := len(aParts)
|
||||
if len(bParts) > maxLen {
|
||||
maxLen = len(bParts)
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
aVal := 0
|
||||
bVal := 0
|
||||
|
||||
if i < len(aParts) {
|
||||
aVal = aParts[i]
|
||||
}
|
||||
if i < len(bParts) {
|
||||
bVal = bParts[i]
|
||||
}
|
||||
|
||||
if aVal < bVal {
|
||||
return -1
|
||||
} else if aVal > bVal {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// parseVersionParts parses a version string into numeric parts
|
||||
func parseVersionParts(version string) []int {
|
||||
parts := strings.Split(version, ".")
|
||||
result := make([]int, len(parts))
|
||||
|
||||
for i, part := range parts {
|
||||
if val, err := strconv.Atoi(part); err == nil {
|
||||
result[i] = val
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// compareBetaBuilds compares beta build versions
|
||||
func compareBetaBuilds(a, b string) int {
|
||||
betaRegex := regexp.MustCompile(`beta(\d+)`)
|
||||
|
||||
aMatches := betaRegex.FindStringSubmatch(a)
|
||||
bMatches := betaRegex.FindStringSubmatch(b)
|
||||
|
||||
// If both are beta versions
|
||||
if len(aMatches) > 1 && len(bMatches) > 1 {
|
||||
aNum, _ := strconv.Atoi(aMatches[1])
|
||||
bNum, _ := strconv.Atoi(bMatches[1])
|
||||
|
||||
if aNum < bNum {
|
||||
return -1
|
||||
} else if aNum > bNum {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Beta versions are considered "less than" release versions
|
||||
if len(aMatches) > 1 && len(bMatches) == 0 {
|
||||
return -1
|
||||
}
|
||||
if len(aMatches) == 0 && len(bMatches) > 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Fallback to string comparison
|
||||
return strings.Compare(a, b)
|
||||
}
|
||||
|
||||
// isNumericBuild checks if a build ID follows numeric pattern (e.g., 22G87)
|
||||
func isNumericBuild(build string) bool {
|
||||
match, _ := regexp.MatchString(`^\d+[A-Z]+\d+$`, build)
|
||||
return match
|
||||
}
|
||||
|
||||
// compareNumericBuilds compares numeric build IDs
|
||||
func compareNumericBuilds(a, b string) int {
|
||||
// Extract prefix numbers and suffix numbers
|
||||
aPrefix, aSuffix := extractBuildParts(a)
|
||||
bPrefix, bSuffix := extractBuildParts(b)
|
||||
|
||||
// Compare prefixes first
|
||||
if aPrefix != bPrefix {
|
||||
if aPrefix < bPrefix {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// Compare suffixes
|
||||
if aSuffix < bSuffix {
|
||||
return -1
|
||||
} else if aSuffix > bSuffix {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// extractBuildParts extracts numeric parts from build ID (e.g., "22G87" -> 22, 87)
|
||||
func extractBuildParts(build string) (int, int) {
|
||||
regex := regexp.MustCompile(`^(\d+)[A-Z]+(\d+)$`)
|
||||
matches := regex.FindStringSubmatch(build)
|
||||
|
||||
if len(matches) < 3 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
prefix, _ := strconv.Atoi(matches[1])
|
||||
suffix, _ := strconv.Atoi(matches[2])
|
||||
|
||||
return prefix, suffix
|
||||
}
|
||||
|
||||
// CreateReplacementPlan analyzes what would be replaced and creates a plan
|
||||
func CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) *ReplacementPlan {
|
||||
var toReplace []IPSWInfo
|
||||
var reasonMsg string
|
||||
|
||||
for _, existing := range existingIPSWs {
|
||||
if ShouldReplaceWithPlatform(existing, newIPSW) {
|
||||
toReplace = append(toReplace, existing)
|
||||
}
|
||||
}
|
||||
|
||||
if len(toReplace) > 0 {
|
||||
if len(toReplace) == 1 {
|
||||
reasonMsg = fmt.Sprintf("Replacing %s %s (build %s) with %s %s (build %s)",
|
||||
toReplace[0].Platform, toReplace[0].Version, toReplace[0].BuildID,
|
||||
newIPSW.Platform, newIPSW.Version, newIPSW.BuildID)
|
||||
} else {
|
||||
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
|
||||
reasonMsg = fmt.Sprintf("Replacing %d older builds of %s %s with newer build %s",
|
||||
len(toReplace), newIPSW.Platform, newComparator.MajorMinor, newIPSW.BuildID)
|
||||
}
|
||||
} else {
|
||||
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
|
||||
reasonMsg = fmt.Sprintf("No older builds found for %s %s - will add as new entry", newIPSW.Platform, newComparator.MajorMinor)
|
||||
}
|
||||
|
||||
return &ReplacementPlan{
|
||||
ToReplace: toReplace,
|
||||
NewIPSW: newIPSW,
|
||||
Config: config,
|
||||
ReasonMsg: reasonMsg,
|
||||
ConflictType: determineConflictType(toReplace, newIPSW),
|
||||
}
|
||||
}
|
||||
|
||||
// determineConflictType categorizes the type of replacement
|
||||
func determineConflictType(toReplace []IPSWInfo, newIPSW IPSWInfo) string {
|
||||
if len(toReplace) == 0 {
|
||||
return "new"
|
||||
}
|
||||
|
||||
if len(toReplace) == 1 {
|
||||
existing := toReplace[0]
|
||||
if existing.Version == newIPSW.Version {
|
||||
return "build_update"
|
||||
}
|
||||
return "version_update"
|
||||
}
|
||||
|
||||
return "multiple_replace"
|
||||
}
|
||||
|
||||
// ReplacementStrategy interface for different replacement implementations
|
||||
type ReplacementStrategy interface {
|
||||
SupportsVersionBasedReplacement() bool
|
||||
CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error)
|
||||
ExecuteReplacement(plan *ReplacementPlan) error
|
||||
GetExistingIPSWs(platform, version string) ([]IPSWInfo, error)
|
||||
}
|
||||
|
||||
// SQLiteReplacementStrategy handles replacements for SQLite databases
|
||||
type SQLiteReplacementStrategy struct {
|
||||
service *DatabaseService
|
||||
}
|
||||
|
||||
// NewSQLiteReplacementStrategy creates a new SQLite replacement strategy
|
||||
func NewSQLiteReplacementStrategy(service *DatabaseService) *SQLiteReplacementStrategy {
|
||||
return &SQLiteReplacementStrategy{service: service}
|
||||
}
|
||||
|
||||
// SupportsVersionBasedReplacement returns true for SQLite strategy
|
||||
func (s *SQLiteReplacementStrategy) SupportsVersionBasedReplacement() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CreateReplacementPlan creates a replacement plan for SQLite
|
||||
func (s *SQLiteReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
|
||||
return CreateReplacementPlan(newIPSW, existingIPSWs, config), nil
|
||||
}
|
||||
|
||||
// ExecuteReplacement executes the replacement plan atomically
|
||||
func (s *SQLiteReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
|
||||
if s.service.gormDB == nil {
|
||||
return fmt.Errorf("GORM database required for replacement operations")
|
||||
}
|
||||
|
||||
// If dry run, just log what would happen
|
||||
if plan.Config.DryRun {
|
||||
fmt.Printf("DRY RUN: %s\n", plan.ReasonMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prompt user if strategy is prompt
|
||||
if plan.Config.Strategy == "prompt" && len(plan.ToReplace) > 0 {
|
||||
if !promptUserForReplacement(plan) {
|
||||
fmt.Printf("Replacement cancelled by user\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Execute atomic replacement transaction
|
||||
return s.service.gormDB.Transaction(func(tx *gorm.DB) error {
|
||||
// Step 1: Delete old IPSW data
|
||||
for _, oldIPSW := range plan.ToReplace {
|
||||
if err := s.deleteIPSWData(tx, oldIPSW.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete old IPSW %s: %w", oldIPSW.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("✓ %s\n", plan.ReasonMsg)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetExistingIPSWs retrieves existing IPSWs for version comparison
|
||||
func (s *SQLiteReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
|
||||
if s.service.gormDB == nil {
|
||||
return nil, fmt.Errorf("GORM database required")
|
||||
}
|
||||
|
||||
majorMinor := extractMajorMinorVersion(version)
|
||||
|
||||
// Query IPSWs with same platform and major.minor version
|
||||
var ipsws []struct {
|
||||
ID string
|
||||
Name string
|
||||
Version string
|
||||
BuildID string
|
||||
Platform string
|
||||
}
|
||||
|
||||
err := s.service.gormDB.Table("ipsws").
|
||||
Select("id, name, version, buildid as build_id, platform").
|
||||
Where("platform = ? AND version LIKE ?", platform, majorMinor+"%").
|
||||
Find(&ipsws).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query existing IPSWs: %w", err)
|
||||
}
|
||||
|
||||
result := make([]IPSWInfo, len(ipsws))
|
||||
for i, ipsw := range ipsws {
|
||||
result[i] = IPSWInfo{
|
||||
ID: ipsw.ID,
|
||||
Name: ipsw.Name,
|
||||
Version: ipsw.Version,
|
||||
BuildID: ipsw.BuildID,
|
||||
Platform: ipsw.Platform,
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// deleteIPSWData deletes all data associated with an IPSW
|
||||
func (s *SQLiteReplacementStrategy) deleteIPSWData(tx *gorm.DB, ipswID string) error {
|
||||
// Delete entitlements (cascading delete handles references)
|
||||
if err := tx.Exec("DELETE FROM entitlements WHERE ipsw_id = ?", ipswID).Error; err != nil {
|
||||
return fmt.Errorf("failed to delete entitlements: %w", err)
|
||||
}
|
||||
|
||||
// Delete IPSW record
|
||||
if err := tx.Exec("DELETE FROM ipsws WHERE id = ?", ipswID).Error; err != nil {
|
||||
return fmt.Errorf("failed to delete IPSW record: %w", err)
|
||||
}
|
||||
|
||||
// Clean up orphaned references
|
||||
return s.cleanupOrphanedReferences(tx)
|
||||
}
|
||||
|
||||
// cleanupOrphanedReferences removes unused paths, keys, and values
|
||||
func (s *SQLiteReplacementStrategy) cleanupOrphanedReferences(tx *gorm.DB) error {
|
||||
// Remove paths not referenced by any entitlements
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM paths
|
||||
WHERE id NOT IN (SELECT DISTINCT path_id FROM entitlements WHERE path_id IS NOT NULL)
|
||||
`).Error; err != nil {
|
||||
return fmt.Errorf("failed to cleanup orphaned paths: %w", err)
|
||||
}
|
||||
|
||||
// Remove keys not referenced by any entitlements
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM entitlement_keys
|
||||
WHERE id NOT IN (SELECT DISTINCT key_id FROM entitlements WHERE key_id IS NOT NULL)
|
||||
`).Error; err != nil {
|
||||
return fmt.Errorf("failed to cleanup orphaned keys: %w", err)
|
||||
}
|
||||
|
||||
// Remove values not referenced by any entitlements
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM entitlement_values
|
||||
WHERE id NOT IN (SELECT DISTINCT value_id FROM entitlements WHERE value_id IS NOT NULL)
|
||||
`).Error; err != nil {
|
||||
return fmt.Errorf("failed to cleanup orphaned values: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostgreSQLReplacementStrategy handles replacements for PostgreSQL databases
|
||||
type PostgreSQLReplacementStrategy struct {
|
||||
service *DatabaseService
|
||||
}
|
||||
|
||||
// NewPostgreSQLReplacementStrategy creates a new PostgreSQL replacement strategy
|
||||
func NewPostgreSQLReplacementStrategy(service *DatabaseService) *PostgreSQLReplacementStrategy {
|
||||
return &PostgreSQLReplacementStrategy{service: service}
|
||||
}
|
||||
|
||||
// SupportsVersionBasedReplacement returns true for PostgreSQL strategy
|
||||
func (p *PostgreSQLReplacementStrategy) SupportsVersionBasedReplacement() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CreateReplacementPlan creates a replacement plan for PostgreSQL
|
||||
func (p *PostgreSQLReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
|
||||
return CreateReplacementPlan(newIPSW, existingIPSWs, config), nil
|
||||
}
|
||||
|
||||
// ExecuteReplacement executes the replacement plan atomically
|
||||
func (p *PostgreSQLReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
|
||||
// Same implementation as SQLite for now since both use GORM
|
||||
sqliteStrategy := &SQLiteReplacementStrategy{service: p.service}
|
||||
return sqliteStrategy.ExecuteReplacement(plan)
|
||||
}
|
||||
|
||||
// GetExistingIPSWs retrieves existing IPSWs for version comparison
|
||||
func (p *PostgreSQLReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
|
||||
// Same implementation as SQLite for now since both use GORM
|
||||
sqliteStrategy := &SQLiteReplacementStrategy{service: p.service}
|
||||
return sqliteStrategy.GetExistingIPSWs(platform, version)
|
||||
}
|
||||
|
||||
// LegacyReplacementStrategy handles replacements for legacy database formats
|
||||
type LegacyReplacementStrategy struct{}
|
||||
|
||||
// NewLegacyReplacementStrategy creates a new legacy replacement strategy
|
||||
func NewLegacyReplacementStrategy() *LegacyReplacementStrategy {
|
||||
return &LegacyReplacementStrategy{}
|
||||
}
|
||||
|
||||
// SupportsVersionBasedReplacement returns false for legacy strategy (simple overwrite)
|
||||
func (l *LegacyReplacementStrategy) SupportsVersionBasedReplacement() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateReplacementPlan creates a simple replacement plan for legacy format
|
||||
func (l *LegacyReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
|
||||
return &ReplacementPlan{
|
||||
ToReplace: existingIPSWs, // Replace all existing data
|
||||
NewIPSW: newIPSW,
|
||||
Config: config,
|
||||
ReasonMsg: "Legacy format: replacing entire database with new data",
|
||||
ConflictType: "legacy_overwrite",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteReplacement executes simple overwrite for legacy format
|
||||
func (l *LegacyReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
|
||||
if plan.Config.DryRun {
|
||||
fmt.Printf("DRY RUN: %s\n", plan.ReasonMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("✓ %s\n", plan.ReasonMsg)
|
||||
// For legacy format, the actual overwrite is handled by the calling code
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExistingIPSWs returns empty list for legacy format
|
||||
func (l *LegacyReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
|
||||
return []IPSWInfo{}, nil
|
||||
}
|
||||
|
||||
// promptUserForReplacement prompts the user to confirm replacement
|
||||
func promptUserForReplacement(plan *ReplacementPlan) bool {
|
||||
fmt.Printf("\n%s\n", plan.ReasonMsg)
|
||||
|
||||
if len(plan.ToReplace) > 0 {
|
||||
fmt.Printf("This will delete the following existing data:\n")
|
||||
for _, ipsw := range plan.ToReplace {
|
||||
fmt.Printf(" - %s (iOS %s, build %s)\n", ipsw.Name, ipsw.Version, ipsw.BuildID)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nDo you want to continue? (y/N): ")
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
if scanner.Scan() {
|
||||
response := strings.ToLower(strings.TrimSpace(scanner.Text()))
|
||||
return response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (c Context) Unmount() error {
|
||||
}
|
||||
|
||||
// DmgInIPSW will mount a DMG from an IPSW
|
||||
func DmgInIPSW(path, typ, pemDbPath string, keys any) (*Context, error) {
|
||||
func DmgInIPSW(path, typ, pemDbPath string, keys any, customMountPoint string) (*Context, error) {
|
||||
var err error
|
||||
|
||||
ipswPath := filepath.Clean(path)
|
||||
@@ -154,7 +154,7 @@ func DmgInIPSW(path, typ, pemDbPath string, keys any) (*Context, error) {
|
||||
}
|
||||
}
|
||||
|
||||
mp, am, err := utils.MountDMG(extractedDMG)
|
||||
mp, am, err := utils.MountDMG(extractedDMG, customMountPoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount %s: %v", extractedDMG, err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type Postgres struct {
|
||||
Password string
|
||||
Database string
|
||||
SSLMode string // SSL mode for connection (disable, require, verify-ca, verify-full)
|
||||
PoolMode string // Connection pool mode (session, transaction)
|
||||
// Config
|
||||
BatchSize int
|
||||
|
||||
@@ -43,7 +44,7 @@ func NewPostgres(host, port, user, password, database string, batchSize int) (Da
|
||||
}
|
||||
|
||||
// NewPostgresWithSSL creates a new Postgres database with SSL configuration.
|
||||
func NewPostgresWithSSL(host, port, user, password, database, sslMode string, batchSize int) (Database, error) {
|
||||
func NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode string, batchSize int) (Database, error) {
|
||||
if host == "" || port == "" || user == "" || database == "" {
|
||||
return nil, fmt.Errorf("'host', 'port', 'user' and 'database' are required")
|
||||
}
|
||||
@@ -57,6 +58,7 @@ func NewPostgresWithSSL(host, port, user, password, database, sslMode string, ba
|
||||
Password: password,
|
||||
Database: database,
|
||||
SSLMode: sslMode,
|
||||
PoolMode: poolMode,
|
||||
BatchSize: batchSize,
|
||||
}, nil
|
||||
}
|
||||
@@ -69,6 +71,10 @@ func (p *Postgres) Connect() (err error) {
|
||||
p.Host, p.Port, p.User, p.Database, p.Password, p.SSLMode,
|
||||
)
|
||||
|
||||
if p.PoolMode != "" {
|
||||
dsn += fmt.Sprintf(" pool_mode=%s", p.PoolMode)
|
||||
}
|
||||
|
||||
p.db, err = gorm.Open(postgres.New(postgres.Config{
|
||||
DSN: dsn,
|
||||
PreferSimpleProtocol: true, // Use simple protocol to avoid prepared statements
|
||||
|
||||
@@ -365,7 +365,7 @@ func mountDMG(ctx *Context) (err error) {
|
||||
}
|
||||
}
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", ctx.SystemOsDmgPath))
|
||||
ctx.MountPath, ctx.IsMounted, err = utils.MountDMG(ctx.SystemOsDmgPath)
|
||||
ctx.MountPath, ctx.IsMounted, err = utils.MountDMG(ctx.SystemOsDmgPath, "")
|
||||
if err != nil {
|
||||
if !errors.Is(err, utils.ErrMountResourceBusy) {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -49,6 +49,8 @@ const (
|
||||
developerURL = "https://developer.apple.com"
|
||||
downloadURLNew = "https://download.developer.apple.com"
|
||||
downloadURL = "https://developer.apple.com/download/"
|
||||
downloadOSesURL = "https://developer.apple.com/download/os/"
|
||||
downloadMoreURL = "https://developer.apple.com/download/all/"
|
||||
downloadAppsURL = "https://developer.apple.com/download/applications/"
|
||||
downloadProfilesURL = "https://developer.apple.com/bug-reporting/profiles-and-logs/"
|
||||
|
||||
@@ -450,12 +452,14 @@ func (dp *DevPortal) Login(username, password string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal keychain credentials: %v", err)
|
||||
}
|
||||
dp.Vault.Set(keyring.Item{
|
||||
if err := dp.Vault.Set(keyring.Item{
|
||||
Key: VaultName,
|
||||
Data: dat,
|
||||
Label: AppName,
|
||||
Description: "application password",
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to save credentials to vault: %v", err)
|
||||
}
|
||||
} else { // credentials found in vault
|
||||
var auth AppleAccountAuth
|
||||
if err := json.Unmarshal(creds.Data, &auth); err != nil {
|
||||
@@ -601,11 +605,13 @@ func (dp *DevPortal) generateSRP(username, password string) (*http.Response, err
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
json.NewEncoder(buf).Encode(&auth{
|
||||
if err := json.NewEncoder(buf).Encode(&auth{
|
||||
AccountName: username,
|
||||
A: base64.StdEncoding.EncodeToString(s.A.Bytes()),
|
||||
Protocols: []string{"s2k", "s2k_fo"},
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode auth request: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", initURL, buf)
|
||||
if err != nil {
|
||||
@@ -1121,12 +1127,14 @@ func (dp *DevPortal) storeSession() error {
|
||||
// clear dev auth mem
|
||||
auth = AppleAccountAuth{}
|
||||
|
||||
dp.Vault.Set(keyring.Item{
|
||||
if err := dp.Vault.Set(keyring.Item{
|
||||
Key: VaultName,
|
||||
Data: data,
|
||||
Label: AppName,
|
||||
Description: "application password",
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to save session to vault: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1616,7 +1624,7 @@ func (dp *DevPortal) getDownloads() (*Downloads, error) {
|
||||
func (dp *DevPortal) getDevDownloads() (map[string][]DevDownload, error) {
|
||||
ipsws := make(map[string][]DevDownload)
|
||||
|
||||
req, err := http.NewRequest("GET", downloadURL, nil)
|
||||
req, err := http.NewRequest("GET", downloadOSesURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create http GET request: %v", err)
|
||||
}
|
||||
@@ -1628,7 +1636,7 @@ func (dp *DevPortal) getDevDownloads() (map[string][]DevDownload, error) {
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("failed to GET %s: response received %s", downloadURL, response.Status)
|
||||
return nil, fmt.Errorf("failed to GET %s: response received %s", downloadOSesURL, response.Status)
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(response.Body)
|
||||
|
||||
+13
-1
@@ -14,12 +14,24 @@ var (
|
||||
ErrSymExists = errors.New("symbol exists")
|
||||
)
|
||||
|
||||
// Platform represents supported Apple platforms
|
||||
type Platform string
|
||||
|
||||
const (
|
||||
PlatformIOS Platform = "iOS"
|
||||
PlatformMacOS Platform = "macOS"
|
||||
PlatformWatchOS Platform = "watchOS"
|
||||
PlatformTvOS Platform = "tvOS"
|
||||
PlatformVisionOS Platform = "visionOS"
|
||||
)
|
||||
|
||||
// Ipsw is the model for an Ipsw file.
|
||||
type Ipsw struct {
|
||||
ID string `gorm:"primaryKey" json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Version string `gorm:"index:idx_platform_version,priority:2" json:"version,omitempty"`
|
||||
BuildID string `gorm:"column:buildid" json:"buildid,omitempty"`
|
||||
Platform Platform `gorm:"type:varchar(20);index:idx_platform_version,priority:1" json:"platform,omitempty"`
|
||||
Devices []*Device `gorm:"many2many:ipsw_devices;" json:"devices,omitempty"`
|
||||
Kernels []*Kernelcache `gorm:"many2many:ipsw_kernels;" json:"kernels,omitempty"`
|
||||
DSCs []*DyldSharedCache `gorm:"many2many:ipsw_dscs;" json:"dscs,omitempty"`
|
||||
|
||||
@@ -54,7 +54,7 @@ func scanDmg(ipswPath, dmgPath, dmgType, pemDB string, handler func(string, stri
|
||||
defer os.Remove(dmgPath)
|
||||
}
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgType, dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
|
||||
@@ -644,8 +644,13 @@ func IsAlreadyMounted(image, mountPoint string) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func MountDMG(image string) (string, bool, error) {
|
||||
mountPoint := fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
|
||||
func MountDMG(image string, customMountPoint string) (string, bool, error) {
|
||||
var mountPoint string
|
||||
if customMountPoint != "" {
|
||||
mountPoint = customMountPoint
|
||||
} else {
|
||||
mountPoint = fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
// check if already mounted
|
||||
@@ -794,7 +799,7 @@ func ExtractFromDMG(ipswPath, dmgPath, destPath, pemDB string, pattern *regexp.R
|
||||
}
|
||||
|
||||
Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
}
|
||||
|
||||
Binary file not shown.
+1
-165
File diff suppressed because one or more lines are too long
@@ -717,9 +717,15 @@ func Parse(ipswPath string, keys ...string) (map[string]*DeviceTree, error) {
|
||||
for _, f := range zr.File {
|
||||
if regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(f.Name) {
|
||||
dtData := make([]byte, f.UncompressedSize64)
|
||||
rc, _ := f.Open()
|
||||
io.ReadFull(rc, dtData)
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open %s: %v", f.Name, err)
|
||||
}
|
||||
_, err = io.ReadFull(rc, dtData)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %s: %v", f.Name, err)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
ivkey, err := hex.DecodeString(keys[0])
|
||||
if err != nil {
|
||||
@@ -777,9 +783,15 @@ func ParseZipFiles(files []*zip.File, keys ...string) (dt map[string]*DeviceTree
|
||||
for _, f := range files {
|
||||
if regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(f.Name) {
|
||||
dtData := make([]byte, f.UncompressedSize64)
|
||||
rc, _ := f.Open()
|
||||
io.ReadFull(rc, dtData)
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open %s: %v", f.Name, err)
|
||||
}
|
||||
_, err = io.ReadFull(rc, dtData)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %s: %v", f.Name, err)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
ivkey, err := hex.DecodeString(keys[0])
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath, pemDB string, arches []stri
|
||||
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
|
||||
var alreadyMounted bool
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+27
-27
@@ -157,18 +157,18 @@ func (a *AA) Info() (*info.Info, error) {
|
||||
var pfiles []fs.File
|
||||
for _, file := range a.Files() {
|
||||
switch {
|
||||
case regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(file.Path()):
|
||||
case regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(file.Name()):
|
||||
fallthrough
|
||||
case regexp.MustCompile(`^Info.plist$`).MatchString(file.Path()):
|
||||
case regexp.MustCompile(`^Info.plist$`).MatchString(file.Name()):
|
||||
fallthrough
|
||||
case regexp.MustCompile(`^AssetData/Info.plist$`).MatchString(file.Path()):
|
||||
case regexp.MustCompile(`^AssetData/Info.plist$`).MatchString(file.Name()):
|
||||
fallthrough
|
||||
case regexp.MustCompile(`Restore.plist$`).MatchString(file.Path()):
|
||||
case regexp.MustCompile(`Restore.plist$`).MatchString(file.Name()):
|
||||
fallthrough
|
||||
case regexp.MustCompile(`BuildManifest.plist$`).MatchString(file.Path()):
|
||||
case regexp.MustCompile(`BuildManifest.plist$`).MatchString(file.Name()):
|
||||
fallthrough
|
||||
case regexp.MustCompile(`SystemVersion.plist$`).MatchString(file.Path()):
|
||||
f, err := a.Open(file.Path(), true)
|
||||
case regexp.MustCompile(`SystemVersion.plist$`).MatchString(file.Name()):
|
||||
f, err := a.Open(file.Name(), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -360,8 +360,8 @@ func (r *Reader) initPayloadMap() (perr error) {
|
||||
if file.isDir {
|
||||
continue
|
||||
}
|
||||
if pre.MatchString(file.Name()) {
|
||||
f, err := r.Open(file.Path(), false)
|
||||
if pre.MatchString(file.Base()) {
|
||||
f, err := r.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
perr = err
|
||||
return
|
||||
@@ -436,9 +436,9 @@ func (r *Reader) GetPayloadFiles(pattern, payloadRange, output string) error {
|
||||
if file.isDir {
|
||||
continue
|
||||
}
|
||||
if pre.MatchString(file.Name()) {
|
||||
if pre.MatchString(file.Base()) {
|
||||
eg.Go(func() error {
|
||||
f, err := r.Open(file.Path(), false)
|
||||
f, err := r.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -460,7 +460,7 @@ func (r *Reader) GetPayloadFiles(pattern, payloadRange, output string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create dir %s: %v", filepath.Dir(fname), err)
|
||||
}
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Extracting from '%s' -> %s\t%s", file.Name(), humanize.Bytes(uint64(f.Size())), fname))
|
||||
utils.Indent(log.Info, 2)(fmt.Sprintf("Extracting from '%s' -> %s\t%s", file.Base(), humanize.Bytes(uint64(f.Size())), fname))
|
||||
if err := os.Rename(path, fname); err != nil {
|
||||
return fmt.Errorf("failed to mv file %s to %s: %v", strings.TrimPrefix(path, tmpdir), fname, err)
|
||||
}
|
||||
@@ -488,9 +488,9 @@ func (r *Reader) PayloadFiles(pattern string, json bool) error {
|
||||
if file.isDir {
|
||||
continue
|
||||
}
|
||||
if pre.MatchString(file.Name()) {
|
||||
if pre.MatchString(file.Base()) {
|
||||
eg.Go(func() error {
|
||||
f, err := r.Open(file.Path(), false)
|
||||
f, err := r.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -574,19 +574,19 @@ func (r *Reader) ExtractCryptex(cryptex, output string) (string, error) {
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
for _, file := range r.Files() {
|
||||
if re.MatchString(file.Name()) {
|
||||
cryptexFile, err := r.Open(file.Path(), false)
|
||||
if re.MatchString(file.Base()) {
|
||||
cryptexFile, err := r.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open cryptex file: %v", err)
|
||||
}
|
||||
defer cryptexFile.Close()
|
||||
// create a temp file to hold the OTA cryptex
|
||||
cf, err := os.Create(filepath.Join(tmpdir, file.Name()))
|
||||
cf, err := os.Create(filepath.Join(tmpdir, file.Base()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create file: %v", err)
|
||||
}
|
||||
// create a temp file to hold the PATCHED OTA cryptex DMG
|
||||
dcf, err := os.Create(filepath.Join(output, file.Name()+".dmg"))
|
||||
dcf, err := os.Create(filepath.Join(output, file.Base()+".dmg"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create file: %v", err)
|
||||
}
|
||||
@@ -597,7 +597,7 @@ func (r *Reader) ExtractCryptex(cryptex, output string) (string, error) {
|
||||
cf.Close()
|
||||
// patch the cryptex
|
||||
if err := ridiff.RawImagePatch("", cf.Name(), dcf.Name(), 0); err != nil {
|
||||
return "", fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Path()), err)
|
||||
return "", fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Name()), err)
|
||||
}
|
||||
return dcf.Name(), nil
|
||||
}
|
||||
@@ -623,19 +623,19 @@ func (r *Reader) ExtractFromCryptexes(pattern, output string) ([]string, error)
|
||||
for _, cryptex := range []string{"cryptex-system-(arm64e?|x86_64h?)$"} {
|
||||
re := regexp.MustCompile(cryptex)
|
||||
for _, file := range r.Files() {
|
||||
if re.MatchString(file.Name()) {
|
||||
cryptexFile, err := r.Open(file.Path(), false)
|
||||
if re.MatchString(file.Base()) {
|
||||
cryptexFile, err := r.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open cryptex file: %v", err)
|
||||
}
|
||||
defer cryptexFile.Close()
|
||||
// create a temp file to hold the OTA cryptex
|
||||
cf, err := os.Create(filepath.Join(tmpdir, file.Name()))
|
||||
cf, err := os.Create(filepath.Join(tmpdir, file.Base()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file: %v", err)
|
||||
}
|
||||
// create a temp file to hold the PATCHED OTA cryptex DMG
|
||||
dcf, err := os.Create(filepath.Join(tmpdir, file.Name()+".dmg"))
|
||||
dcf, err := os.Create(filepath.Join(tmpdir, file.Base()+".dmg"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file: %v", err)
|
||||
}
|
||||
@@ -645,12 +645,12 @@ func (r *Reader) ExtractFromCryptexes(pattern, output string) ([]string, error)
|
||||
cf.Close()
|
||||
// patch the cryptex
|
||||
if err := ridiff.RawImagePatch("", cf.Name(), dcf.Name(), 0); err != nil {
|
||||
return nil, fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Path()), err)
|
||||
return nil, fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Name()), err)
|
||||
}
|
||||
dcf.Close()
|
||||
// mount the patched cryptex
|
||||
utils.Indent(log.Info, 4)(fmt.Sprintf("Mounting DMG %s", dcf.Name()))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dcf.Name())
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dcf.Name(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
|
||||
}
|
||||
@@ -792,8 +792,8 @@ func (r *Reader) payloadLookuo(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *File) Name() string { _, elem, _ := split(f.name); return elem }
|
||||
func (f *File) Path() string { return f.name }
|
||||
func (f *File) Base() string { _, elem, _ := split(f.name); return elem }
|
||||
func (f *File) Name() string { return f.name }
|
||||
func (f *File) Size() int64 {
|
||||
if f.zfile != nil {
|
||||
return int64(f.zfile.UncompressedSize64)
|
||||
|
||||
+15
-7
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
util "github.com/blacktop/ipsw/internal/magic"
|
||||
"github.com/blacktop/ipsw/pkg/bom"
|
||||
"github.com/blacktop/ipsw/pkg/ota/pbzx"
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -707,13 +708,20 @@ func (y *YAA) Parse(r io.ReadSeeker) error {
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to read aa pbzx patch data: %w", err)
|
||||
}
|
||||
var pbuf bytes.Buffer
|
||||
if err := pbzx.Extract(context.Background(), bytes.NewReader(data), &pbuf, runtime.NumCPU()); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to extract aa pbzx patch data: %w", err)
|
||||
}
|
||||
|
||||
if err := y.Parse(bytes.NewReader(pbuf.Bytes())); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
|
||||
if isPBZX, err := util.IsPBZXData(bytes.NewReader(data)); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to check if data is pbzx: %w", err)
|
||||
} else if isPBZX {
|
||||
var pbuf bytes.Buffer
|
||||
if err := pbzx.Extract(context.Background(), bytes.NewReader(data), &pbuf, runtime.NumCPU()); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to extract aa pbzx patch data: %w", err)
|
||||
}
|
||||
if err := y.Parse(bytes.NewReader(pbuf.Bytes())); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := y.Parse(bytes.NewReader(data)); err != nil {
|
||||
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Warnf("YAA.Parse: unsupported YOP operation: %s (let author know)", ent.Yop)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/css/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "src/components",
|
||||
"utils": "src/lib/utils"
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,6 @@ ipsw appstore profile [flags]
|
||||
* [ipsw appstore profile create](/docs/cli/ipsw/appstore/profile/create) - Create a new provisioning profile
|
||||
* [ipsw appstore profile info](/docs/cli/ipsw/appstore/profile/info) - Dump provisioning profile information
|
||||
* [ipsw appstore profile ls](/docs/cli/ipsw/appstore/profile/ls) - List provisioning profiles and download their data
|
||||
* [ipsw appstore profile renew](/docs/cli/ipsw/appstore/profile/renew) - Renew and expired or invalide provisioning profile
|
||||
* [ipsw appstore profile renew](/docs/cli/ipsw/appstore/profile/renew) - Renew an expired or invalid provisioning profile
|
||||
* [ipsw appstore profile rm](/docs/cli/ipsw/appstore/profile/rm) - Delete a provisioning profile that is used for app development or distribution
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ title: renew
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: renew
|
||||
description: Renew and expired or invalide provisioning profile
|
||||
description: Renew an expired or invalid provisioning profile
|
||||
---
|
||||
## ipsw appstore profile renew
|
||||
|
||||
Renew and expired or invalide provisioning profile
|
||||
Renew an expired or invalid provisioning profile
|
||||
|
||||
```
|
||||
ipsw appstore profile renew <NAME> [flags]
|
||||
|
||||
@@ -23,8 +23,6 @@ ipsw diff <IPSW> <IPSW> [flags]
|
||||
❯ ipsw diff <old.ipsw> <new.ipsw> --output <output/folder> --markdown
|
||||
--kdk /Library/Developer/KDKs/KDK_15.0_24A5264n.kdk/System/Library/Kernels/kernel.release.t6031
|
||||
--kdk /Library/Developer/KDKs/KDK_15.0_24A5279h.kdk/System/Library/Kernels/kernel.release.t6031
|
||||
# Use a previously saved .idiff file
|
||||
❯ ipsw diff --in <path/to/.idiff> --output <output/folder> --markdown
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -38,7 +36,6 @@ ipsw diff <IPSW> <IPSW> [flags]
|
||||
--fw Diff other firmwares
|
||||
-h, --help help for diff
|
||||
--html Save diff as HTML file
|
||||
-i, --in string Path to IPSW .idiff file
|
||||
--json Save diff as JSON file
|
||||
-k, --kdk stringArray Path to KDKs to diff
|
||||
--launchd Diff launchd configs
|
||||
|
||||
@@ -44,7 +44,7 @@ ipsw download appledb [flags]
|
||||
--dyld Extract dyld_shared_cache(s) from remote OTA
|
||||
--fcs-keys Download AEA1 DMG fcs-key pem files
|
||||
--fcs-keys-json Download AEA1 DMG fcs-keys as JSON
|
||||
-f, --flat Do NOT perserve directory structure when downloading with --pattern
|
||||
-f, --flat Do NOT preserve directory structure when downloading with --pattern
|
||||
-h, --help help for appledb
|
||||
--insecure do not verify ssl certs
|
||||
-j, --json Dump DB query results as JSON
|
||||
|
||||
@@ -40,7 +40,7 @@ ipsw download ipsw [flags]
|
||||
-a, --dyld-arch stringArray dyld_shared_cache architecture(s) to remote extract
|
||||
--fcs-keys Download AEA1 DMG fcs-key pem files
|
||||
--fcs-keys-json Download AEA1 DMG fcs-keys as JSON
|
||||
-f, --flat Do NOT perserve directory structure when downloading with --pattern
|
||||
-f, --flat Do NOT preserve directory structure when downloading with --pattern
|
||||
-h, --help help for ipsw
|
||||
--ibridge Download iBridge IPSWs
|
||||
--insecure do not verify ssl certs
|
||||
|
||||
@@ -43,7 +43,7 @@ ipsw download ota [options] [flags]
|
||||
--driver-kit Extract DriverKit dyld_shared_cache(s) from remote OTA zip
|
||||
--dyld Extract dyld_shared_cache(s) from remote OTA zip
|
||||
-a, --dyld-arch stringArray dyld_shared_cache architecture(s) to remote extract
|
||||
-f, --flat Do NOT perserve directory structure when downloading with --pattern
|
||||
-f, --flat Do NOT preserve directory structure when downloading with --pattern
|
||||
-h, --help help for ota
|
||||
--info Show all the latest OTAs available
|
||||
--insecure do not verify ssl certs
|
||||
|
||||
@@ -39,7 +39,7 @@ ipsw download wiki [flags]
|
||||
-y, --confirm do not prompt user for confirmation
|
||||
--db string Path to local JSON database (will use CWD by default) (default "wiki_db.json")
|
||||
-d, --device string iOS Device (i.e. iPhone11,2)
|
||||
-f, --flat Do NOT perserve directory structure when downloading with --pattern
|
||||
-f, --flat Do NOT preserve directory structure when downloading with --pattern
|
||||
-h, --help help for wiki
|
||||
--insecure do not verify ssl certs
|
||||
--ipsw Download IPSWs
|
||||
|
||||
@@ -17,12 +17,12 @@ ipsw dyld a2f <DSC> <ADDR> [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-c, --cache string Path to .a2s addr to sym cache file (speeds up analysis)
|
||||
-h, --help help for a2f
|
||||
-i, --in string Path to file containing list of addresses to lookup
|
||||
-j, --json Output as JSON
|
||||
-o, --out string Path to output JSON file
|
||||
-s, --slide uint dyld_shared_cache slide to apply
|
||||
-c, --cache string Path to .a2s addr to sym cache file (speeds up analysis)
|
||||
-h, --help help for a2f
|
||||
-i, --in string Path to file containing list of addresses to lookup
|
||||
-j, --json Output as JSON
|
||||
-o, --output string Path to output JSON file
|
||||
-s, --slide uint dyld_shared_cache slide to apply
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -17,12 +17,12 @@ ipsw dyld symaddr <DSC> [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --all Find all symbol matches
|
||||
-b, --binds Also search LC_DYLD_INFO binds
|
||||
-h, --help help for symaddr
|
||||
-i, --image string dylib image to search
|
||||
--in string Path to JSON file containing list of symbols to lookup
|
||||
--out string Path to output JSON file
|
||||
-a, --all Find all symbol matches
|
||||
-b, --binds Also search LC_DYLD_INFO binds
|
||||
-h, --help help for symaddr
|
||||
-i, --image string dylib image to search
|
||||
--in string Path to JSON file containing list of symbols to lookup
|
||||
-o, --output string Path to output JSON file
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -55,7 +55,7 @@ $ ipsw extract --dyld --driverkit macOS.ipsw
|
||||
-x, --exclave Extract Exclave Bundle
|
||||
--fcs-key Extract AEA1 DMG fcs-key pem files
|
||||
-f, --files Extract File System files
|
||||
--flat Do NOT perserve directory structure when extracting
|
||||
--flat Do NOT preserve directory structure when extracting
|
||||
-h, --help help for extract
|
||||
--iboot Extract iBoot
|
||||
--insecure do not verify ssl certs
|
||||
|
||||
@@ -22,6 +22,7 @@ ipsw fw aea [flags]
|
||||
-h, --help help for aea
|
||||
--id Print AEA file ID
|
||||
-i, --info Print info
|
||||
--insecure Allow insecure connections
|
||||
-k, --key Get archive decryption key
|
||||
-b, --key-val string Base64 encoded symmetric encryption key
|
||||
-o, --output string Folder to extract files to
|
||||
|
||||
@@ -33,5 +33,5 @@ ipsw idev amfi [flags]
|
||||
### SEE ALSO
|
||||
|
||||
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
|
||||
* [ipsw idev amfi dev](/docs/cli/ipsw/idev/amfi/dev) - Enabled Developer Mode on device
|
||||
* [ipsw idev amfi dev](/docs/cli/ipsw/idev/amfi/dev) - Enable Developer Mode on device
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ title: dev
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: dev
|
||||
description: Enabled Developer Mode on device
|
||||
description: Enable Developer Mode on device
|
||||
---
|
||||
## ipsw idev amfi dev
|
||||
|
||||
Enabled Developer Mode on device
|
||||
Enable Developer Mode on device
|
||||
|
||||
```
|
||||
ipsw idev amfi dev [flags]
|
||||
|
||||
@@ -4,11 +4,11 @@ title: ls
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: ls
|
||||
description: List installed provision profiles
|
||||
description: List installed provisioning profiles
|
||||
---
|
||||
## ipsw idev prof ls
|
||||
|
||||
List installed provision profiles
|
||||
List installed provisioning profiles
|
||||
|
||||
```
|
||||
ipsw idev prof ls [flags]
|
||||
|
||||
@@ -35,7 +35,7 @@ ipsw idev prof [flags]
|
||||
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
|
||||
* [ipsw idev prof cloud](/docs/cli/ipsw/idev/prof/cloud) - Get cloud configuration
|
||||
* [ipsw idev prof install](/docs/cli/ipsw/idev/prof/install) - Install profile
|
||||
* [ipsw idev prof ls](/docs/cli/ipsw/idev/prof/ls) - List installed provision profiles
|
||||
* [ipsw idev prof ls](/docs/cli/ipsw/idev/prof/ls) - List installed provisioning profiles
|
||||
* [ipsw idev prof rm](/docs/cli/ipsw/idev/prof/rm) - Remove profile by name
|
||||
* [ipsw idev prof wifi](/docs/cli/ipsw/idev/prof/wifi) - Change Wi-Fi power state
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ title: clear
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: clear
|
||||
description: Remove all provision profiles
|
||||
description: Remove all provisioning profiles
|
||||
---
|
||||
## ipsw idev prov clear
|
||||
|
||||
Remove all provision profiles
|
||||
Remove all provisioning profiles
|
||||
|
||||
```
|
||||
ipsw idev prov clear [flags]
|
||||
|
||||
@@ -4,11 +4,11 @@ title: dump
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: dump
|
||||
description: Dump installed provision profiles
|
||||
description: Dump installed provisioning profiles
|
||||
---
|
||||
## ipsw idev prov dump
|
||||
|
||||
Dump installed provision profiles
|
||||
Dump installed provisioning profiles
|
||||
|
||||
```
|
||||
ipsw idev prov dump [flags]
|
||||
|
||||
@@ -4,11 +4,11 @@ title: install
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: install
|
||||
description: Install a provision profile (.mobileprovision file)
|
||||
description: Install a provisioning profile (.mobileprovision file)
|
||||
---
|
||||
## ipsw idev prov install
|
||||
|
||||
Install a provision profile (.mobileprovision file)
|
||||
Install a provisioning profile (.mobileprovision file)
|
||||
|
||||
```
|
||||
ipsw idev prov install <PROV> [flags]
|
||||
|
||||
@@ -4,11 +4,11 @@ title: ls
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: ls
|
||||
description: List installed provision profiles
|
||||
description: List installed provisioning profiles
|
||||
---
|
||||
## ipsw idev prov ls
|
||||
|
||||
List installed provision profiles
|
||||
List installed provisioning profiles
|
||||
|
||||
```
|
||||
ipsw idev prov ls [flags]
|
||||
|
||||
@@ -33,9 +33,9 @@ ipsw idev prov [flags]
|
||||
### SEE ALSO
|
||||
|
||||
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
|
||||
* [ipsw idev prov clear](/docs/cli/ipsw/idev/prov/clear) - Remove all provision profiles
|
||||
* [ipsw idev prov dump](/docs/cli/ipsw/idev/prov/dump) - Dump installed provision profiles
|
||||
* [ipsw idev prov install](/docs/cli/ipsw/idev/prov/install) - Install a provision profile (.mobileprovision file)
|
||||
* [ipsw idev prov ls](/docs/cli/ipsw/idev/prov/ls) - List installed provision profiles
|
||||
* [ipsw idev prov rm](/docs/cli/ipsw/idev/prov/rm) - Remove a provision profile
|
||||
* [ipsw idev prov clear](/docs/cli/ipsw/idev/prov/clear) - Remove all provisioning profiles
|
||||
* [ipsw idev prov dump](/docs/cli/ipsw/idev/prov/dump) - Dump installed provisioning profiles
|
||||
* [ipsw idev prov install](/docs/cli/ipsw/idev/prov/install) - Install a provisioning profile (.mobileprovision file)
|
||||
* [ipsw idev prov ls](/docs/cli/ipsw/idev/prov/ls) - List installed provisioning profiles
|
||||
* [ipsw idev prov rm](/docs/cli/ipsw/idev/prov/rm) - Remove a provisioning profile
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ title: rm
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: rm
|
||||
description: Remove a provision profile
|
||||
description: Remove a provisioning profile
|
||||
---
|
||||
## ipsw idev prov rm
|
||||
|
||||
Remove a provision profile
|
||||
Remove a provisioning profile
|
||||
|
||||
```
|
||||
ipsw idev prov rm <PROV> [flags]
|
||||
|
||||
@@ -46,7 +46,7 @@ Download and Parse IPSWs (and SO much more)
|
||||
* [ipsw mdevs](/docs/cli/ipsw/mdevs) - List all MobileDevices in IPSW
|
||||
* [ipsw mount](/docs/cli/ipsw/mount) - Mount DMG from IPSW
|
||||
* [ipsw ota](/docs/cli/ipsw/ota) - Parse OTAs
|
||||
* [ipsw pbzx](/docs/cli/ipsw/pbzx) - Decompess pbzx files
|
||||
* [ipsw pbzx](/docs/cli/ipsw/pbzx) - Decompress pbzx files
|
||||
* [ipsw pkg](/docs/cli/ipsw/pkg) - 🚧 List contents of a DMG/PKG file
|
||||
* [ipsw plist](/docs/cli/ipsw/plist) - Dump plist as JSON
|
||||
* [ipsw ssh](/docs/cli/ipsw/ssh) - SSH into a jailbroken device
|
||||
|
||||
@@ -37,7 +37,7 @@ ipsw kernel [flags]
|
||||
* [ipsw kernel dwarf](/docs/cli/ipsw/kernel/dwarf) - 🚧 Dump DWARF debug information
|
||||
* [ipsw kernel extract](/docs/cli/ipsw/kernel/extract) - Extract KEXT(s) from kernelcache
|
||||
* [ipsw kernel ida](/docs/cli/ipsw/kernel/ida) - 🚧 Analyze kernelcache in IDA Pro
|
||||
* [ipsw kernel kexts](/docs/cli/ipsw/kernel/kexts) - List kernel extentions
|
||||
* [ipsw kernel kexts](/docs/cli/ipsw/kernel/kexts) - List kernel extensions
|
||||
* [ipsw kernel mach](/docs/cli/ipsw/kernel/mach) - Dump kernelcache mach_traps
|
||||
* [ipsw kernel mig](/docs/cli/ipsw/kernel/mig) - Dump kernelcache mig subsystem
|
||||
* [ipsw kernel symbolicate](/docs/cli/ipsw/kernel/symbolicate) - Symbolicate kernelcache
|
||||
|
||||
@@ -4,11 +4,11 @@ title: kexts
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: kexts
|
||||
description: List kernel extentions
|
||||
description: List kernel extensions
|
||||
---
|
||||
## ipsw kernel kexts
|
||||
|
||||
List kernel extentions
|
||||
List kernel extensions
|
||||
|
||||
```
|
||||
ipsw kernel kexts <kernelcache> [flags]
|
||||
|
||||
@@ -27,7 +27,7 @@ ipsw macho patch add <MACHO> <LC> <LC_FIELDS...> [flags]
|
||||
-h, --help help for add
|
||||
-o, --output string Output new file
|
||||
-f, --overwrite Overwrite file
|
||||
-s, --re-sign Adhoc sign file
|
||||
-s, --re-sign Ad-hoc sign file
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -27,7 +27,7 @@ ipsw macho patch rm <MACHO> <LC> <LC_FIELDS...> [flags]
|
||||
-h, --help help for rm
|
||||
-o, --output string Output new file
|
||||
-f, --overwrite Overwrite file
|
||||
-s, --re-sign Adhoc sign file
|
||||
-s, --re-sign Ad-hoc sign file
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -29,15 +29,19 @@ $ ipsw mount fs iPod5,1_7.1.2_11D257_Restore.ipsw --lookup
|
||||
# Mount dyld shared cache (exc) DMG with AEA pem DB
|
||||
$ ipsw mount exc iPhone.ipsw --pem-db /path/to/pem.json
|
||||
|
||||
# Mount to a custom mount point
|
||||
$ ipsw mount fs iPhone.ipsw --mount-point /mnt/ios-filesystem
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for mount
|
||||
-k, --key string DMG key
|
||||
--lookup Lookup DMG keys on theapplewiki.com
|
||||
--pem-db string AEA pem DB JSON file
|
||||
-h, --help help for mount
|
||||
-k, --key string DMG key
|
||||
--lookup Lookup DMG keys on theapplewiki.com
|
||||
-m, --mount-point string Custom mount point (default: /tmp/<dmg>.mount)
|
||||
--pem-db string AEA pem DB JSON file
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -4,11 +4,11 @@ title: pbzx
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: pbzx
|
||||
description: Decompess pbzx files
|
||||
description: Decompress pbzx files
|
||||
---
|
||||
## ipsw pbzx
|
||||
|
||||
Decompess pbzx files
|
||||
Decompress pbzx files
|
||||
|
||||
```
|
||||
ipsw pbzx [flags]
|
||||
|
||||
@@ -4,11 +4,11 @@ title: debugserver
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
sidebar_label: debugserver
|
||||
description: Prep device for remote debugging
|
||||
description: Prepare device for remote debugging
|
||||
---
|
||||
## ipsw ssh debugserver
|
||||
|
||||
Prep device for remote debugging
|
||||
Prepare device for remote debugging
|
||||
|
||||
```
|
||||
ipsw ssh debugserver [flags]
|
||||
|
||||
@@ -38,6 +38,6 @@ ipsw ssh [flags]
|
||||
### SEE ALSO
|
||||
|
||||
* [ipsw](/docs/cli/ipsw) - Download and Parse IPSWs (and SO much more)
|
||||
* [ipsw ssh debugserver](/docs/cli/ipsw/ssh/debugserver) - Prep device for remote debugging
|
||||
* [ipsw ssh debugserver](/docs/cli/ipsw/ssh/debugserver) - Prepare device for remote debugging
|
||||
* [ipsw ssh shsh](/docs/cli/ipsw/ssh/shsh) - Get SHSH blobs from device
|
||||
|
||||
|
||||
Generated
-19900
File diff suppressed because it is too large
Load Diff
+35
-2
@@ -23,18 +23,51 @@
|
||||
"@docusaurus/theme-mermaid": "^3.8.1",
|
||||
"@docusaurus/theme-search-algolia": "^3.8.1",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@supabase/supabase-js": "^2.50.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.263.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"prism-themes": "^1.9.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"redocusaurus": "^2.5.0"
|
||||
"redocusaurus": "^2.5.0",
|
||||
"tailwind-merge": "^1.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
"@docusaurus/tsconfig": "^3.8.1",
|
||||
"@docusaurus/types": "^3.8.1"
|
||||
"@docusaurus/types": "^3.8.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20.5.2",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"postcss": "^8.4.29",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
Generated
+1796
-57
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,79 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
@@ -0,0 +1,160 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user