├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── build_iso.yml ├── Containerfile ├── LICENSE ├── README.md ├── artifacthub-repo.yml ├── assets └── logo.jpg ├── cosign.pub ├── flatpaks └── isengard_flatpaks ├── scripts ├── cleanup.sh ├── configure_kde.sh ├── enable_services.sh ├── install_1password.sh ├── install_packages.sh ├── install_warp.sh ├── just.sh └── preconfigure.sh └── system_files ├── etc ├── keyd │ └── default.conf └── yum.repos.d │ ├── docker-ce.repo │ ├── gh-cli.repo │ ├── insync.repo │ ├── scrcpy.repo │ └── vscode.repo └── usr └── share └── ublue-os └── just └── 80-isengard.just /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build-isengard 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - testing 8 | schedule: 9 | - cron: '05 10 * * *' # 10:05am UTC everyday 10 | push: 11 | branches: 12 | - main 13 | - testing 14 | paths-ignore: 15 | - '**.md' 16 | - '**.txt' 17 | workflow_dispatch: 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 21 | cancel-in-progress: true 22 | 23 | env: 24 | MY_IMAGE_NAME: "isengard" 25 | ARG_BASE_IMAGE_NAME: "bazzite" 26 | ARG_IMAGE_TAG: "${{ github.ref_name }}" 27 | IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" 28 | 29 | jobs: 30 | push-ghcr: 31 | name: Build and push image 32 | runs-on: ubuntu-24.04 33 | continue-on-error: false 34 | permissions: 35 | contents: read 36 | packages: write 37 | id-token: write 38 | 39 | steps: 40 | # Checkout push-to-registry action GitHub repository 41 | - name: Checkout Push to Registry action 42 | uses: actions/checkout@v4 43 | 44 | - name: Check just syntax 45 | uses: ublue-os/just-action@v2 46 | 47 | - name: Generate tags 48 | id: generate-tags 49 | shell: bash 50 | run: | 51 | if [[ "${{ github.event_name }}" == "pull_request" ]]; then 52 | if [[ "${{ github.base_ref}}" == "main" ]]; then 53 | echo ARG_IMAGE_TAG="stable" >> $GITHUB_ENV 54 | fi 55 | fi 56 | 57 | # Generate a timestamp for creating an image version history 58 | TIMESTAMP="$(date +%Y%m%d)" 59 | VARIANT="${{ env.ARG_IMAGE_TAG }}" 60 | 61 | if [[ "${VARIANT}" == "main" ]]; then 62 | VARIANT="stable" 63 | echo ARG_IMAGE_TAG="stable" >> $GITHUB_ENV 64 | fi 65 | 66 | COMMIT_TAGS=() 67 | BUILD_TAGS=() 68 | TESTING_TAGS=() 69 | 70 | # Have tags for tracking builds during pull request 71 | SHA_SHORT="${GITHUB_SHA::7}" 72 | 73 | COMMIT_TAGS+=("pr-${{ github.event.number }}") 74 | COMMIT_TAGS+=("${SHA_SHORT}") 75 | 76 | # Tags for tracking builds during testing 77 | TESTING_TAGS+=("${SHA_SHORT}-${VARIANT}") 78 | TESTING_TAGS+=("${SHA_SHORT}") 79 | TESTING_TAGS+=("${VARIANT}") 80 | 81 | # Append matching timestamp tags to keep a version history 82 | for TAG in "${TESTING_TAGS[@]}"; do 83 | TESTING_TAGS+=("${TAG}-${TIMESTAMP}") 84 | done 85 | 86 | BUILD_TAGS=("${VARIANT}") 87 | BUILD_TAGS+=("latest") 88 | 89 | # Append matching timestamp tags to keep a version history 90 | for TAG in "${BUILD_TAGS[@]}"; do 91 | BUILD_TAGS+=("${TAG}-${TIMESTAMP}") 92 | done 93 | 94 | BUILD_TAGS+=("${TIMESTAMP}") 95 | 96 | if [[ "${{ github.ref_name }}" == "testing" ]]; then 97 | echo "Generated the following testing tags: " 98 | for TAG in "${TESTING_TAGS[@]}"; do 99 | echo "${TAG}" 100 | done 101 | 102 | alias_tags=("${TESTING_TAGS[@]}") 103 | 104 | elif [[ "${{ github.event_name }}" == "pull_request" ]]; then 105 | echo "Generated the following commit tags: " 106 | for TAG in "${COMMIT_TAGS[@]}"; do 107 | echo "${TAG}" 108 | done 109 | 110 | alias_tags=("${COMMIT_TAGS[@]}") 111 | else 112 | alias_tags=("${BUILD_TAGS[@]}") 113 | fi 114 | 115 | echo "Generated the following build tags: " 116 | for TAG in "${BUILD_TAGS[@]}"; do 117 | echo "${TAG}" 118 | done 119 | 120 | echo "alias_tags=${alias_tags[*]}" >> $GITHUB_OUTPUT 121 | 122 | - name: Get current version 123 | id: labels 124 | run: | 125 | ver=$(skopeo inspect docker://ghcr.io/ublue-os/${{ env.ARG_BASE_IMAGE_NAME }}:${{ env.ARG_IMAGE_TAG }} | jq -r '.Labels["org.opencontainers.image.version"]') 126 | echo "VERSION=$ver" >> $GITHUB_OUTPUT 127 | 128 | # Build metadata 129 | - name: Image Metadata 130 | uses: docker/metadata-action@v5 131 | id: meta 132 | with: 133 | images: | 134 | ${{ env.MY_IMAGE_NAME }} 135 | 136 | labels: | 137 | io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository }}/main/README.md 138 | io.artifacthub.package.logo-url=https://raw.githubusercontent.com/${{ github.repository }}/main/assets/logo.jpg 139 | org.opencontainers.image.description=Customized ${{ env.ARG_BASE_IMAGE_NAME }} image that includes additional features for developers and system administrators. 140 | org.opencontainers.image.title=${{ env.MY_IMAGE_NAME }} 141 | org.opencontainers.image.version=${{ steps.labels.outputs.VERSION }} 142 | 143 | # Build image using Buildah action 144 | - name: Build Image 145 | id: build_image 146 | uses: redhat-actions/buildah-build@v2 147 | with: 148 | containerfiles: | 149 | ./Containerfile 150 | # Postfix image name with -custom to make it a little more descriptive 151 | # Syntax: https://docs.github.com/en/actions/learn-github-actions/expressions#format 152 | image: ${{ env.MY_IMAGE_NAME }} 153 | tags: | 154 | ${{ steps.generate-tags.outputs.alias_tags }} 155 | build-args: | 156 | BASE_IMAGE_NAME=${{ env.ARG_BASE_IMAGE_NAME }} 157 | IMAGE_TAG=${{ env.ARG_IMAGE_TAG }} 158 | labels: ${{ steps.meta.outputs.labels }} 159 | oci: false 160 | 161 | # Workaround bug where capital letters in your GitHub username make it impossible to push to GHCR. 162 | # https://github.com/macbre/push-to-ghcr/issues/12 163 | - name: Lowercase Registry 164 | id: registry_case 165 | uses: ASzc/change-string-case-action@v6 166 | with: 167 | string: ${{ env.IMAGE_REGISTRY }} 168 | 169 | - name: Login to GitHub Container Registry 170 | uses: docker/login-action@v3 171 | with: 172 | registry: ghcr.io 173 | username: ${{ github.actor }} 174 | password: ${{ secrets.GITHUB_TOKEN }} 175 | 176 | # Push the image to GHCR (Image Registry) 177 | - name: Push To GHCR 178 | uses: redhat-actions/push-to-registry@v2 179 | id: push 180 | env: 181 | REGISTRY_USER: ${{ github.actor }} 182 | REGISTRY_PASSWORD: ${{ github.token }} 183 | with: 184 | image: ${{ steps.build_image.outputs.image }} 185 | tags: ${{ steps.build_image.outputs.tags }} 186 | registry: ${{ steps.registry_case.outputs.lowercase }} 187 | username: ${{ env.REGISTRY_USER }} 188 | password: ${{ env.REGISTRY_PASSWORD }} 189 | extra-args: | 190 | --disable-content-trust 191 | 192 | # Sign container 193 | - uses: sigstore/cosign-installer@v3.8.2 194 | 195 | - name: Sign container image 196 | shell: bash 197 | run: | 198 | cosign sign -y --key env://COSIGN_PRIVATE_KEY ${{ steps.registry_case.outputs.lowercase }}/${{ steps.build_image.outputs.image }}@${TAGS} 199 | env: 200 | TAGS: ${{ steps.push.outputs.digest }} 201 | COSIGN_EXPERIMENTAL: false 202 | COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} 203 | 204 | build_iso: 205 | name: build iso 206 | needs: [push-ghcr] 207 | if: ${{ github.event_name == 'pull_request' && endsWith(github.event.pull_request.title, '[ISO]') || github.ref_name == 'testing' }} 208 | uses: ./.github/workflows/build_iso.yml 209 | secrets: inherit 210 | -------------------------------------------------------------------------------- /.github/workflows/build_iso.yml: -------------------------------------------------------------------------------- 1 | name: Build ISOs 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }}-iso 9 | cancel-in-progress: true 10 | 11 | env: 12 | image_name: "isengard" 13 | major_version: "41" 14 | 15 | jobs: 16 | build-iso: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | id-token: write 22 | steps: 23 | 24 | - name: Free Disk Space (Ubuntu) 25 | uses: jlumbroso/free-disk-space@v1.3.1 26 | 27 | - name: Checkout Repo 28 | uses: actions/checkout@v4 29 | 30 | - name: Set Image Tag 31 | id: generate-tag 32 | shell: bash 33 | run: | 34 | TAG="stable" 35 | 36 | if [[ -n "${{ github.event.number }}" ]]; then 37 | TAG="pr-${{ github.event.number }}" 38 | fi 39 | 40 | if [[ "${{ github.ref_name }}" == "testing" ]]; then 41 | TAG="testing" 42 | fi 43 | 44 | echo "tag=${TAG}" >> $GITHUB_OUTPUT 45 | 46 | # May do a deck version at some point 47 | # - name: Set EXTRA_BOOT_PARAMS 48 | # id: generate-extra-params 49 | # shell: bash 50 | # run: | 51 | # EXTRA_BOOT_PARAMS="" 52 | # if [[ "${{ matrix.image_name }}" =~ "deck" ]]; then 53 | # EXTRA_BOOT_PARAMS="inst.resolution=1280x800" 54 | # fi 55 | # echo "extra-boot-params=${EXTRA_BOOT_PARAMS}" >> $GITHUB_OUTPUT 56 | 57 | - name: Set Flatpaks Directory Shortname 58 | id: generate-flatpak-dir-shortname 59 | shell: bash 60 | run: | 61 | FLATPAK_DIR_SHORTNAME="flatpaks" 62 | echo "flatpak-dir-shortname=${FLATPAK_DIR_SHORTNAME}" >> $GITHUB_OUTPUT 63 | 64 | # Get Bazzite and remove Firefox 65 | - name: Get Bazzite KDE Flatpaks 66 | shell: bash 67 | run: | 68 | curl --fail -L -o ${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }}/bazzite_flatpaks https://raw.githubusercontent.com/ublue-os/bazzite/main/installer/kde_flatpaks/flatpaks 69 | grep -v 'app/org.mozilla.firefox/x86_64/stable' ${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }}/bazzite_flatpaks > ${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }}/bazzite_flatpaks_no_firefox 70 | mv ${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }}/bazzite_flatpaks_no_firefox ${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }}/bazzite_flatpaks 71 | 72 | - name: Determine Flatpak Dependencies 73 | id: flatpak_dependencies 74 | shell: bash 75 | run: | 76 | set -ex 77 | image="ghcr.io/noelmiller/${{ env.image_name }}:${{ steps.generate-tag.outputs.tag }}" 78 | # Make temp space 79 | TEMP_FLATPAK_INSTALL_DIR=$(mktemp -d -p ${{ github.workspace }} flatpak.XXX) 80 | # Get list of refs from directory 81 | FLATPAK_REFS_DIR=${{ github.workspace }}/${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }} 82 | FLATPAK_REFS_DIR_LIST=$(cat ${FLATPAK_REFS_DIR}/* | tr '\n' ' ' ) 83 | # Generate install script 84 | cat << EOF > ${TEMP_FLATPAK_INSTALL_DIR}/script.sh 85 | cat /temp_flatpak_install_dir/script.sh 86 | mkdir -p /flatpak/flatpak /flatpak/triggers 87 | mkdir /var/tmp || true 88 | chmod -R 1777 /var/tmp 89 | flatpak config --system --set languages "*" 90 | flatpak remote-add --system flathub https://flathub.org/repo/flathub.flatpakrepo 91 | flatpak install --system -y ${FLATPAK_REFS_DIR_LIST} 92 | ostree refs --repo=\${FLATPAK_SYSTEM_DIR}/repo | grep '^deploy/' | grep -v 'org\.freedesktop\.Platform\.openh264' | sed 's/^deploy\///g' > /output/flatpaks_with_deps 93 | EOF 94 | docker run --rm --privileged \ 95 | --entrypoint bash \ 96 | -e FLATPAK_SYSTEM_DIR=/flatpak/flatpak \ 97 | -e FLATPAK_TRIGGERSDIR=/flatpak/triggers \ 98 | --volume ${FLATPAK_REFS_DIR}:/output \ 99 | --volume ${TEMP_FLATPAK_INSTALL_DIR}:/temp_flatpak_install_dir \ 100 | ${image} /temp_flatpak_install_dir/script.sh 101 | docker rmi ${image} 102 | 103 | - name: Build ISOs 104 | uses: jasonn3/build-container-installer@v1.2.4 105 | id: build 106 | with: 107 | arch: x86_64 108 | image_name: ${{ env.image_name }} 109 | image_repo: ghcr.io/noelmiller 110 | variant: 'Kinoite' 111 | version: ${{ env.major_version }} 112 | image_tag: ${{ steps.generate-tag.outputs.tag }} 113 | secure_boot_key_url: 'https://github.com/ublue-os/akmods/raw/main/certs/public_key.der' 114 | enrollment_password: 'universalblue' 115 | iso_name: ${{ env.image_name }}-${{ steps.generate-tag.outputs.tag }}.iso 116 | enable_cache_dnf: "false" 117 | enable_cache_skopeo: "false" 118 | flatpak_remote_refs_dir: ${{ steps.generate-flatpak-dir-shortname.outputs.flatpak-dir-shortname }} 119 | enable_flatpak_dependencies: "false" 120 | extra_boot_params: ${{ steps.generate-extra-params.outputs.extra-boot-params }} 121 | 122 | - name: Move ISOs to Upload Directory 123 | id: upload-directory 124 | shell: bash 125 | run: | 126 | ISO_UPLOAD_DIR=${{ github.workspace }}/upload 127 | mkdir ${ISO_UPLOAD_DIR} 128 | mv ${{ steps.build.outputs.iso_path }}/${{ steps.build.outputs.iso_name }} ${ISO_UPLOAD_DIR} 129 | mv ${{ steps.build.outputs.iso_path }}/${{ steps.build.outputs.iso_name }}-CHECKSUM ${ISO_UPLOAD_DIR} 130 | echo "iso-upload-dir=${ISO_UPLOAD_DIR}" >> $GITHUB_OUTPUT 131 | 132 | - name: Upload ISOs and Checksum to Job Artifacts 133 | uses: actions/upload-artifact@v4 134 | with: 135 | name: ${{ env.image_name }}-${{ steps.generate-tag.outputs.tag }}-${{ env.major_version}} 136 | path: ${{ steps.upload-directory.outputs.iso-upload-dir }} 137 | if-no-files-found: error 138 | retention-days: 0 139 | compression-level: 0 140 | overwrite: true 141 | 142 | # The purpose of this is to upload ISOs to R2 so they can be accessed by the general public. It is unlikely I will ever do this. 143 | # - name: Upload ISOs and Checksum to R2 144 | # if: github.event_name == 'workflow_dispatch' && github.ref_name == 'main' 145 | # shell: bash 146 | # env: 147 | # RCLONE_CONFIG_R2_TYPE: s3 148 | # RCLONE_CONFIG_R2_PROVIDER: Cloudflare 149 | # RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} 150 | # RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} 151 | # RCLONE_CONFIG_R2_REGION: auto 152 | # RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} 153 | # SOURCE_DIR: ${{ steps.upload-directory.outputs.iso-upload-dir }} 154 | # run: | 155 | # sudo apt-get update 156 | # sudo apt-get install -y rclone 157 | # rclone copy $SOURCE_DIR R2:isengard 158 | -------------------------------------------------------------------------------- /Containerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE_NAME="${BASE_IMAGE_NAME:-bazzite}" 2 | ARG IMAGE_TAG="${IMAGE_TAG:-stable}" 3 | 4 | FROM ghcr.io/ublue-os/${BASE_IMAGE_NAME}:${IMAGE_TAG} AS isengard 5 | 6 | COPY system_files / 7 | COPY scripts /scripts 8 | 9 | RUN /scripts/preconfigure.sh && \ 10 | /scripts/install_1password.sh && \ 11 | /scripts/install_warp.sh && \ 12 | /scripts/install_packages.sh && \ 13 | /scripts/configure_kde.sh && \ 14 | /scripts/enable_services.sh && \ 15 | /scripts/just.sh && \ 16 | /scripts/cleanup.sh && \ 17 | ostree container commit 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Isengard Logo](assets/logo.jpg) 2 | 3 | # Isengard 4 | [![build-isengard](https://github.com/noelmiller/isengard/actions/workflows/build.yml/badge.svg)](https://github.com/noelmiller/isengard/actions/workflows/build.yml) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/isengard)](https://artifacthub.io/packages/search?repo=isengard) 5 | 6 | Custom Fedora Atomic Image for Desktops and Laptops. This is my take on what the modern Linux Desktop should look like. 7 | 8 | **Note: I do not have images for Nvidia or other variants of Bazzite. I may add them if there is demand.** 9 | 10 | # Purpose 11 | 12 | This is an image that is built on the work of [Universal Blue](https://github.com/ublue-os), [Bazzite](https://github.com/ublue-os/bazzite), and [Fedora Kinoite](https://fedoraproject.org/kinoite/) projects. 13 | 14 | The `Containerfile` is built directly off of [Bazzite](https://github.com/ublue-os/bazzite). 15 | 16 | **This image is not recommended for general usage.** 17 | 18 | If you want images designed for general consumption, I suggest using [Bazzite](https://github.com/ublue-os/bazzite) or [Bluefin](https://github.com/ublue-os/bluefin) from the Universal Blue project. 19 | 20 | # Features 21 | 22 | These are the features included in my image! 23 | 24 | ## Packages 25 | 26 | In addition to the packages included in [Bazzite](https://github.com/ublue-os/bazzite), I include the following installed by default: 27 | 28 | ### Layered Packages (through RPM-Ostree) 29 | 30 | #### System Administration 31 | 32 | - Libvirtd, Qemu, and Virt-Manager 33 | - Subscription-Manager (For running RHEL containers) 34 | - Cockpit (Not enabled by default) 35 | - Cockpit Plugins 36 | - cockpit-navigator 37 | - cockpit-bridge 38 | - cockpit-system 39 | - cockpit-selinux 40 | - cockpit-networkmanager 41 | - cockpit-storaged 42 | - cockpit-podman 43 | - cockpit-machines 44 | - cockpit-kdump 45 | 46 | #### Programming 47 | 48 | - VSCode 49 | - Zed 50 | - GH (Github CLI) 51 | - NodeJS 52 | 53 | #### Utilities 54 | 55 | - Syncthing 56 | - 1Password 57 | - Stow 58 | - scrcpy (used for controlling an android phone over USB) 59 | 60 | ### Git Repositories (simple clone) 61 | 62 | - [FZF-Tab-Completion](https://github.com/lincheney/fzf-tab-completion) 63 | 64 | ### System Flatpaks 65 | 66 | #### Browser 67 | 68 | - Brave 69 | - Google Chrome 70 | 71 | #### Communications 72 | 73 | - Slack 74 | - Discord (using Vesktop) 75 | - Element 76 | - Signal 77 | 78 | #### Programming 79 | 80 | - Podman Desktop 81 | 82 | #### Utilities 83 | 84 | - VLC 85 | - Calibre 86 | - OBS DroidCam Plugin 87 | 88 | #### Gaming 89 | 90 | - XIVLauncher (FFXIV Launcher) 91 | - OSU Lazer 92 | - Dolphin Emulator 93 | - Fightcade 94 | 95 | #### Design 96 | 97 | - Inkscape 98 | - Gimp 99 | 100 | ## Cockpit 101 | 102 | I do not enable cockpit by default as I use this image on my laptop as well. 103 | 104 | ### Caveat 105 | 106 | Cockpit is not installed in the traditional way it normally is on Fedora Workstation. It must be run in a container. You can still run it as a service on boot, but the install method is different. 107 | 108 | ### Install and Configure Cockpit 109 | 110 | Here are the steps required: 111 | 112 | 1. Run the Cockpit web service with a privileged container (as root): 113 | 114 | `podman container runlabel --name cockpit-ws RUN quay.io/cockpit/ws` 115 | 116 | 2. Make Cockpit start on boot (as root): 117 | 118 | `podman container runlabel INSTALL quay.io/cockpit/ws` 119 | 120 | `systemctl enable cockpit.service` 121 | 122 | The full documentation for cockpit can be found [here](https://cockpit-project.org/running.html#coreos). 123 | 124 | ## Using the Image 125 | 126 | If you do decide you want to try my image, you will want to rebase from Fedora Kinoite using this command: 127 | 128 | ```bash 129 | rpm-ostree rebase ostree-unverified-registry:ghcr.io/noelmiller/isengard:latest 130 | ``` 131 | 132 | After rebase, you will need to run the command below to install all flatpaks that are shipped with Bazzite and Isengard 133 | 134 | ```bash 135 | ujust _install-isengard-flatpaks 136 | ``` 137 | 138 | If there is demand, I may publish ISOs. 139 | 140 | ## Verification 141 | 142 | These images are signed with sigstore's [cosign](https://docs.sigstore.dev/cosign/overview/). You can verify the signature by downloading the `cosign.pub` key from this repo and running the following command: 143 | 144 | ```bash 145 | cosign verify --key cosign.pub ghcr.io/noelmiller/isengard 146 | ``` 147 | 148 | ## Special Thanks 149 | 150 | The contributors at Universal Blue, Bazzite, and Fedora are amazing. This image would not exist without the incredible work they do every day! 151 | -------------------------------------------------------------------------------- /artifacthub-repo.yml: -------------------------------------------------------------------------------- 1 | # Artifact Hub repository metadata file 2 | # Used to become verified publisher and more - https://artifacthub.io/docs/topics/repositories/#verified-publisher 3 | repositoryID: 45385f79-ffe6-491d-b350-7ee9cb720717 4 | owners: 5 | - name: Noel Miller 6 | email: noelmiller@protonmail.com -------------------------------------------------------------------------------- /assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelmiller/isengard/24d122f37d83fa22983dcc059135d285a7f8f83b/assets/logo.jpg -------------------------------------------------------------------------------- /cosign.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/WiSXhdr6yF/AHpnhxOtnnze1heH 3 | wjz8/MBc63isCH0JphiPAJ99hkfnDrdNLadWOwfPoSdy4cgoYdMIXxsMxA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /flatpaks/isengard_flatpaks: -------------------------------------------------------------------------------- 1 | app/app.drey.Warp/x86_64/stable 2 | app/com.github.zocker_160.SyncThingy/x86_64/stable 3 | app/com.google.Chrome/x86_64/stable 4 | app/com.obsproject.Studio/x86_64/stable 5 | app/com.slack.Slack/x86_64/stable 6 | app/com.spotify.Client/x86_64/stable 7 | app/dev.vencord.Vesktop/x86_64/stable 8 | app/dev.goats.xivlauncher/x86_64/stable 9 | app/im.riot.Riot/x86_64/stable 10 | app/io.podman_desktop.PodmanDesktop/x86_64/stable 11 | app/org.DolphinEmu.dolphin-emu/x86_64/stable 12 | app/org.gimp.GIMP/x86_64/stable 13 | app/org.inkscape.Inkscape/x86_64/stable 14 | app/org.signal.Signal/x86_64/stable 15 | app/org.videolan.VLC/x86_64/stable 16 | app/sh.ppy.osu/x86_64/stable 17 | app/com.calibre_ebook.calibre/x86_64/stable 18 | app/com.fightcade.Fightcade/x86_64/stable 19 | app/com.brave.Browser/x86_64/stable 20 | runtime/com.fightcade.Fightcade.Wine/x86_64/stable 21 | runtime/com.obsproject.Studio.Plugin.DroidCam/x86_64/stable 22 | -------------------------------------------------------------------------------- /scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ouex pipefail 4 | 5 | rm -vr /scripts 6 | -------------------------------------------------------------------------------- /scripts/configure_kde.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ouex pipefail 4 | 5 | # Configure Taskbar 6 | sed -i '//,/<\/entry>/ s/[^<]*<\/default>/applications:org.gnome.Ptyxis.desktop,preferred:\/\/browser,preferred:\/\/filemanager,applications:dev.zed.Zed.desktop,applications:steam.desktop<\/default>/' /usr/share/plasma/plasmoids/org.kde.plasma.taskmanager/contents/config/main.xml 7 | 8 | # Configure Favorites 9 | sed -i '//,/<\/entry>/ s/[^<]*<\/default>/org.gnome.Ptyxis.desktop,preferred:\/\/browser,org.kde.dolphin.desktop,dev.zed.Zed.desktop,steam.desktop<\/default>/' /usr/share/plasma/plasmoids/org.kde.plasma.kickoff/contents/config/main.xml 10 | -------------------------------------------------------------------------------- /scripts/enable_services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ouex pipefail 4 | 5 | systemctl enable docker.socket 6 | -------------------------------------------------------------------------------- /scripts/install_1password.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ouex pipefail 4 | 5 | #### Variables 6 | 7 | # Can be "beta" or "stable" 8 | RELEASE_CHANNEL="${ONEPASSWORD_RELEASE_CHANNEL:-stable}" 9 | 10 | # Must be over 1000 11 | GID_ONEPASSWORD="${GID_ONEPASSWORD:-1500}" 12 | 13 | # Must be over 1000 14 | GID_ONEPASSWORDCLI="${GID_ONEPASSWORDCLI:-1600}" 15 | 16 | echo "Installing 1Password" 17 | 18 | # On libostree systems, /opt is a symlink to /var/opt, 19 | # which actually only exists on the live system. /var is 20 | # a separate mutable, stateful FS that's overlaid onto 21 | # the ostree rootfs. Therefore we need to install it into 22 | # /usr/lib/1Password instead, and dynamically create a 23 | # symbolic link /opt/1Password => /usr/lib/1Password upon 24 | # boot. 25 | 26 | # Prepare staging directory 27 | mkdir -p /var/opt # -p just in case it exists 28 | # for some reason... 29 | 30 | # Setup repo 31 | cat << EOF > /etc/yum.repos.d/1password.repo 32 | [1password] 33 | name=1Password ${RELEASE_CHANNEL^} Channel 34 | baseurl=https://downloads.1password.com/linux/rpm/${RELEASE_CHANNEL}/\$basearch 35 | enabled=1 36 | gpgcheck=1 37 | repo_gpgcheck=1 38 | gpgkey=https://downloads.1password.com/linux/keys/1password.asc 39 | EOF 40 | 41 | # Import signing key 42 | rpm --import https://downloads.1password.com/linux/keys/1password.asc 43 | 44 | # Now let's install the packages. 45 | dnf5 -y install 1password 1password-cli 46 | 47 | # Clean up the yum repo (updates are baked into new images) 48 | rm /etc/yum.repos.d/1password.repo -f 49 | 50 | # And then we do the hacky dance! 51 | mv /var/opt/1Password /usr/lib/1Password # move this over here 52 | 53 | # Create a symlink /usr/bin/1password => /opt/1Password/1password 54 | rm /usr/bin/1password 55 | ln -s /opt/1Password/1password /usr/bin/1password 56 | 57 | ##### 58 | # The following is a bastardization of "after-install.sh" 59 | # which is normally packaged with 1password. You can compare with 60 | # /usr/lib/1Password/after-install.sh if you want to see. 61 | 62 | cd /usr/lib/1Password 63 | 64 | # chrome-sandbox requires the setuid bit to be specifically set. 65 | # See https://github.com/electron/electron/issues/17972 66 | chmod 4755 /usr/lib/1Password/chrome-sandbox 67 | 68 | # Normally, after-install.sh would create a group, 69 | # "onepassword", right about now. But if we do that during 70 | # the ostree build it'll disappear from the running system! 71 | # I'm going to work around that by hardcoding GIDs and 72 | # crossing my fingers that nothing else steps on them. 73 | # These numbers _should_ be okay under normal use, but 74 | # if there's a more specific range that I should use here 75 | # please submit a PR! 76 | 77 | # Specifically, GID must be > 1000, and absolutely must not 78 | # conflict with any real groups on the deployed system. 79 | # Normal user group GIDs on Fedora are sequential starting 80 | # at 1000, so let's skip ahead and set to something higher. 81 | 82 | BROWSER_SUPPORT_PATH="/usr/lib/1Password/1Password-BrowserSupport" 83 | 84 | # BrowserSupport binary needs setgid. This gives no extra permissions to the binary. 85 | # It only hardens it against environmental tampering. 86 | chgrp "${GID_ONEPASSWORD}" "${BROWSER_SUPPORT_PATH}" 87 | chmod g+s "${BROWSER_SUPPORT_PATH}" 88 | 89 | # onepassword-cli also needs its own group and setgid, like the other helpers. 90 | chgrp "${GID_ONEPASSWORDCLI}" /usr/bin/op 91 | chmod g+s /usr/bin/op 92 | 93 | # Dynamically create the required groups via sysusers.d 94 | # and set the GID based on the files we just chgrp'd 95 | cat >/usr/lib/sysusers.d/onepassword.conf </usr/lib/sysusers.d/onepassword-cli.conf </usr/lib/tmpfiles.d/onepassword.conf < /usr/lib/1Password upon 13 | # boot. 14 | 15 | # Prepare staging directory 16 | mkdir -p /var/opt # -p just in case it exists 17 | # for some reason... 18 | 19 | # Configure Repositories 20 | rpm --import https://releases.warp.dev/linux/keys/warp.asc 21 | sh -c 'echo -e "[warpdotdev]\nname=warpdotdev\nbaseurl=https://releases.warp.dev/linux/rpm/stable\nenabled=1\ngpgcheck=1\ngpgkey=https://releases.warp.dev/linux/keys/warp.asc" > /etc/yum.repos.d/warpdotdev.repo' 22 | dnf5 -y install warp-terminal 23 | 24 | # And then we do the hacky dance! 25 | mv /var/opt/warpdotdev /usr/lib/warpdotdev # move this over here 26 | 27 | # Create a symlink /usr/bin/warp-terminal => /opt/warpdotdev/warp-terminal/warp 28 | rm /usr/bin/warp-terminal 29 | ln -s /opt/warpdotdev/warp-terminal/warp /usr/bin/warp-terminal 30 | 31 | # Register path symlink 32 | # We do this via tmpfiles.d so that it is created by the live system. 33 | cat >/usr/lib/tmpfiles.d/warpdotdev.conf <> /usr/share/ublue-os/justfile 6 | -------------------------------------------------------------------------------- /scripts/preconfigure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ouex pipefail 4 | 5 | # Apply IP Forwarding before installing Docker to prevent messing with LXC networking 6 | sysctl -p 7 | -------------------------------------------------------------------------------- /system_files/etc/keyd/default.conf: -------------------------------------------------------------------------------- 1 | [ids] 2 | * 3 | 4 | [main] 5 | capslock = ` 6 | -------------------------------------------------------------------------------- /system_files/etc/yum.repos.d/docker-ce.repo: -------------------------------------------------------------------------------- 1 | [docker-ce-testing] 2 | name=Docker CE Testing - $basearch 3 | baseurl=https://download.docker.com/linux/fedora/$releasever/$basearch/test 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=https://download.docker.com/linux/fedora/gpg 7 | -------------------------------------------------------------------------------- /system_files/etc/yum.repos.d/gh-cli.repo: -------------------------------------------------------------------------------- 1 | [gh-cli] 2 | name=packages for the GitHub CLI 3 | baseurl=https://cli.github.com/packages/rpm 4 | enabled=1 5 | gpgkey=https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x23F3D4EA75716059 6 | -------------------------------------------------------------------------------- /system_files/etc/yum.repos.d/insync.repo: -------------------------------------------------------------------------------- 1 | [insync] 2 | name=insync repo 3 | baseurl=http://yum.insync.io/fedora/$releasever/ 4 | gpgcheck=1 5 | gpgkey=https://d2t3ff60b2tol4.cloudfront.net/repomd.xml.key 6 | enabled=1 7 | metadata_expire=120m 8 | -------------------------------------------------------------------------------- /system_files/etc/yum.repos.d/scrcpy.repo: -------------------------------------------------------------------------------- 1 | [copr:copr.fedorainfracloud.org:zeno:scrcpy] 2 | name=Copr repo for scrcpy owned by zeno 3 | baseurl=https://download.copr.fedorainfracloud.org/results/zeno/scrcpy/fedora-$releasever-$basearch/ 4 | type=rpm-md 5 | skip_if_unavailable=True 6 | gpgcheck=1 7 | gpgkey=https://download.copr.fedorainfracloud.org/results/zeno/scrcpy/pubkey.gpg 8 | repo_gpgcheck=0 9 | enabled=1 10 | enabled_metadata=1 11 | -------------------------------------------------------------------------------- /system_files/etc/yum.repos.d/vscode.repo: -------------------------------------------------------------------------------- 1 | [code] 2 | name=Visual Studio Code 3 | baseurl=https://packages.microsoft.com/yumrepos/vscode 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=https://packages.microsoft.com/keys/microsoft.asc -------------------------------------------------------------------------------- /system_files/usr/share/ublue-os/just/80-isengard.just: -------------------------------------------------------------------------------- 1 | # Connect home using WireGuard 2 | connect-home: 3 | #!/bin/bash 4 | echo "Connecting to home" 5 | sudo tailscale up --exit-node fw --accept-routes 6 | 7 | # Disconnect from home using WireGuard 8 | disconnect-home: 9 | #!/bin/bash 10 | echo "Disconnecting from home" 11 | tailscale down 12 | 13 | # Install system flatpaks 14 | _install-isengard-flatpaks: 15 | #!/bin/bash 16 | BAZZITE_FLATPAK_LIST="$(curl https://raw.githubusercontent.com/ublue-os/bazzite/main/scripts/kde_flatpaks/flatpaks | tr '\n' ' ')" 17 | ISENGARD_FLATPAK_LIST="$(curl https://raw.githubusercontent.com/noelmiller/isengard/main/flatpaks/isengard_flatpaks | tr '\n' ' ')" 18 | echo "Installing Bazzite Flatpaks.." 19 | flatpak --system -y install ${BAZZITE_FLATPAK_LIST} 20 | echo "Installing Isengard Flatpaks.." 21 | flatpak --system -y install ${ISENGARD_FLATPAK_LIST} 22 | --------------------------------------------------------------------------------