Compare commits

...

111 commits

Author SHA1 Message Date
Kroese
297714126c
build: Update QEMU base image to v7.29 (#377)
Some checks failed
Update / dockerHubDescription (push) Has been cancelled
2025-11-22 05:00:34 +01:00
renovate[bot]
056dfdd026
chore(deps): update actions/checkout action to v6 (#375) 2025-11-21 12:35:52 +01:00
Kroese
4cdd13585d
fix: Continue booting when Fiano fails (#376)
Add error handling for logo replacement in BIOS
2025-11-21 12:33:10 +01:00
Kroese
1b3f506bbe
fix: Update Workspaces configuration (#374) 2025-11-14 06:33:48 +01:00
Kroese
9288c856e4
build: Update QEMU base image to v7.28 (#372) 2025-11-12 07:07:54 +01:00
Kroese
67a172743c
build: Use Github token (#366) 2025-10-29 14:05:31 +01:00
Kroese
333948ef19
fix: Inherit owner from parent folder (#359) 2025-10-22 04:03:24 +02:00
Kroese
dd59b97af6
build: Add code quality checks (#363) 2025-10-22 01:02:41 +02:00
Kroese
5c26b95ee4
build: Update QEMU base image to v7.27 (#362) 2025-10-21 23:19:57 +02:00
Kroese
b3450d11eb
docs: Update docker run command (#361) 2025-10-21 23:15:38 +02:00
Kroese
61ff166d16
build: Add review workflow for shell formatting (#360) 2025-10-21 22:38:09 +02:00
Kroese
211601d12b
build: Update QEMU base image to v7.26 (#358) 2025-10-19 18:02:33 +02:00
Kroese
e032e349ee
feat: Improve Github Codespaces configuration (#357) 2025-10-19 12:48:57 +02:00
Kroese
2f0e962edd
feat: Improve Github Codespaces configuration (#356) 2025-10-19 10:40:37 +02:00
Kroese
05a699ba9a
feat: Improve Github Codespaces configuration (#355) 2025-10-19 01:11:57 +02:00
Kroese
72468237d3
fix: Remove version variable in Codespaces (#354) 2025-10-17 19:02:01 +02:00
Kroese
20068d21a3
fix: Remove hostRequirements in Codespaces (#353) 2025-10-17 18:57:16 +02:00
Kroese
6672deb920
fix: Use temporary file when extracting ROM (#352) 2025-10-17 17:18:21 +02:00
Kroese
fb7cd67691
feat: Improve Github Codespaces configuration (#351) 2025-10-17 13:35:52 +02:00
Kroese
c3d9de925c
build: Update QEMU base image version to v7.25 (#350) 2025-10-17 13:33:28 +02:00
Kroese
b1d47f113b
feat: Load memory module (#349) 2025-10-17 13:31:46 +02:00
Kroese
8def1efbc0
build: Validate JSON and YML files (#348) 2025-10-17 10:05:33 +02:00
Kroese
9a8afcf237
feat: Make macOS version selectable in Codespaces (#347) 2025-10-16 08:55:31 +02:00
Kroese
e461eba7b3
feat: Improve Github Codespaces configuration (#346) 2025-10-16 00:01:30 +02:00
Kroese
3935b89941
feat: Improve Github Codespaces configuration (#345) 2025-10-15 23:45:21 +02:00
Kroese
9c8458d30b
fix: Use Skylake CPU model for macOS 14+ on AMD (#341) 2025-10-15 21:33:53 +02:00
Kroese
61d30d1672
docs: Add warning about AMD processors (#343) 2025-10-15 21:31:32 +02:00
Kroese
0daaa39547
docs: Add info about Migration Assistant (#344) 2025-10-15 21:30:24 +02:00
Kroese
2d21f714c3
feat: Add custom .yml for Github Codespaces (#342) 2025-10-15 11:53:22 +02:00
Kroese
d3c2d44855
build: Update QEMU base image to v7.24 (#339) 2025-10-13 15:06:02 +02:00
Kroese
cfd279b7e1
build: Update QEMU base image version to v7.23 (#336) 2025-10-12 02:02:37 +02:00
Kroese
8423965cf8
build: Run check for all files (#335)
Removed specific paths from pull request trigger.
2025-10-12 00:21:57 +02:00
Kroese
4c529524ad
build: Update QEMU base image to v7.22 (#332) 2025-10-08 22:06:07 +02:00
Kroese
7f1202725c
build: Update QEMU base image to v7.21 (#330) 2025-10-07 01:17:59 +02:00
Kroese
6c6f929adf
fix: Correct Tahoe version number (#329) 2025-10-03 11:17:16 +02:00
Kroese
feac11ef9a
feat: Add startup hook (#328) 2025-10-03 11:15:21 +02:00
renovate[bot]
9d19aeeaf3
chore(deps): update qemux/qemu docker tag to v7.20 (#327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 05:42:16 +02:00
renovate[bot]
edaa2d2bfc
chore(deps): update peter-evans/dockerhub-description action to v5 (#325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 23:40:04 +02:00
Kroese
a92a2cb0a0
build: Update QEMU base image version to 7.19 (#324) 2025-10-01 13:16:31 +02:00
Kroese
944d58a982
docs: Readme (#323) 2025-09-30 18:01:16 +02:00
Kroese
eda63efc6e
docs: Add macOS Tahoe support (#322) 2025-09-30 17:26:11 +02:00
Kroese
45b966703e
feat: Set default to Sonoma (#321) 2025-09-30 11:33:07 +02:00
Kroese
1f31b50ff8
feat: Added custom BIOS logo (#319) 2025-09-30 01:16:08 +02:00
renovate[bot]
05dad712f3
chore(deps): update qemux/qemu docker tag to v7.18 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 00:47:56 +02:00
Kroese
4e804a1ef9
feat: Update warning messages (#314) 2025-09-24 16:42:50 +02:00
Kroese
5b25e4c64d
build: Update QEMU base image version to 7.17 (#315) 2025-09-24 16:41:59 +02:00
Kroese
f9ea2f2781
fix: Move KVM warning (#313) 2025-09-23 16:16:11 +02:00
Kroese
35c7419044
feat: Disable VMX cpu flag (#312) 2025-09-23 15:24:23 +02:00
Kroese
8e7572b1ff
feat: Add additional KVM check (#311) 2025-09-23 08:57:35 +02:00
Kroese
125f14164b
feat: Disable unsupported CPU flags in TCG mode (#310) 2025-09-23 08:44:48 +02:00
Kroese
1ed6749ccd
build: Update QEMU base image to v7.16 (#309) 2025-09-22 22:02:37 +02:00
renovate[bot]
fc99211115
chore(deps): update hadolint/hadolint-action action to v3.3.0 (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 21:34:24 +02:00
Kroese
d4add50eb4
fix: Variable expansion in retry loop (#308) 2025-09-22 21:32:25 +02:00
Kroese
c787b8dd13
feat: Show delay progress (#306)
Added a delay function to handle retry messages for failed downloads.
2025-09-22 07:21:26 +02:00
Kroese
54e08a9425
revert: Simplify conditional checks (#305)
This reverts commit 733266fd6c.
2025-09-21 23:06:48 +02:00
Kroese
733266fd6c
fix: Simplify conditional checks (#304) 2025-09-21 15:04:44 +02:00
Kroese
08ac3ab823
build: Update QEMU base image to v7.15 (#303)
Updated QEMU base image version from 7.14 to 7.15.
2025-09-21 04:47:39 +02:00
Kroese
a737399811
fix: Simplify conditional checks (#302) 2025-09-20 23:48:56 +02:00
Kroese
109b84fd6f
feat: Resume failed download (#301) 2025-09-20 14:46:28 +02:00
renovate[bot]
fb044b9158
chore(deps): update qemux/qemu docker tag to v7.14 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 23:47:29 +02:00
Kroese
a82c3b58fd
feat: Cleanup old files before installation (#299) 2025-09-17 18:35:00 +02:00
renovate[bot]
0cd494674c
chore(deps): update qemux/qemu docker tag to v7.13 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 18:27:20 +02:00
Kroese
7d85112d12
feat: Restrict AMD CPU's to a single core (#297) 2025-09-15 22:09:51 +02:00
Kroese
f15a2d6ae5
feat: Create subfolder only after version is checked (#296) 2025-09-15 17:58:15 +02:00
Kroese
827f458cae
feat: Seperate subfolders for each version (#295)
Allows for easier version management as there is no need to clear the /storage folder anymore when switching between macOS releases.
2025-09-15 17:37:53 +02:00
Kroese
27a66f4748
feat: Remove leading and trailing spaces from VERSION (#294) 2025-09-15 14:18:24 +02:00
Kroese
9fb20e6ef5
build: Disable automatic builds (#293)
Removed push trigger and paths to ignore from build workflow.
2025-09-14 11:52:53 +02:00
renovate[bot]
e1977b9cdc
chore(deps): update hadolint/hadolint-action action to v3.2.0 (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 20:18:38 +02:00
Kroese
db7d2d32a4
feat: Add syntax parser directive (#286) 2025-08-27 21:39:13 +02:00
dependabot[bot]
fa9319e74e
build(deps): bump alpine from 3.21 to 3.22 (#263)
Bumps alpine from 3.21 to 3.22.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.22'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 21:06:11 +02:00
dependabot[bot]
b6a318f6e9
build(deps): bump actions/checkout from 4 to 5 (#282)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 20:58:56 +02:00
renovate[bot]
d93c75b979
chore(deps): update qemux/qemu docker tag to v7.12 (#257) 2025-05-06 06:40:41 +02:00
Kroese
9115d132f6
docs: Add input types (#251) 2025-04-23 12:50:38 +02:00
Kroese
88c3f6219e
docs: Add quotes around $PWD (#247) 2025-04-16 12:13:46 +02:00
Kroese
e0a357091d
build: Update qemu-docker to v7.11 (#246) 2025-04-14 21:04:15 +02:00
Kroese
69b79d9ba9
build: Update qemu-docker to v7.10 (#245) 2025-04-08 07:58:04 +02:00
Kroese
132033171d
build: Update qemu-docker to v7.08 (#241) 2025-04-04 01:19:27 +02:00
Kroese
88a476ef4c
docs: Disk pass-through (#240) 2025-04-03 10:31:29 +02:00
Kroese
08255c5a45
build: Update OpenCore to 1.0.4 (#239) 2025-04-03 01:34:21 +02:00
Kroese
a410d8a123
build: Update qemu-docker to v7.07 (#236) 2025-03-28 20:34:27 +01:00
Kroese
6060d36387
docs: Github Codespaces (#234) 2025-03-27 01:47:11 +01:00
Kroese
0ffd7852fe
fix: Set BIOS variable (#233) 2025-03-27 00:18:30 +01:00
Kroese
431d9e544f
build: Update qemu-docker to v7.06 (#232) 2025-03-26 19:42:04 +01:00
Kroese
1bf46b7e0b
docs: Apple Account (#230) 2025-03-25 21:14:14 +01:00
Kroese
bbbc853178
docs: Github Codespaces (#229) 2025-03-25 14:44:55 +01:00
Kroese
453ec734ba
feat: Remove non-printable characters (#228) 2025-03-24 14:01:17 +01:00
renovate[bot]
cdf3e9df63
build: Update qemu-docker to v7.04 (#213) 2025-03-21 21:54:06 +01:00
Kroese
8e23be901f
docs: KVM information (#225) 2025-03-20 23:21:30 +01:00
Kroese
431b4b6ce9
docs: Compatibility chart (#224) 2025-03-20 23:07:18 +01:00
Kroese
a4735a328f
docs: Add Podman (#223) 2025-03-20 20:06:11 +01:00
Kroese
6d588e42e1
fix: Platform variable (#222) 2025-03-20 16:50:18 +01:00
Kroese
5698b772b3
build: Update qemu-docker to v7.03 (#220) 2025-03-20 10:07:33 +01:00
Kroese
874bfa197b
feat: Add devcontainer (#218) 2025-03-19 09:18:35 +01:00
Kroese
6d65c6cfbb
docs: Github Codespaces (#217) 2025-03-18 14:15:12 +01:00
Kroese
9e07cb5367
feat: Add devcontainer (#216) 2025-03-18 11:23:42 +01:00
Kroese
de76cca895
docs: Port number (#215) 2025-03-18 05:00:46 +01:00
Kroese
69fb95c90f
build: Update qemu-docker to v7.01 (#214) 2025-03-18 03:58:10 +01:00
Kroese
d07d09ea48
feat: Switch to port 80 (#212) 2025-03-17 16:44:07 +01:00
Kroese
422ded1364
feat: Improved download method (#211) 2025-03-16 13:59:01 +01:00
Kroese
3277ec9eea
docs: File sharing (#210) 2025-03-16 07:31:41 +01:00
Kroese
2f0ee294e0
docs: Readme (#209) 2025-03-16 07:04:15 +01:00
Kroese
ad46446f1b
feat: Verify clock source (#207) 2025-03-15 14:10:59 +01:00
renovate[bot]
e94516ebec
build: Update qemu-docker to v6.22 (#206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 14:09:30 +01:00
renovate[bot]
876b858c69
build: Update qemu-docker to v6.21 (#205) 2025-03-12 21:56:19 +01:00
Kroese
35922a1714
docs: Use relative paths (#204) 2025-03-12 12:21:12 +01:00
Francois Liot
70f6feb864
docs: Kubernetes deployment (#202) 2025-03-07 00:41:03 +01:00
Kroese
5ccb6fde73
build: Update qemu-docker to v6.20 (#201) 2025-03-04 15:46:45 +01:00
Kroese
90e5afd771
build: Update qemu-docker to v6.19 (#200) 2025-03-03 12:28:21 +01:00
Kroese
0d270138f2
build: Update qemu-docker to v6.18 (#198) 2025-02-26 22:48:22 +01:00
Kroese
3768d0f3df
feat: Make app name configurable (#197)
Some checks failed
Build / Test (push) Has been cancelled
Build / Build (push) Has been cancelled
2025-02-25 15:13:03 +01:00
Kroese
8d10ce8f68
build: Update qemu-docker to v6.17 (#196)
Some checks are pending
Build / Test (push) Waiting to run
Build / Build (push) Blocked by required conditions
2025-02-25 06:02:18 +01:00
23 changed files with 677 additions and 708 deletions

View file

@ -0,0 +1,19 @@
{
"name": "macOS 15 (Sequoia)",
"service": "macos",
"containerEnv": {
"VERSION": "15"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -0,0 +1,19 @@
{
"name": "macOS 13 (Ventura)",
"service": "macos",
"containerEnv": {
"VERSION": "13"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -0,0 +1,19 @@
{
"name": "macOS 12 (Monterey)",
"service": "macos",
"containerEnv": {
"VERSION": "12"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -0,0 +1,19 @@
{
"name": "macOS 11 (Big Sur)",
"service": "macos",
"containerEnv": {
"VERSION": "11"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -0,0 +1,19 @@
{
"name": "macOS 10 (Catalina)",
"service": "macos",
"containerEnv": {
"VERSION": "10"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -0,0 +1,21 @@
services:
macos:
container_name: macos
image: ghcr.io/dockur/macos
environment:
RAM_SIZE: "half"
DISK_SIZE: "max"
CPU_CORES: "max"
devices:
- /dev/kvm
- /dev/net/tun
cap_add:
- NET_ADMIN
ports:
- 8006:8006
- 5900:5900/tcp
- 5900:5900/udp
volumes:
- ./macos:/storage
restart: on-failure
stop_grace_period: 2m

View file

@ -0,0 +1,19 @@
{
"name": "macOS 14 (Sonoma)",
"service": "macos",
"containerEnv": {
"VERSION": "14"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}

View file

@ -1,4 +1,5 @@
.dockerignore
.devcontainer
.git
.github
.gitignore

View file

@ -21,6 +21,7 @@ body:
attributes:
label: Docker compose
description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations:
required: true
- type: textarea
@ -28,6 +29,7 @@ body:
attributes:
label: Docker log
description: The logfile of the container (as shown by `docker logs macos`).
render: shell
validations:
required: true
- type: textarea

View file

@ -23,6 +23,7 @@ body:
attributes:
label: Docker compose
description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations:
required: true
- type: textarea
@ -30,6 +31,7 @@ body:
attributes:
label: Docker log
description: The logfile of the container (as shown by `docker logs macos`).
render: shell
validations:
required: true
- type: textarea

View file

@ -2,16 +2,6 @@ name: Build
on:
workflow_dispatch:
push:
branches:
- master
paths-ignore:
- '**/*.md'
- '**/*.yml'
- '.gitignore'
- '.dockerignore'
- '.github/**'
- '.github/workflows/**'
concurrency:
group: build
@ -32,7 +22,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-

View file

@ -7,14 +7,19 @@ jobs:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
SHELLCHECK_OPTS: -x --source-path=src -e SC1091 -e SC2001 -e SC2002 -e SC2034 -e SC2064 -e SC2153 -e SC2317 -e SC2028
- name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.1.0
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
ignore: DL3008,DL3018,DL3020,DL3029,DL3059
failure-threshold: warning
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v4
with:
yaml_exclude_regex: ".*\\kubernetes\\.yml$"

View file

@ -12,13 +12,15 @@ jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ secrets.DOCKERHUB_REPO }}
short-description: ${{ github.event.repository.description }}
readme-filepath: ./readme.md
-
name: Checkout repo
uses: actions/checkout@v6
-
name: Docker Hub Description
uses: peter-evans/dockerhub-description@v5
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ secrets.DOCKERHUB_REPO }}
short-description: ${{ github.event.repository.description }}
readme-filepath: ./readme.md

66
.github/workflows/review.yml vendored Normal file
View file

@ -0,0 +1,66 @@
on:
pull_request:
name: "Review"
permissions:
contents: read
pull-requests: write
checks: write
jobs:
review:
name: review
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Spelling
uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: warning
pattern: |
*.md
*.sh
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Hadolint
uses: reviewdog/action-hadolint@v1
with:
level: warning
reporter: github-pr-review
hadolint_ignore: DL3008 DL3018 DL3020 DL3029 DL3059
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: YamlLint
uses: reviewdog/action-yamllint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: ActionLint
uses: reviewdog/action-actionlint@v1
with:
level: warning
reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellformat
uses: reviewdog/action-shfmt@v1
with:
level: warning
shfmt_flags: "-i 2 -ci -bn"
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: Shellcheck
uses: reviewdog/action-shellcheck@v1
with:
level: warning
reporter: github-pr-review
shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2002 -e SC2034 -e SC2064 -e SC2153 -e SC2317 -e SC2028
github_token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,11 +1,6 @@
on:
workflow_dispatch:
pull_request:
paths:
- '**/*.sh'
- '.github/workflows/test.yml'
- '.github/workflows/check.yml'
- 'Dockerfile'
name: "Test"
permissions: {}

View file

@ -1,6 +1,8 @@
FROM --platform=$BUILDPLATFORM alpine:3.21 AS builder
# syntax=docker/dockerfile:1
ARG VERSION_OPENCORE="1.0.3"
FROM --platform=$BUILDPLATFORM alpine:3.22 AS builder
ARG VERSION_OPENCORE="1.0.4"
ARG REPO_OPENCORE="https://github.com/acidanthera/OpenCorePkg"
ADD $REPO_OPENCORE/releases/download/$VERSION_OPENCORE/OpenCore-$VERSION_OPENCORE-RELEASE.zip /tmp/opencore.zip
@ -10,7 +12,7 @@ RUN apk --update --no-cache add unzip && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/*
FROM scratch AS runner
COPY --from=qemux/qemu-docker:6.16 / /
COPY --from=qemux/qemu:7.29 / /
ARG VERSION_ARG="0.0"
ARG VERSION_KVM_OPENCORE="v21"
@ -25,10 +27,7 @@ ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \
apt-get update && \
apt-get --no-install-recommends -y install \
xxd \
fdisk \
mtools \
python3 && \
mtools && \
apt-get clean && \
echo "$VERSION_ARG" > /run/version && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@ -46,11 +45,11 @@ ADD --chmod=644 \
ADD $REPO_KVM_OPENCORE/releases/download/$VERSION_KVM_OPENCORE/OpenCore-$VERSION_KVM_OPENCORE.iso.gz /opencore.iso.gz
VOLUME /storage
EXPOSE 8006 5900
EXPOSE 5900 8006
ENV VERSION="13"
ENV VERSION="14"
ENV RAM_SIZE="4G"
ENV CPU_CORES="2"
ENV CPU_CORES="1"
ENV DISK_SIZE="64G"
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]

View file

@ -3,7 +3,7 @@ services:
image: dockurr/macos
container_name: macos
environment:
VERSION: "13"
VERSION: "14"
devices:
- /dev/kvm
- /dev/net/tun
@ -13,5 +13,7 @@ services:
- 8006:8006
- 5900:5900/tcp
- 5900:5900/udp
volumes:
- ./macos:/storage
restart: always
stop_grace_period: 2m

View file

@ -10,68 +10,77 @@ spec:
requests:
storage: 64Gi
---
apiVersion: v1
kind: Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: macos
labels:
name: macos
spec:
containers:
- name: macos
image: dockurr/macos
env:
- name: VERSION
value: "13"
- name: RAM_SIZE
value: "4G"
- name: CPU_CORES
value: "2"
- name: DISK_SIZE
value: "64G"
ports:
- containerPort: 8006
- containerPort: 5900
- containerPort: 5900
protocol: UDP
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /storage
name: storage
- mountPath: /dev/kvm
name: dev-kvm
- mountPath: /dev/net/tun
name: dev-tun
terminationGracePeriodSeconds: 120
volumes:
- name: storage
persistentVolumeClaim:
claimName: macos-pvc
- hostPath:
path: /dev/kvm
name: dev-kvm
- hostPath:
path: /dev/net/tun
type: CharDevice
name: dev-tun
replicas: 1
selector:
matchLabels:
app: macos
template:
metadata:
labels:
app: macos
spec:
containers:
- name: macos
image: dockurr/macos
env:
- name: VERSION
value: "14"
- name: DISK_SIZE
value: "64G"
ports:
- containerPort: 8006
name: http
protocol: TCP
- containerPort: 5900
name: vnc
protocol: TCP
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /storage
name: storage
- mountPath: /dev/kvm
name: dev-kvm
- mountPath: /dev/net/tun
name: dev-tun
terminationGracePeriodSeconds: 120
volumes:
- name: storage
persistentVolumeClaim:
claimName: macos-pvc
- hostPath:
path: /dev/kvm
name: dev-kvm
- hostPath:
path: /dev/net/tun
type: CharDevice
name: dev-tun
---
apiVersion: v1
kind: Service
metadata:
name: macos
spec:
internalTrafficPolicy: Cluster
ports:
- name: tcp-8006
port: 8006
- name: tcp-5900
port: 5900
- name: udp-5900
port: 5900
protocol: UDP
- name: http
port: 8006
protocol: TCP
targetPort: 8006
- name: vnc
port: 5900
protocol: TCP
targetPort: 5900
selector:
name: macos
type: NodePort
app: macos
type: ClusterIP

164
readme.md
View file

@ -1,4 +1,4 @@
<h1 align="center">OSX<br />
<h1 align="center">macOS<br />
<div align="center">
<a href="https://github.com/dockur/macos/"><img src="https://github.com/dockur/macos/raw/master/.github/logo.png" title="Logo" style="max-width:100%;" width="128" /></a>
</div>
@ -12,7 +12,7 @@
</div></h1>
OSX (macOS) inside a Docker container.
MacOS inside a Docker container.
## Features ✨
@ -22,7 +22,7 @@ OSX (macOS) inside a Docker container.
## Usage 🐳
Via Docker Compose:
##### Via Docker Compose:
```yaml
services:
@ -30,7 +30,7 @@ services:
image: dockurr/macos
container_name: macos
environment:
VERSION: "13"
VERSION: "14"
devices:
- /dev/kvm
- /dev/net/tun
@ -40,31 +40,27 @@ services:
- 8006:8006
- 5900:5900/tcp
- 5900:5900/udp
volumes:
- ./macos:/storage
restart: always
stop_grace_period: 2m
```
Via Docker CLI:
##### Via Docker CLI:
```bash
docker run -it --rm -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN --stop-timeout 120 dockurr/macos
docker run -it --rm --name macos -e "VERSION=14" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/macos:/storage" --stop-timeout 120 docker.io/dockurr/macos
```
Via Kubernetes:
##### Via Kubernetes:
```shell
kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/master/kubernetes.yml
```
## Compatibility ⚙️
##### Via Github Codespaces:
| **Product** | **Platform** | |
|---|---|---|
| Docker Engine | Linux| ✅ |
| Docker Desktop | Linux | ❌ |
| Docker Desktop | macOS | ❌ |
| Docker Desktop | Windows 11 | ✅ |
| Docker Desktop | Windows 10 | ❌ |
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dockur/macos)
## FAQ 💬
@ -72,29 +68,33 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
Very simple! These are the steps:
- Start the container and connect to [port 8006](http://localhost:8006) using your web browser.
- Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser.
- Choose `Disk Utility` and then select the largest `Apple Inc. VirtIO Block Media` disk.
- Click the `Erase` button to format the disk to APFS, and give it any recognizable name you like.
- Click the `Erase` button to format the disk to APFS, and give it any name you like.
- Close the current window and proceed the installation by clicking `Reinstall macOS`.
- When prompted where you want to install it, select the disk you just created previously.
- When prompted where you want to install it, select the disk you created previously.
- After all files are copied, select your region, language, and account settings.
- After all files are copied, select your region, language, and keyboard settings.
- When the `Migration Assistant` wants to transfer data, select `Not now` (bottom left).
- On the `Apple ID` screen, select `Set Up Later` (bottom left) and then proceed using `Skip`.
- On the `Create a Computer Account` screen, fill in a username and password and `Continue`.
Enjoy your brand new machine, and don't forget to star this repo!
### How do I select the macOS version?
### How do I select the version of macOS?
By default, macOS 13 (Ventura) will be installed, as it offers the best performance.
But you can add the `VERSION` environment variable to your compose file, in order to specify an alternative macOS version to be downloaded:
By default, macOS 14 (Sonoma) will be installed, but you can add the `VERSION` environment variable in order to specify an alternative:
```yaml
environment:
VERSION: "13"
VERSION: "15"
```
Select from the values below:
@ -107,16 +107,19 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
| `12` | macOS 12 | Monterey |
| `11` | macOS 11 | Big Sur |
> [!NOTE]
> Support for macOS 15 (Sequoia) is still in its infancy, as it does not allow you to sign in to your Apple Account yet.
### How do I change the storage location?
To change the storage location, include the following bind mount in your compose file:
```yaml
volumes:
- /var/osx:/storage
- ./macos:/storage
```
Replace the example path `/var/osx` with the desired storage folder.
Replace the example path `./macos` with the desired storage folder or named volume.
### How do I change the size of the disk?
@ -132,7 +135,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
### How do I change the amount of CPU or RAM?
By default, the container will be allowed to use a maximum of 2 CPU cores and 4 GB of RAM.
By default, macOS will be allowed to use a single CPU core and 4 GB of RAM.
If you want to adjust this, you can specify the desired amount using the following environment variables:
@ -142,6 +145,73 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
CPU_CORES: "4"
```
> [!IMPORTANT]
> If your system has an AMD processor (instead of Intel), it is not advisable to enable multiple cores before the installation is completed and you have verified that everything runs stable for a while. Because in many cases it will introduce issues, which are difficult to pinpoint if you do not have experience with its behavior on a single core first.
### How do I assign an individual IP address to the container?
By default, the container uses bridge networking, which shares the IP address with the host.
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
```bash
docker network create -d macvlan \
--subnet=192.168.0.0/24 \
--gateway=192.168.0.1 \
--ip-range=192.168.0.100/28 \
-o parent=eth0 vlan
```
Be sure to modify these values to match your local subnet.
Once you have created the network, change your compose file to look as follows:
```yaml
services:
macos:
container_name: macos
..<snip>..
networks:
vlan:
ipv4_address: 192.168.0.100
networks:
vlan:
external: true
```
An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default.
> [!IMPORTANT]
> This IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
### How can macOS acquire an IP address from my router?
After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for macOS to become part of your home network by requesting an IP from your router, just like your other devices.
To enable this mode, in which the container and macOS will have separate IP addresses, add the following lines to your compose file:
```yaml
environment:
DHCP: "Y"
devices:
- /dev/vhost-net
device_cgroup_rules:
- 'c *:* rwm'
```
### How do I pass-through a disk?
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way:
```yaml
devices:
- /dev/sdb:/disk1
- /dev/sdc1:/disk2
```
Use `/disk1` if you want it to become your main drive, and use `/disk2` and higher to add them as secondary drives.
### How do I pass-through a USB device?
To pass-through a USB device, first lookup its vendor and product id via the `lsusb` command, then add them to your compose file like this:
@ -153,11 +223,35 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
- /dev/bus/usb
```
### How do I share files with the host?
To share files with the host, add the following volume to your compose file:
```yaml
volumes:
- ./example:/shared
```
Then start macOS and execute the following command:
```shell
sudo -S mount_9p shared
```
In Finders menu bar, click on “Go Computer” to access this shared folder, it will show the contents of `./example`.
### How do I verify if my system supports KVM?
Only Linux and Windows 11 support KVM virtualization, macOS and Windows 10 do not unfortunately.
You can run the following commands in Linux to check your system:
First check if your software is compatible using this chart:
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
After that you can run the following commands in Linux to check your system:
```bash
sudo apt install cpu-checker
@ -172,16 +266,16 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/macos/refs/heads/maste
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
If you do not receive any error from `kvm-ok` but the container still complains about KVM, please check whether:
- you are not using "Docker Desktop for Linux" as it does not support KVM, instead make use of Docker Engine directly.
- it could help to add `privileged: true` to your compose file (or `sudo` to your `docker run` command), to rule out any permission issue.
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
### How do I run Windows in a container?
You can use [dockur/windows](https://github.com/dockur/windows) for that. It shares many of the same features, and even has completely automatic installation.
### How do I run a Linux desktop in a container?
You can use [qemus/qemu](https://github.com/qemus/qemu) in that case.
### Is this project legal?
Yes, this project contains only open-source code and does not distribute any copyrighted material. Neither does it try to circumvent any copyright protection measures. So under all applicable laws, this project will be considered legal.

View file

@ -2,13 +2,17 @@
set -Eeuo pipefail
# Docker environment variables
: "${SECURE:="off"}" # Secure boot
: "${BOOT_MODE:="macos"}" # Boot mode
BOOT_DESC=""
BOOT_OPTS=""
SECURE="off"
OVMF="/usr/share/OVMF"
msg="Configuring boot..."
html "$msg"
[[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
case "${HEIGHT,,}" in
"1080" )
DEST="$PROCESS"
@ -41,12 +45,23 @@ DEST="$STORAGE/$DEST"
if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then
[ ! -s "$OVMF/$ROM" ] || [ ! -f "$OVMF/$ROM" ] && error "UEFI boot file ($OVMF/$ROM) not found!" && exit 44
cp "$OVMF/$ROM" "$DEST.rom"
if [[ "${LOGO:-}" == [Nn]* ]]; then
cp "$OVMF/$ROM" "$DEST.tmp"
else
if ! /run/utk.bin "$OVMF/$ROM" replace_ffs LogoDXE "/var/www/img/${PROCESS,,}.ffs" save "$DEST.tmp"; then
warn "failed to add custom logo to BIOS!"
cp "$OVMF/$ROM" "$DEST.tmp"
fi
fi
mv "$DEST.tmp" "$DEST.rom"
! setOwner "$DEST.rom" && error "Failed to set the owner for \"$DEST.rom\" !"
fi
if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then
[ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45
cp "$OVMF/$VARS" "$DEST.vars"
cp "$OVMF/$VARS" "$DEST.tmp"
mv "$DEST.tmp" "$DEST.vars"
! setOwner "$DEST.vars" && error "Failed to set the owner for \"$DEST.vars\" !"
fi
BOOT_OPTS+=" -drive if=pflash,format=raw,readonly=on,file=$DEST.rom"
@ -139,28 +154,52 @@ if [ ! -f "$IMG" ]; then
rm -rf "$OUT"
if [[ "$DEBUG" == [Yy1]* ]]; then
info ""
info "Model: $MODEL"
info "Rom: $ROM"
info "Serial: $SN"
info "Board: $MLB"
info ""
fi
info ""
info "Model: $MODEL"
info "Rom: $ROM"
info "Serial: $SN"
info "Board: $MLB"
info ""
fi
! setOwner "$IMG" && error "Failed to set the owner for \"$IMG\" !"
BOOT_DRIVE_ID="OpenCore"
DISK_OPTS+=" -device virtio-blk-pci,drive=${BOOT_DRIVE_ID},bus=pcie.0,addr=0x5,bootindex=$BOOT_INDEX"
DISK_OPTS+=" -drive file=$IMG,id=$BOOT_DRIVE_ID,format=raw,cache=unsafe,readonly=on,if=none"
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
DEFAULT_FLAGS="vendor=GenuineIntel,vmware-cpuid-freq=on,-pdpe1gb"
DEFAULT_FLAGS="vendor=GenuineIntel,vmx=off,vmware-cpuid-freq=on,-pdpe1gb"
if [[ "$CPU_VENDOR" == "AuthenticAMD" || "${KVM:-}" == [Nn]* ]]; then
if [ -z "${CPU_MODEL:-}" ]; then
case "${VERSION,,}" in
"ventura" | "13"* )
CPU_MODEL="Haswell-noTSX" ;;
"monterey" | "12"* )
CPU_MODEL="Haswell-noTSX" ;;
"bigsur" | "big-sur" | "11"* )
CPU_MODEL="Haswell-noTSX" ;;
"catalina" | "10"* )
CPU_MODEL="Haswell-noTSX" ;;
*)
CPU_MODEL="Skylake-Client-v4"
DEFAULT_FLAGS+=",-spec-ctrl"
;;
esac
fi
if [[ "${KVM:-}" == [Nn]* ]] || [[ "${ARCH,,}" != "amd64" ]] || [[ "$OSTYPE" =~ ^darwin ]]; then
DEFAULT_FLAGS+=",-pcid,-tsc-deadline,-invpcid,-xsavec,-xsaves,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+smep,+xsave,+xsaveopt,+xgetbv1,+movbe,+rdrand,check"
else
DEFAULT_FLAGS+=",+pcid,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+smep,+xsave,+xsavec,+xsaves,+xsaveopt,+xgetbv1,+movbe,+rdrand,check"
fi
if [[ "$CPU_VENDOR" != "GenuineIntel" ]] || [[ "${KVM:-}" == [Nn]* ]]; then
[ -z "${CPU_MODEL:-}" ] && CPU_MODEL="Haswell-noTSX"
DEFAULT_FLAGS+=",+pcid,+ssse3,+sse4.2,+popcnt,+avx,+avx2,+aes,+fma,+bmi1,+bmi2,+smep,+xsave,+xsavec,+xsaveopt,+xgetbv1,+movbe,+rdrand,check"
fi
if [ -z "${CPU_FLAGS:-}" ]; then
@ -169,20 +208,26 @@ else
CPU_FLAGS="$DEFAULT_FLAGS,$CPU_FLAGS"
fi
SM_BIOS=""
CLOCKSOURCE="tsc"
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
[ -f "$CLOCK" ] && CLOCK=$(<"$CLOCK")
if [[ "${CLOCK,,}" == "kvm-clock" ]]; then
if [[ "$CPU_VENDOR" != "GenuineIntel" ]] && [[ "${CPU_CORES,,}" == "2" ]]; then
warn "Restricted processor to a single core because nested virtualization was detected!"
CPU_CORES="1"
else
warn "Nested virtualization was detected, this might cause issues running macOS!"
fi
fi
if [[ "${CLOCK,,}" == "hpet" ]]; then
warn "Your clocksource is HPET instead of TSC, this will cause issues running macOS!"
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot not found?"
else
result=$(<"$CLOCK")
result="${result//[![:print:]]/}"
case "${result,,}" in
"${CLOCKSOURCE,,}" )
if [[ "$CPU_VENDOR" == "GenuineIntel" && "$CPU_CORES" == "1" && "${KVM:-}" != [Nn]* ]]; then
CPU_CORES="2"
fi ;;
"kvm-clock" ) warn "Nested KVM virtualization detected, this might cause issues running macOS!" ;;
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected, this might cause issues running macOS!" ;;
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE', otherwise it will cause issues running macOS!" ;;
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE', otherwise it will cause issues running macOS!" ;;
esac
fi
case "$CPU_CORES" in
@ -190,7 +235,7 @@ case "$CPU_CORES" in
"5" ) CPU_CORES="4" ;;
"9" ) CPU_CORES="8" ;;
esac
case "$CPU_CORES" in
"1" | "2" | "4" | "8" ) SMP="$CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1" ;;
"6" | "7" ) SMP="$CPU_CORES,sockets=3,dies=1,cores=2,threads=1" ;;

View file

@ -1,22 +1,27 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${APP:="macOS"}"
: "${VGA:="vmware"}"
: "${DISK_TYPE:="blk"}"
APP="macOS"
SUPPORT="https://github.com/dockur/macos"
: "${PLATFORM:="x64"}"
: "${SUPPORT:="https://github.com/dockur/macos"}"
cd /run
. start.sh # Startup hook
. utils.sh # Load functions
. reset.sh # Initialize system
. server.sh # Start webserver
. install.sh # Get the OSX images
. disk.sh # Initialize disks
. display.sh # Initialize graphics
. network.sh # Initialize network
. boot.sh # Configure boot
. proc.sh # Initialize processor
. memory.sh # Check available memory
. config.sh # Configure arguments
. finish.sh # Finish initialization
trap - ERR

View file

@ -1,512 +0,0 @@
#!/usr/bin/env python3
"""
Gather recovery information for Macs.
Copyright (c) 2019, vit9696
https://github.com/acidanthera/OpenCorePkg/blob/master/Utilities/macrecovery/macrecovery.py
"""
import argparse
import hashlib
import json
import linecache
import os
import random
import struct
import string
import sys
try:
from urllib.request import Request, HTTPError, urlopen
from urllib.parse import urlparse
except ImportError:
print('ERROR: Python 2 is not supported, please use Python 3')
sys.exit(1)
SELF_DIR = os.path.dirname(os.path.realpath(__file__))
# MacPro7,1
RECENT_MAC = 'Mac-27AD2F918AE68F61'
MLB_ZERO = '00000000000000000'
MLB_VALID = 'F5K105303J9K3F71M'
MLB_PRODUCT = 'F5K00000000K3F700'
TYPE_SID = 16
TYPE_K = 64
TYPE_FG = 64
INFO_PRODUCT = 'AP'
INFO_IMAGE_LINK = 'AU'
INFO_IMAGE_HASH = 'AH'
INFO_IMAGE_SESS = 'AT'
INFO_SIGN_LINK = 'CU'
INFO_SIGN_HASH = 'CH'
INFO_SIGN_SESS = 'CT'
INFO_REQURED = [INFO_PRODUCT, INFO_IMAGE_LINK, INFO_IMAGE_HASH, INFO_IMAGE_SESS, INFO_SIGN_LINK, INFO_SIGN_HASH, INFO_SIGN_SESS]
def run_query(url, headers, post=None, raw=False):
if post is not None:
data = '\n'.join(entry + '=' + post[entry] for entry in post).encode()
else:
data = None
req = Request(url=url, headers=headers, data=data)
try:
response = urlopen(req)
if raw:
return response
return dict(response.info()), response.read()
except HTTPError as e:
print(f'ERROR: "{e}" when connecting to {url}')
sys.exit(1)
def generate_id(id_type, id_value=None):
return id_value or ''.join(random.choices(string.hexdigits[:16].upper(), k=id_type))
def product_mlb(mlb):
return '00000000000' + mlb[11:15] + '00'
def mlb_from_eeee(eeee):
if len(eeee) != 4:
print('ERROR: Invalid EEEE code length!')
sys.exit(1)
return f'00000000000{eeee}00'
# zhangyoufu https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7#gistcomment-3061078 2021-10-08
Apple_EFI_ROM_public_key_1 = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF
ChunkListHeader = struct.Struct('<4sIBBBxQQQ')
assert ChunkListHeader.size == 0x24
Chunk = struct.Struct('<I32s')
assert Chunk.size == 0x24
def verify_chunklist(cnkpath):
with open(cnkpath, 'rb') as f:
hash_ctx = hashlib.sha256()
data = f.read(ChunkListHeader.size)
hash_ctx.update(data)
magic, header_size, file_version, chunk_method, signature_method, chunk_count, chunk_offset, signature_offset = ChunkListHeader.unpack(data)
assert magic == b'CNKL'
assert header_size == ChunkListHeader.size
assert file_version == 1
assert chunk_method == 1
assert signature_method in [1, 2]
assert chunk_count > 0
assert chunk_offset == 0x24
assert signature_offset == chunk_offset + Chunk.size * chunk_count
for _ in range(chunk_count):
data = f.read(Chunk.size)
hash_ctx.update(data)
chunk_size, chunk_sha256 = Chunk.unpack(data)
yield chunk_size, chunk_sha256
digest = hash_ctx.digest()
if signature_method == 1:
data = f.read(256)
assert len(data) == 256
signature = int.from_bytes(data, 'little')
plaintext = int(f'0x1{"f"*404}003031300d060960864801650304020105000420{"0"*64}', 16) | int.from_bytes(digest, 'big')
assert pow(signature, 0x10001, Apple_EFI_ROM_public_key_1) == plaintext
elif signature_method == 2:
data = f.read(32)
assert data == digest
raise RuntimeError('Chunklist missing digital signature')
else:
raise NotImplementedError
assert f.read(1) == b''
def get_session(args):
headers = {
'Host': 'osrecovery.apple.com',
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
}
headers, _ = run_query('http://osrecovery.apple.com/', headers)
if args.verbose:
print('Session headers:')
for header in headers:
print(f'{header}: {headers[header]}')
for header in headers:
if header.lower() == 'set-cookie':
cookies = headers[header].split('; ')
for cookie in cookies:
return cookie if cookie.startswith('session=') else ...
raise RuntimeError('No session in headers ' + str(headers))
def get_image_info(session, bid, mlb=MLB_ZERO, diag=False, os_type='default', cid=None):
headers = {
'Host': 'osrecovery.apple.com',
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
'Cookie': session,
'Content-Type': 'text/plain',
}
post = {
'cid': generate_id(TYPE_SID, cid),
'sn': mlb,
'bid': bid,
'k': generate_id(TYPE_K),
'fg': generate_id(TYPE_FG)
}
if diag:
url = 'http://osrecovery.apple.com/InstallationPayload/Diagnostics'
else:
url = 'http://osrecovery.apple.com/InstallationPayload/RecoveryImage'
post['os'] = os_type
headers, output = run_query(url, headers, post)
output = output.decode('utf-8')
info = {}
for line in output.split('\n'):
try:
key, value = line.split(': ')
info[key] = value
except KeyError:
continue
except ValueError:
continue
for k in INFO_REQURED:
if k not in info:
raise RuntimeError(f'Missing key {k}')
return info
def save_image(url, sess, filename='', directory=''):
purl = urlparse(url)
headers = {
'Host': purl.hostname,
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
'Cookie': '='.join(['AssetToken', sess])
}
if not os.path.exists(directory):
os.makedirs(directory)
if filename == '':
filename = os.path.basename(purl.path)
if filename.find(os.sep) >= 0 or filename == '':
raise RuntimeError('Invalid save path ' + filename)
print(f'Saving {url} to {directory}{os.sep}{filename}...')
with open(os.path.join(directory, filename), 'wb') as fh:
response = run_query(url, headers, raw=True)
headers = dict(response.headers)
totalsize = -1
for header in headers:
if header.lower() == 'content-length':
totalsize = int(headers[header])
break
size = 0
last = 0
while True:
chunk = response.read(2**20)
if not chunk:
break
fh.write(chunk)
size += len(chunk)
if totalsize > 0:
progress = size / totalsize
if (progress - last) >= 0.01 or progress >= 1:
last = progress
print(f'\r{progress*100:.1f}% downloaded', end='')
else:
# Fallback if Content-Length isn't available
progress = size / (2**20)
if (progress - last) >= 10:
last = progress
print(f'\r{progress} MB downloaded...', end='')
sys.stdout.flush()
print('\nDownload complete!')
return os.path.join(directory, os.path.basename(filename))
def verify_image(dmgpath, cnkpath):
print('Verifying image with chunklist...')
with open(dmgpath, 'rb') as dmgf:
last=0
for cnkcount, (cnksize, cnkhash) in enumerate(verify_chunklist(cnkpath), 1):
if (cnkcount - last) >= 10:
last = cnkcount
print(f'\rChunk {cnkcount} ({cnksize} bytes)', end='')
sys.stdout.flush()
cnk = dmgf.read(cnksize)
if len(cnk) != cnksize:
raise RuntimeError(f'Invalid chunk {cnkcount} size: expected {cnksize}, read {len(cnk)}')
if hashlib.sha256(cnk).digest() != cnkhash:
raise RuntimeError(f'Invalid chunk {cnkcount}: hash mismatch')
if dmgf.read(1) != b'':
raise RuntimeError('Invalid image: larger than chunklist')
print('\nImage verification complete!')
def action_download(args):
"""
Reference information for queries:
Recovery latest:
cid=3076CE439155BA14
sn=...
bid=Mac-E43C1C25D4880AD6
k=4BE523BB136EB12B1758C70DB43BDD485EBCB6A457854245F9E9FF0587FB790C
os=latest
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
Recovery default:
cid=4A35CB95FF396EE7
sn=...
bid=Mac-E43C1C25D4880AD6
k=0A385E6FFC3DDD990A8A1F4EC8B98C92CA5E19C9FF1DD26508C54936D8523121
os=default
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
Diagnostics:
cid=050C59B51497CEC8
sn=...
bid=Mac-E43C1C25D4880AD6
k=37D42A8282FE04A12A7D946304F403E56A2155B9622B385F3EB959A2FBAB8C93
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
"""
session = get_session(args)
info = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=args.diagnostics, os_type=args.os_type)
if args.verbose:
print(info)
print(f'Downloading {info[INFO_PRODUCT]}...')
cnkname = '' if args.basename == '' else args.basename + '.chunklist'
cnkpath = save_image(info[INFO_SIGN_LINK], info[INFO_SIGN_SESS], cnkname, args.outdir)
dmgname = '' if args.basename == '' else args.basename + '.dmg'
dmgpath = save_image(info[INFO_IMAGE_LINK], info[INFO_IMAGE_SESS], dmgname, args.outdir)
try:
verify_image(dmgpath, cnkpath)
return 0
except Exception as err:
if isinstance(err, AssertionError) and str(err) == '':
try:
tb = sys.exc_info()[2]
while tb.tb_next:
tb = tb.tb_next
err = linecache.getline(tb.tb_frame.f_code.co_filename, tb.tb_lineno, tb.tb_frame.f_globals).strip()
except Exception:
err = "Invalid chunklist"
print(f'\rImage verification failed. ({err})')
return 1
def action_selfcheck(args):
"""
Sanity check server logic for recovery:
if not valid(bid):
return error()
ppp = get_ppp(sn)
if not valid(ppp):
return latest_recovery(bid = bid) # Returns newest for bid.
if valid(sn):
if os == 'default':
return default_recovery(sn = sn, ppp = ppp) # Returns oldest for sn.
else:
return latest_recovery(sn = sn, ppp = ppp) # Returns newest for sn.
return default_recovery(ppp = ppp) # Returns oldest.
"""
session = get_session(args)
valid_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='default')
valid_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='latest')
product_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='default')
product_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='latest')
generic_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='default')
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
if args.verbose:
print(valid_default)
print(valid_latest)
print(product_default)
print(product_latest)
print(generic_default)
print(generic_latest)
if valid_default[INFO_PRODUCT] == valid_latest[INFO_PRODUCT]:
# Valid MLB must give different default and latest if this is not a too new product.
print(f'ERROR: Cannot determine any previous product, got {valid_default[INFO_PRODUCT]}')
return 1
if product_default[INFO_PRODUCT] != product_latest[INFO_PRODUCT]:
# Product-only MLB must give the same value for default and latest.
print(f'ERROR: Latest and default do not match for product MLB, got {product_default[INFO_PRODUCT]} and {product_latest[INFO_PRODUCT]}')
return 1
if generic_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
# Zero MLB always give the same value for default and latest.
print(f'ERROR: Generic MLB gives different product, got {generic_default[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
return 1
if valid_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
# Valid MLB must always equal generic MLB.
print(f'ERROR: Cannot determine unified latest product, got {valid_latest[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
return 1
if product_default[INFO_PRODUCT] != valid_default[INFO_PRODUCT]:
# Product-only MLB can give the same value with valid default MLB.
# This is not an error for all models, but for our chosen code it is.
print(f'ERROR: Valid and product MLB give mismatch, got {product_default[INFO_PRODUCT]} and {valid_default[INFO_PRODUCT]}')
return 1
print('SUCCESS: Found no discrepancies with MLB validation algorithm!')
return 0
def action_verify(args):
"""
Try to verify MLB serial number.
"""
session = get_session(args)
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
uvalid_default = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='default')
uvalid_latest = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='latest')
uproduct_default = get_image_info(session, bid=args.board_id, mlb=product_mlb(args.mlb), diag=False, os_type='default')
if args.verbose:
print(generic_latest)
print(uvalid_default)
print(uvalid_latest)
print(uproduct_default)
# Verify our MLB number.
if uvalid_default[INFO_PRODUCT] != uvalid_latest[INFO_PRODUCT]:
print(f'SUCCESS: {args.mlb} MLB looks valid and supported!' if uvalid_latest[INFO_PRODUCT] == generic_latest[INFO_PRODUCT] else f'SUCCESS: {args.mlb} MLB looks valid, but probably unsupported!')
return 0
print('UNKNOWN: Run selfcheck, check your board-id, or try again later!')
# Here we have matching default and latest products. This can only be true for very
# new models. These models get either latest or special builds.
if uvalid_default[INFO_PRODUCT] == generic_latest[INFO_PRODUCT]:
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new!')
return 0
if uproduct_default[INFO_PRODUCT] != uvalid_default[INFO_PRODUCT]:
print(f'UNKNOWN: {args.mlb} MLB looks invalid, other models use product {uproduct_default[INFO_PRODUCT]} instead of {uvalid_default[INFO_PRODUCT]}!')
return 0
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new and using special builds!')
return 0
def action_guess(args):
"""
Attempt to guess which model does this MLB belong.
"""
mlb = args.mlb
anon = mlb.startswith('000')
with open(args.board_db, 'r', encoding='utf-8') as fh:
db = json.load(fh)
supported = {}
session = get_session(args)
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
for model in db:
try:
if anon:
# For anonymous lookup check when given model does not match latest.
model_latest = get_image_info(session, bid=model, mlb=MLB_ZERO, diag=False, os_type='latest')
if model_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
if db[model] == 'current':
print(f'WARN: Skipped {model} due to using latest product {model_latest[INFO_PRODUCT]} instead of {generic_latest[INFO_PRODUCT]}')
continue
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
if user_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
supported[model] = [db[model], user_default[INFO_PRODUCT], generic_latest[INFO_PRODUCT]]
else:
# For normal lookup check when given model has mismatching normal and latest.
user_latest = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='latest')
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
if user_latest[INFO_PRODUCT] != user_default[INFO_PRODUCT]:
supported[model] = [db[model], user_default[INFO_PRODUCT], user_latest[INFO_PRODUCT]]
except Exception as e:
print(f'WARN: Failed to check {model}, exception: {e}')
if len(supported) > 0:
print(f'SUCCESS: MLB {mlb} looks supported for:')
for model in supported.items():
print(f'- {model}, up to {supported[model][0]}, default: {supported[model][1]}, latest: {supported[model][2]}')
return 0
print(f'UNKNOWN: Failed to determine supported models for MLB {mlb}!')
return None
def main():
parser = argparse.ArgumentParser(description='Gather recovery information for Macs')
parser.add_argument('action', choices=['download', 'selfcheck', 'verify', 'guess'],
help='Action to perform: "download" - performs recovery downloading,'
' "selfcheck" checks whether MLB serial validation is possible, "verify" performs'
' MLB serial verification, "guess" tries to find suitable mac model for MLB.')
parser.add_argument('-o', '--outdir', type=str, default='com.apple.recovery.boot',
help='customise output directory for downloading, defaults to com.apple.recovery.boot')
parser.add_argument('-n', '--basename', type=str, default='',
help='customise base name for downloading, defaults to remote name')
parser.add_argument('-b', '--board-id', type=str, default=RECENT_MAC,
help=f'use specified board identifier for downloading, defaults to {RECENT_MAC}')
parser.add_argument('-m', '--mlb', type=str, default=MLB_ZERO,
help=f'use specified logic board serial for downloading, defaults to {MLB_ZERO}')
parser.add_argument('-e', '--code', type=str, default='',
help='generate product logic board serial with specified product EEEE code')
parser.add_argument('-os', '--os-type', type=str, default='default', choices=['default', 'latest'],
help=f'use specified os type, defaults to default {MLB_ZERO}')
parser.add_argument('-diag', '--diagnostics', action='store_true', help='download diagnostics image')
parser.add_argument('-v', '--verbose', action='store_true', help='print debug information')
parser.add_argument('-db', '--board-db', type=str, default=os.path.join(SELF_DIR, 'boards.json'),
help='use custom board list for checking, defaults to boards.json')
args = parser.parse_args()
if args.code != '':
args.mlb = mlb_from_eeee(args.code)
if len(args.mlb) != 17:
print('ERROR: Cannot use MLBs in non 17 character format!')
sys.exit(1)
if args.action == 'download':
return action_download(args)
if args.action == 'selfcheck':
return action_selfcheck(args)
if args.action == 'verify':
return action_verify(args)
if args.action == 'guess':
return action_guess(args)
assert False
if __name__ == '__main__':
sys.exit(main())

View file

@ -7,24 +7,136 @@ set -Eeuo pipefail
: "${MLB:=""}" # Board serial
: "${MAC:=""}" # MAC address
: "${UUID:=""}" # Unique ID
: "${VERSION:=""}" # OSX Version
: "${WIDTH:="1920"}" # Horizontal
: "${HEIGHT:="1080"}" # Vertical
: "${VERSION:="13"}" # OSX Version
: "${MODEL:="iMacPro1,1"}" # Device model
TMP="$STORAGE/tmp"
BASE_IMG_ID="InstallMedia"
BASE_IMG="$STORAGE/base.dmg"
BASE_VERSION="$STORAGE/$PROCESS.version"
downloadImage() {
function getRandom() {
local length="${1}"
local result=""
local chars=("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F")
for ((i=0; i<length; i++)); do
result+="${chars[$((RANDOM % 16))]}"
done
echo "$result"
return 0
}
delay() {
local i
local delay="$1"
local msg="Retrying failed download in X seconds..."
info "${msg/X/$delay}"
for i in $(seq "$delay" -1 1); do
html "${msg/X/$i}"
sleep 1
done
return 0
}
function download() {
local info=""
local dest="$1"
local board="$2"
local version="$3"
local type="latest"
local appleSession=""
local downloadLink=""
local downloadSession=""
local mlb="00000000000000000"
local rc total size progress
local msg="Downloading macOS ${version^}"
info "$msg recovery image..." && html "$msg..."
appleSession=$(curl --disable -v -H "Host: osrecovery.apple.com" \
-H "Connection: close" \
-A "InternetRecovery/1.0" https://osrecovery.apple.com/ 2>&1 | tr ';' '\n' | awk -F'session=|;' '{print $2}' | grep 1)
info=$(curl --disable -s -X POST -H "Host: osrecovery.apple.com" \
-H "Connection: close" \
-A "InternetRecovery/1.0" \
-b "session=\"${appleSession}\"" \
-H "Content-Type: text/plain" \
-d $'cid='"$(getRandom 16)"$'\nsn='"${mlb}"$'\nbid='"${board}"$'\nk='"$(getRandom 64)"$'\nfg='"$(getRandom 64)"$'\nos='"${type}" \
https://osrecovery.apple.com/InstallationPayload/RecoveryImage | tr ' ' '\n')
downloadLink=$(echo "$info" | grep 'oscdn' | grep 'dmg')
downloadSession=$(echo "$info" | grep 'expires' | grep 'dmg')
if [ -z "$downloadLink" ] || [ -z "$downloadSession" ]; then
local code="99"
msg="Failed to connect to the Apple servers, reason:"
curl --silent --max-time 10 --output /dev/null --fail -H "Host: osrecovery.apple.com" -H "Connection: close" -A "InternetRecovery/1.0" https://osrecovery.apple.com/ || {
code="$?"
}
case "${code,,}" in
"6" ) error "$msg could not resolve host!" ;;
"7" ) error "$msg no internet connection available!" ;;
"28" ) error "$msg connection timed out!" ;;
"99" )
[ -n "$info" ] && echo "$info" && echo
error "$msg unknown error" ;;
*) error "$msg $code" ;;
esac
return 1
fi
# Check if running with interactive TTY or redirected to docker log
if [ -t 1 ]; then
progress="--progress=bar:noscroll"
else
progress="--progress=dot:giga"
fi
rm -f "$dest"
/run/progress.sh "$dest" "0" "$msg ([P])..." &
{ wget "$downloadLink" -O "$dest" -q --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || :
fKill "progress.sh"
if (( rc == 0 )) && [ -f "$dest" ]; then
total=$(stat -c%s "$dest")
size=$(formatBytes "$total")
if [ "$total" -lt 100000 ]; then
error "Invalid recovery image, file is only $size ?" && return 1
fi
html "Download finished successfully..."
return 0
fi
msg="Failed to download $downloadLink"
(( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
(( rc == 4 )) && error "$msg , network failure!" && return 1
(( rc == 8 )) && error "$msg , server issued an error response!" && return 1
error "$msg , reason: $rc"
return 1
}
install() {
local board
local version="$1"
local file="BaseSystem"
local path="$TMP/$file.dmg"
local dest="$2"
case "${version,,}" in
"tahoe" | "26"* | "16"* )
board="Mac-CFF7D910A743CAAF" ;;
"sequoia" | "15"* )
board="Mac-937A206F2EE63C01" ;;
"sonoma" | "14"* )
@ -42,31 +154,31 @@ downloadImage() {
return 1 ;;
esac
local msg="Downloading macOS ${version^}"
info "$msg recovery image..." && html "$msg..."
rm -f "$dest"
rm -rf "$TMP"
mkdir -p "$TMP"
/run/progress.sh "$path" "" "$msg ([P])..." &
if ! /run/fetch.py -b "$board" -n "$file" -os latest -o "$TMP" download; then
error "Failed to fetch macOS \"${version^}\" recovery image with board id \"$board\"!"
fKill "progress.sh"
return 1
if ! makeDir "$STORAGE"; then
error "Failed to create directory \"$STORAGE\" !" && return 1
fi
fKill "progress.sh"
find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete
find "$STORAGE" -maxdepth 1 -type f \( -iname 'data.*' -or -iname 'macos.*' \) -delete
if [ ! -f "$path" ] || [ ! -s "$path" ]; then
error "Failed to find file \"$path\" !"
return 1
if [ -f "/boot.dmg" ]; then
cp "/boot.dmg" "$dest"
return 0
fi
mv -f "$path" "$BASE_IMG"
rm -rf "$TMP"
local file="$STORAGE/boot.dmg"
if ! download "$file" "$board" "$version"; then
delay 5
if ! download "$file" "$board" "$version"; then
rm -f "$file"
exit 60
fi
fi
echo "$version" > "$BASE_VERSION"
mv -f "$file" "$dest"
return 0
}
@ -76,11 +188,15 @@ generateID() {
[ -n "$UUID" ] && return 0
[ -s "$file" ] && UUID=$(<"$file")
UUID="${UUID//[![:print:]]/}"
[ -n "$UUID" ] && return 0
UUID=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
UUID="${UUID^^}"
UUID="${UUID//[![:print:]]/}"
echo "$UUID" > "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
return 0
}
@ -91,12 +207,15 @@ generateAddress() {
[ -n "$MAC" ] && return 0
[ -s "$file" ] && MAC=$(<"$file")
MAC="${MAC//[![:print:]]/}"
[ -n "$MAC" ] && return 0
# Generate Apple MAC address based on Docker container ID in hostname
MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/00:16:cb:\3:\4:\5/')
MAC="${MAC^^}"
echo "$MAC" > "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
return 0
}
@ -109,6 +228,8 @@ generateSerial() {
[ -n "$SN" ] && [ -n "$MLB" ] && return 0
[ -s "$file" ] && SN=$(<"$file")
[ -s "$file2" ] && MLB=$(<"$file2")
SN="${SN//[![:print:]]/}"
MLB="${MLB//[![:print:]]/}"
[ -n "$SN" ] && [ -n "$MLB" ] && return 0
# Generate unique serial numbers for machine
@ -125,27 +246,35 @@ generateSerial() {
echo "$SN" > "$file"
echo "$MLB" > "$file2"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
! setOwner "$file2" && error "Failed to set the owner for \"$file2\" !"
return 0
}
if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then
VERSION="${VERSION:1:-1}"
fi
VERSION=$(expr "$VERSION" : "^\ *\(.*[^ ]\)\ *$")
if [ -z "$VERSION" ]; then
VERSION="14"
warn "no value specified for the VERSION variable, defaulting to \"${VERSION}\"."
fi
if [ ! -f "$BASE_IMG" ] || [ ! -s "$BASE_IMG" ]; then
if ! downloadImage "$VERSION"; then
rm -rf "$TMP"
exit 34
fi
fi
STORED_VERSION=""
if [ -f "$BASE_VERSION" ]; then
STORED_VERSION=$(<"$BASE_VERSION")
fi
STORAGE="$STORAGE/${VERSION,,}"
BASE_IMG="$STORAGE/base.dmg"
if [ "$VERSION" != "$STORED_VERSION" ]; then
info "Different version detected, switching base image from \"$STORED_VERSION\" to \"$VERSION\""
if ! downloadImage "$VERSION"; then
rm -rf "$TMP"
exit 34
if [ ! -f "$BASE_IMG" ] || [ ! -s "$BASE_IMG" ]; then
! install "$VERSION" "$BASE_IMG" && exit 34
! setOwner "$BASE_IMG" && error "Failed to set the owner for \"$BASE_IMG\" !"
fi
fi
if ! generateID; then
@ -153,7 +282,7 @@ if ! generateID; then
fi
if ! generateSerial; then
error "Failed to generate serialnumber!" && exit 36
error "Failed to generate serial number!" && exit 36
fi
if ! generateAddress; then