├── .gitattributes ├── .github └── workflows │ ├── build-wsl-image.yml │ └── release-event.json ├── Dockerfile ├── LICENSE ├── README.md ├── build.sh ├── clab_icon.ico ├── images ├── devpod_settings.png └── docker_desktop_integration.png ├── oobe.sh ├── profile ├── proxyman.sh ├── terminal-profile.json ├── wsl-distribution.conf ├── wsl.conf └── zsh ├── .p10k-lean.zsh ├── .p10k.zsh ├── .zshrc ├── .zshrc-lean ├── install-tools-completions.sh └── install-zsh-plugins.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.github/workflows/build-wsl-image.yml: -------------------------------------------------------------------------------- 1 | name: Check, Build & Publish WSL2 Image 2 | 3 | on: 4 | # Nightly check at 00:00 UTC 5 | schedule: 6 | - cron: '0 0 * * *' 7 | # Manual “Run workflow” button 8 | workflow_dispatch: 9 | # When you click “Publish release” in the UI 10 | release: 11 | types: [published] 12 | 13 | permissions: 14 | contents: write # required for tag push & gh‑release 15 | 16 | jobs: 17 | # ------------------------------------------------------------ 18 | # 1. Decide whether we need a new release tag 19 | # (runs on schedule or workflow‑dispatch, never on release) 20 | # ------------------------------------------------------------ 21 | check-and-release: 22 | if: github.event_name != 'release' 23 | runs-on: ubuntu-latest 24 | outputs: 25 | NEED_RELEASE: ${{ steps.decide.outputs.NEED_RELEASE }} 26 | NEW_TAG: ${{ steps.decide.outputs.NEW_TAG }} 27 | steps: 28 | - name: Checkout repository ⚡ 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 # get full history 32 | fetch-tags: true # still unreliable; we force‑fetch below 33 | 34 | - name: Ensure ALL tags are present 35 | run: git fetch --tags --prune --force 36 | 37 | - name: Get latest Containerlab version 38 | id: clab 39 | shell: bash 40 | run: | 41 | set -euo pipefail 42 | latest_url=$(curl -sLI -o /dev/null -w '%{url_effective}' \ 43 | https://github.com/srl-labs/containerlab/releases/latest) 44 | ver=${latest_url##*/} 45 | echo "CONTAINERLAB_VER=${ver#v}" >> "$GITHUB_OUTPUT" 46 | echo "Found Containerlab ${ver#v}" 47 | 48 | - name: Get latest WSL image tag 49 | id: current 50 | shell: bash 51 | run: | 52 | set -euo pipefail 53 | tag=$(git tag --list --sort=-v:refname | head -n1 || true) 54 | echo "LATEST_TAG=$tag" >> "$GITHUB_OUTPUT" 55 | 56 | if [[ -z "$tag" ]]; then 57 | echo "CONTAINERLAB_CUR=none" >> "$GITHUB_OUTPUT" 58 | echo "WSL_CUR=none" >> "$GITHUB_OUTPUT" 59 | exit 0 60 | fi 61 | 62 | echo "Latest tag is $tag" 63 | 64 | echo "CONTAINERLAB_CUR=${tag%%-*}" >> "$GITHUB_OUTPUT" 65 | echo "WSL_CUR=${tag#*-}" >> "$GITHUB_OUTPUT" 66 | 67 | - name: Decide whether to cut a new tag 68 | id: decide 69 | shell: bash 70 | run: | 71 | set -euo pipefail 72 | 73 | need_release="false" 74 | new_clab="${{ steps.clab.outputs.CONTAINERLAB_VER }}" 75 | cur_clab="${{ steps.current.outputs.CONTAINERLAB_CUR }}" 76 | cur_wsl="${{ steps.current.outputs.WSL_CUR }}" 77 | new_wsl="1.0" 78 | 79 | bump_wsl () { 80 | local major=${cur_wsl%%.*} 81 | local minor=${cur_wsl#*.} 82 | new_wsl="${major}.$((minor+1))" 83 | } 84 | 85 | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then 86 | need_release="true" 87 | [[ "$cur_clab" == "$new_clab" && "$cur_wsl" != "none" ]] && bump_wsl 88 | elif [[ "$cur_clab" == "none" ]]; then 89 | need_release="true" 90 | elif [[ "$cur_clab" != "$new_clab" ]]; then 91 | need_release="true" 92 | fi 93 | 94 | echo "NEED_RELEASE=$need_release" >> "$GITHUB_OUTPUT" 95 | 96 | if [[ "$need_release" == "true" ]]; then 97 | new_tag="${new_clab}-${new_wsl}" 98 | echo "NEW_TAG=$new_tag" >> "$GITHUB_OUTPUT" 99 | echo "Releasing $new_tag" 100 | else 101 | echo "No release required" 102 | fi 103 | 104 | - name: Create Git tag 105 | if: steps.decide.outputs.NEED_RELEASE == 'true' 106 | run: | 107 | git config --local user.email "github-actions@github.com" 108 | git config --local user.name "GitHub Actions" 109 | git tag -a "${{ steps.decide.outputs.NEW_TAG }}" \ 110 | -m "Release ${{ steps.decide.outputs.NEW_TAG }}" 111 | git push origin "${{ steps.decide.outputs.NEW_TAG }}" 112 | 113 | - name: Draft GitHub Release 114 | if: steps.decide.outputs.NEED_RELEASE == 'true' 115 | uses: softprops/action-gh-release@v1 116 | with: 117 | tag_name: ${{ steps.decide.outputs.NEW_TAG }} 118 | name: "Release ${{ steps.decide.outputs.NEW_TAG }}" 119 | body: | 120 | WSL2 image for Containerlab ${{ steps.clab.outputs.CONTAINERLAB_VER }} 121 | 122 | ${{ github.event_name == 'workflow_dispatch' && 'Manually triggered.' || 'Automated nightly release.' }} 123 | draft: false 124 | prerelease: false 125 | 126 | # ------------------------------------------------------------ 127 | # 2. Build & upload WSL image when we *have* a tag 128 | # ------------------------------------------------------------ 129 | build-and-publish: 130 | needs: check-and-release 131 | if: needs.check-and-release.outputs.NEED_RELEASE == 'true' || github.event_name == 'release' 132 | runs-on: ubuntu-22.04 133 | steps: 134 | - uses: actions/checkout@v4 135 | 136 | - uses: docker/setup-buildx-action@v2 137 | 138 | - name: Build Docker image 139 | run: docker build . -t clab-wsl-debian 140 | 141 | - name: Export container filesystem ➜ .wsl 142 | run: | 143 | docker run -t --name wsl_export clab-wsl-debian ls / 144 | docker export wsl_export -o clab.tar 145 | docker rm wsl_export 146 | mv clab.tar clab.wsl 147 | 148 | - uses: actions/upload-artifact@v4 149 | with: 150 | name: clab-wsl2 151 | path: clab.wsl 152 | 153 | - name: Determine tag name 154 | id: tag 155 | run: | 156 | if [[ "${{ github.event_name }}" == "release" ]]; then 157 | echo "TAG=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" 158 | else 159 | echo "TAG=${{ needs.check-and-release.outputs.NEW_TAG }}" >> "$GITHUB_OUTPUT" 160 | fi 161 | 162 | - name: Upload release asset 163 | uses: svenstaro/upload-release-action@v2 164 | with: 165 | repo_token: ${{ secrets.GITHUB_TOKEN }} 166 | file: clab.wsl 167 | asset_name: clab-${{ steps.tag.outputs.TAG }}.wsl 168 | tag: ${{ steps.tag.outputs.TAG }} 169 | -------------------------------------------------------------------------------- /.github/workflows/release-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "action": "created", 4 | "ref": "refs/tags/v0.0.10", 5 | "sha": "b7e12928f13caf61af40d3e8788649a1a8f24c22", 6 | "release": { 7 | "tag_name": "0.0.10" 8 | } 9 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | 3 | RUN apt update -y && apt upgrade -y 4 | RUN apt install -y \ 5 | git \ 6 | curl \ 7 | sudo \ 8 | wget \ 9 | nano \ 10 | vim \ 11 | jq \ 12 | bash-completion 13 | 14 | RUN apt install -y --no-install-recommends \ 15 | direnv \ 16 | btop \ 17 | iputils-ping \ 18 | tcpdump \ 19 | iproute2 \ 20 | qemu-kvm \ 21 | dnsutils \ 22 | telnet \ 23 | unzip \ 24 | openssh-server \ 25 | zsh && rm -rf /var/lib/apt/lists/* 26 | 27 | # Copy WSL related config and scripts 28 | COPY --chmod=644 --chown=root:root ./wsl-distribution.conf /etc/wsl-distribution.conf 29 | COPY --chmod=644 --chown=root:root ./wsl.conf /etc/wsl.conf 30 | COPY --chmod=755 ./oobe.sh /etc/oobe.sh 31 | COPY ./clab_icon.ico /usr/lib/wsl/clab_icon.ico 32 | COPY ./terminal-profile.json /usr/lib/wsl/terminal-profile.json 33 | 34 | COPY ./profile /etc/profile 35 | 36 | # Add proxyman tool 37 | COPY --chmod=755 ./proxyman.sh /usr/local/bin/proxyman 38 | 39 | RUN bash -c "echo 'port 2222' >> /etc/ssh/sshd_config" 40 | 41 | # Create clab user and add to sudo group 42 | RUN useradd -m -s /bin/zsh clab && \ 43 | echo "clab:clab" | chpasswd && \ 44 | adduser clab sudo && \ 45 | # Add NOPASSWD sudo rights for clab user 46 | echo "clab ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/clab && \ 47 | chmod 0440 /etc/sudoers.d/clab && \ 48 | # Copy skel files to clab's home 49 | cp -r /etc/skel/. /home/clab/ && \ 50 | chown -R clab:clab /home/clab/ 51 | 52 | # Set clab as default user 53 | ENV USER=clab 54 | USER clab 55 | WORKDIR /home/clab 56 | 57 | # Install Containerlab 58 | RUN curl -sL https://containerlab.dev/setup | sudo -E bash -s "all" 59 | 60 | # Install gNMIc and gNOIc 61 | RUN bash -c "$(curl -sL https://get-gnmic.openconfig.net)" && \ 62 | bash -c "$(curl -sL https://get-gnoic.kmrd.dev)" 63 | 64 | # Create SSH key for vscode user to enable passwordless SSH to devices 65 | RUN ssh-keygen -t ecdsa -b 256 -N "" -f ~/.ssh/id_ecdsa 66 | 67 | # Install pyenv 68 | RUN bash -c "$(curl https://pyenv.run)" 69 | 70 | # Install Oh My Zsh 71 | RUN bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended 72 | 73 | COPY --chown=clab:clab ./zsh/.zshrc /home/clab/.zshrc 74 | COPY --chown=clab:clab ./zsh/.p10k-lean.zsh /home/clab/.p10k-lean.zsh 75 | COPY --chown=clab:clab ./zsh/.zshrc-lean /home/clab/.zshrc-lean 76 | COPY --chown=clab:clab ./zsh/.p10k.zsh /home/clab/.p10k.zsh 77 | COPY --chown=clab:clab ./zsh/install-zsh-plugins.sh /tmp/install-zsh-plugins.sh 78 | COPY --chown=clab:clab ./zsh/install-tools-completions.sh /tmp/install-tools-completions.sh 79 | RUN chmod +x /tmp/install-zsh-plugins.sh /tmp/install-tools-completions.sh 80 | RUN bash -c "/tmp/install-zsh-plugins.sh && /tmp/install-tools-completions.sh" -------------------------------------------------------------------------------- /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 | # Containerlab WSL 2 | 3 | A WSL distribution designed for easy 'plug and play' usage with [Containerlab](https://containerlab.dev). 4 | 5 | | OS | Supported | VM-based NOSes | 6 | | :--------: | :---------: | :------------: | 7 | | Windows 10 | Yes | No | 8 | | Windows 11 | Yes | Yes | 9 | 10 | We recommend using Windows Terminal for the best experience: 11 | - Windows 11 users: Windows Terminal is installed by default. 12 | - Windows 10 users: Download Windows Terminal from the [Microsoft Store](https://aka.ms/terminal). 13 | 14 | # Quick Start 15 | 16 | **Ensure you are on the latest version of WSL (WSL2.4.4 or newer). Use `wsl --update`.** 17 | 18 | - Download the `.wsl` file from the [releases page](https://github.com/kaelemc/wsl-clab/releases/latest). 19 | - Double click the `.wsl` file to install. 20 | - Open 'Containerlab' from the start menu, or execute `wsl -d Containerlab` 21 | - Complete the interactive shell selection. 22 | - If you are behind a proxy, the OOBE script will let you configure it using [proxyman](https://github.com/FloSch62/proxyman). 23 | - If you have Docker Desktop installed. See [Docker Desktop](#docker-desktop). 24 | - Done! you can start labbing. (see [DevPod](#devpod) for a great way to lab). 25 | 26 | > [!NOTE] 27 | > Default credentials are `clab:clab` 28 | 29 | # WSL Installation 30 | 31 | This distro makes use of WSL2, which requires that virtualization is enabled in 32 | your UEFI/BIOS. 33 | 34 | This may appear as something called 'SVM (AMD-V)' or 'Intel VT-x' depending on 35 | your processor. 36 | 37 | ### Windows 11 38 | 39 | Open PowerShell and type: 40 | 41 | ```powershell 42 | wsl --install 43 | ``` 44 | 45 | Restart your PC, and WSL2 should be installed. 46 | 47 | ### Windows 10 48 | 49 | > [!TIP] 50 | > Newer versions of Windows 10 allow usage of `wsl --install`, just like with Windows 11. 51 | 52 | **Instructions are from ['Manual installation steps for older versions of WSL'.](https://learn.microsoft.com/en-us/windows/wsl/install-manual)** 53 | 54 | Open an elevated PowerShell window (as administrator) and paste the following two commands: 55 | 56 | ```powershell 57 | dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 58 | 59 | dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart 60 | ``` 61 | 62 | At this point restart your computer. After it has rebooted download the latest 63 | WSL2 kernel. [Download link](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi). 64 | 65 | Follow the installation wizard. After completion finally set WSL2 as the default 66 | version of WSL. 67 | 68 | In PowerShell or command prompt paste the following: 69 | 70 | ```powershell 71 | wsl --set-default-version 2 72 | ``` 73 | 74 | ## Version Check 75 | 76 | Run `wsl --version` in PowerShell or command prompt to ensure WSL2 is enabled. 77 | The WSL version number should be 2.4.4.0 or higher. 78 | 79 | ```powershell 80 | PS C:\Users\Kaelem> wsl --version 81 | WSL version: 2.4.4.0 82 | Kernel version: 5.15.167.4-1 83 | WSLg version: 1.0.65 84 | MSRDC version: 1.2.5620 85 | Direct3D version: 1.611.1-81528511 86 | DXCore version: 10.0.26100.1-240331-1435.ge-release 87 | Windows version: 10.0.19044.5131 88 | ``` 89 | 90 | # Distro Installation 91 | 92 | **Ensure WSL is enabled and you have WSL 2.4.4 or newer. See the 93 | [version check](#version-check) instructions.** 94 | 95 | 96 | 1. Download the `.wsl` file from the [latest release](https://github.com/kaelemc/wsl-clab/releases/latest). 97 | 98 | 2. Double click the `.wsl` file to install the distribution. 99 | 100 | > [!NOTE] 101 | > If you see an error that nested virtualization is not supported, see the 102 | > [vrnetlab](#vrnetlab-nested-virtualization) section below. 103 | 104 | 3. From the start menu you can launch the distribution from a new 'Containerlab' 105 | shortcut which has been added. 106 | 107 | or in PowerShell/cmd you can execute: 108 | 109 | ```powershell 110 | wsl -d Containerlab 111 | ``` 112 | 113 | 4. On first launch you will be presented with an interactive menu to select what 114 | shell and prompt you would like. 115 | 116 | This menu will give you options of `zsh`, `bash` (with a fancy two-line prompt) 117 | or `bash` with the default prompt. 118 | 119 | You will also be presented with the choice to have the Fira Code 120 | [nerd font](https://www.nerdfonts.com/font-downloads) automatically installed on 121 | your system. **We recommend you install this font (especially if using `zsh` as 122 | your shell of choice).** 123 | 124 | Finally at the end SSH keys will be copied from your Windows host into 125 | Containerlab WSL to enable passwordless SSH. This is an integral step for 126 | [DevPod](#devpod) usage. 127 | 128 | If no SSH keys are found on your machine, an RSA keypair will be automatically 129 | generated. 130 | 131 | To run the setup again, execute `/etc/oobe.sh` inside Containerlab WSL. 132 | 133 | > [!IMPORTANT] 134 | > After installation, close and reopen Windows Terminal to ensure 135 | > proper font rendering and appearance settings have been applied 136 | > correctly. 137 | > 138 | > This step is necessary for the terminal to recognize and use the 139 | > newly installed WSL distribution's display configurations. 140 | 141 | 5. You can open Containerlab WSL in the following ways: 142 | 143 | - From the profile in Windows Terminal (recommended). 144 | - From the shortcut in the start menu. 145 | - Executing `wsl -d Containerlab` in PowerShell or command prompt. 146 | 147 | > [!NOTE] 148 | > Opening WSL via the shortcut or `wsl -d Containerlab` will not 149 | > open in our custom Windows Terminal profile. The customised 150 | > appearance settings will not be functional in this case. 151 | 152 | # vrnetlab (Nested virtualization) 153 | 154 | > [!IMPORTANT] 155 | > This feature is only supported on Windows 11. 156 | 157 | You can run [vrnetlab (VM-based)](https://github.com/hellt/vrnetlab) nodes on 158 | top of WSL2 and use them in containerlab. Containerlab WSL is already configured 159 | so that nested virtualization is enabled on the distro side. 160 | 161 | To use vrnetlab nodes on Containerlab WSL you must ensure that nested 162 | virtualization is enabled globally in WSL. 163 | 164 | - You can do this by opening the *'WSL Settings'* app, going to the *'Optional 165 | features'* tab and ensuring *'Enable nested virtualization'* is enabled. 166 | 167 | > [!NOTE] 168 | > You should be good to go if you don't get any errors during installation or 169 | > distro bootup saying that *'Nested virtualization is not supported on this 170 | > machine.'* 171 | 172 | See the [containerlab user manual](https://containerlab.dev/manual/vrnetlab/) 173 | for more information about vrnetlab. 174 | 175 | # Performance Tuning 176 | 177 | WSL2 runs as a VM. By default allocated resources are: 178 | 179 | | Resource | Default value | Description | 180 | | :------: | -------------------- | :---------- | 181 | | vCPU | Logical thread count | If your processor has 8 cores and 16 threads, WSL2 will assign 16 threads to the WSL VM. | 182 | | RAM | 50% of system memory | If you have 32Gb of RAM on your system, WSL will allocate 16Gb to the WSL VM. | 183 | | Disk | 1Tb | Regardless of disk size, the WSL VM will have a VHD with a maximum size of 1Tb. The disk is thin/sparse provisioned. | 184 | 185 | Despite the fairly generous resource allocation by default. WSL2 will not use 186 | 100% of the assigned resources. 187 | 188 | # Docker Desktop 189 | 190 | If you have Docker desktop installed. You **must** ensure the integration with 191 | the Containerlab WSL distro is disabled, otherwise Containerlab will not work 192 | inside Containerlab WSL. 193 | 194 | 1. Open Docker Desktop window and go to settings (gear icon on the title bar) 195 | 1. Under the 'Resources tab, enter the 'WSL integration' page 196 | 1. Ensure 'Containerlab' has integration disabled 197 | 198 | ![Docker Desktop integration screenshot](./images/docker_desktop_integration.png) 199 | 200 | # DevPod 201 | 202 | [DevPod](https://devpod.sh/) is an awesome tool which can let us easily run labs 203 | which take advantage of Devcontainers, which overall can give a 'one-click' lab 204 | experience. It's like running the codespaces labs but on your local machine. 205 | 206 | Check out [this video](https://www.youtube.com/watch?v=ceDrFx2K3jE) for more 207 | info. 208 | 209 | Containerlab WSL was designed to support this lab experience out of the box. 210 | Just remember the following consideration: 211 | 212 | - When using DevPod, ensure Containerlab WSL is started (it does **not** 213 | automatically launch on Windows startup), you should leave the terminal window 214 | with Containerlab WSL open in the background. 215 | 216 | A one-time configuration step is required. You must setup a provider in DevPod. 217 | For Containerlab WSL, create the **SSH** provider with the following values: 218 | 219 | | Field | Value | 220 | |-------|------------------| 221 | | Host | `clab@localhost` | 222 | | Port | `2222` | 223 | 224 | You can leave the other settings as the default values. See the screenshot 225 | below. 226 | 227 | After configuring the provider, you are done! You can now use one-click labs you 228 | see with the DevPod button, or configure the lab workspaces yourself. 229 | 230 | ![DevPod settings screenshot](./images/devpod_settings.png) 231 | 232 | > [!NOTE] 233 | > You may have to tick 'Use Builtin Ssh' for correct behaviour. 234 | 235 | # Accessing lab nodes 236 | 237 | You probably want to access your nodes from a terminal application your computer, such as SecureCRT, MobaXTerm, PuTTY etc. 238 | 239 | There are two ways you can achieve this: 240 | - On Windows, add a static route to the lab management network. 241 | - SSH Tunneling/Proxy (depends on terminal/console application support). 242 | 243 | ## Adding a static route 244 | 245 | You can add a static route to Windows, where the destination subnet is your labs management subnet, and the next hop is the IP of Containerlab WSL. 246 | 247 | ### Step 1 - Get the IP Address of Containerlab WSL 248 | 249 | In Containerlab WSL -- Execute and copy/note down the outputted IP address 250 | ```bash 251 | ip addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' 252 | ``` 253 | 254 | ### Step 2 - Add the route 255 | 256 | By default the Containerlab management subnet is `172.20.20.0/24`. If you have used a custom management subnet in your toplogy, please adjust the below command accordingly. 257 | 258 | In Windows, open an elevated Command Prompt Window (Run as Administrator). Execute the following (replace ``, `` and ``) 259 | ```cmd 260 | route add MASK 261 | ``` 262 | 263 | For example, if using the default management subnet of `172.20.20.0/24`, and the Containerlab WSL IP address was `172.28.121.79`; The command would be: 264 | ```cmd 265 | route add 172.20.20.0 MASK 255.255.255.0 172.12.121.79 266 | ``` 267 | 268 | ### Removing the route (Optional) 269 | 270 | If for some reason you need to remove the route, say the subnet or WSL IP address changed, you can execute the following in an elevated Command Prompt window: 271 | ```cmd 272 | route delete 273 | ``` 274 | 275 | ## Configuring SSH tunneling (SecureCRT) 276 | 277 | In SecureCRT you can configure SSH tunneling so you can access your lab nodes (running in WSL) without having to modify routes in Windows. 278 | 279 | ### Step 1 - Configure an SSH session to WSL Containerlab 280 | 281 | In SecureCRT create and save an SSH session to WSL Containerlab. Session parameters are below: 282 | 283 | **Session Parameters** 284 | 285 | - Hostname: `wsl.localhost` 286 | - Port: `2222` 287 | - Username: `clab` 288 | 289 | Ensure you save the session. 290 | 291 | ### Step 2 - Add sessions to your lab nodes 292 | 293 | The only remaining step is when adding lab nodes, ensure you go to the 'Firewall' settings -> 'Select Session' -> Select the saved session to WSL (default name: `wsl.localhost`). 294 | 295 | Now you should be able to 'natively' connect to your lab nodes from within Windows. 296 | 297 | # Developers 298 | 299 | Development should be performed from another WSL distribution. 300 | 301 | Clone the repository and build using the build script (you may have to 302 | `chmod +x` the script) 303 | 304 | ```bash 305 | ./build.sh 306 | ``` 307 | 308 | This will place `clab.wsl` in `C:\temp`. Doubleclick to install the 309 | distribution. 310 | 311 | ## Manual Steps 312 | 313 | 1. From inside a WSL distro Build the container: 314 | 315 | ```bash 316 | docker build . --tag ghcr.io/kaelemc/clab-wsl-debian 317 | ``` 318 | 319 | 2. Run it and export the filesystem to a `.wsl` file: 320 | 321 | ```bash 322 | docker run -t --name wsl_export ghcr.io/kaelemc/clab-wsl-debian ls / 323 | docker export wsl_export > /mnt/c/temp/clab.wsl 324 | ``` 325 | 326 | > [!IMPORTANT] 327 | > Create the 'temp' directory on your C: drive if it doesn't exist. 328 | 329 | 3. Remove the container to ease rebuilding: 330 | 331 | ```bash 332 | docker rm wsl_export 333 | ``` 334 | 335 | 4. Use it 336 | 337 | In your windows filesystem at `C:\temp` should be a file `clab.wsl`, double 338 | click to install. or use: 339 | 340 | ```powershell 341 | wsl --install --from-file clab.wsl 342 | ``` 343 | 344 | # Uninstallation 345 | 346 | Uninstall Containerlab WSL using the following command in PowerShell/command 347 | prompt: 348 | 349 | ```powershell 350 | wsl --unregister Containerlab 351 | ``` 352 | 353 | Ensure uninstallation by checking installed distros: 354 | 355 | ```powershell 356 | wsl -l -v 357 | ``` 358 | 359 | # Reference Material 360 | 361 | - https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro#export-the-tar-from-a-container 362 | - https://learn.microsoft.com/en-us/windows/wsl/build-custom-distro 363 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define container name and image tag 4 | CONTAINER_NAME="wsl_export" 5 | IMAGE_TAG="ghcr.io/kaelemc/clab-wsl-debian" 6 | EXPORT_FILENAME="clab.wsl" # Keep the filename consistent for now 7 | 8 | # --- Automatic Temporary Path Detection --- 9 | TEMP_PATH="" 10 | 11 | # Check for WSL-specific temp paths first 12 | if [ -d "/mnt/c/temp" ]; then 13 | TEMP_PATH="/mnt/c/temp" 14 | echo "Detected WSL environment. Using temporary path: $TEMP_PATH" 15 | elif [ -d "/mnt/c/Temp" ]; then 16 | TEMP_PATH="/mnt/c/Temp" 17 | echo "Detected WSL environment. Using temporary path: $TEMP_PATH" 18 | # If WSL paths don't exist, check for standard Linux /tmp 19 | elif [ -d "/tmp" ]; then 20 | TEMP_PATH="/tmp" 21 | echo "Assuming standard Linux environment. Using temporary path: $TEMP_PATH" 22 | else 23 | # Error if no suitable temp directory is found 24 | echo "Error: Could not find a suitable temporary directory." 25 | echo "Checked: /mnt/c/temp, /mnt/c/Temp, /tmp" 26 | exit 1 27 | fi 28 | 29 | # Construct the full path for the export file 30 | EXPORT_FILE_PATH="$TEMP_PATH/$EXPORT_FILENAME" 31 | OLD_EXPORT_FILE_PATH="$EXPORT_FILE_PATH.old" 32 | 33 | # --- Docker Operations --- 34 | 35 | # Remove the previous container if it exists (ignore errors if it doesn't) 36 | echo "Attempting to remove previous container '$CONTAINER_NAME'..." 37 | docker rm "$CONTAINER_NAME" 2>/dev/null || true 38 | 39 | # Build the Docker image 40 | echo "Building Docker image '$IMAGE_TAG'..." 41 | docker build . --tag "$IMAGE_TAG" 42 | if [ $? -ne 0 ]; then 43 | echo "Error: Docker build failed." 44 | exit 1 45 | fi 46 | 47 | # Rename old export file if it exists 48 | if [ -f "$EXPORT_FILE_PATH" ]; then 49 | echo "Moving existing '$EXPORT_FILE_PATH' to '$OLD_EXPORT_FILE_PATH'..." 50 | mv "$EXPORT_FILE_PATH" "$OLD_EXPORT_FILE_PATH" 51 | if [ $? -ne 0 ]; then 52 | echo "Warning: Failed to move existing export file." 53 | # Decide if this should be a fatal error or just a warning 54 | fi 55 | fi 56 | 57 | # Run the Docker container (just to create it, ls / is optional) 58 | # Using --rm might be simpler if you don't need to inspect it after failure 59 | # but the original script uses explicit rm, so keeping that pattern. 60 | echo "Running temporary container '$CONTAINER_NAME' from image '$IMAGE_TAG'..." 61 | docker run -t --name "$CONTAINER_NAME" "$IMAGE_TAG" ls / 62 | if [ $? -ne 0 ]; then 63 | echo "Error: Failed to run the container '$CONTAINER_NAME'." 64 | # Optional: Clean up container even on failure 65 | # docker rm "$CONTAINER_NAME" 2>/dev/null || true 66 | exit 1 67 | fi 68 | 69 | # Export the Docker container 70 | echo "Exporting container '$CONTAINER_NAME' to '$EXPORT_FILE_PATH'..." 71 | docker export "$CONTAINER_NAME" > "$EXPORT_FILE_PATH" 72 | if [ $? -ne 0 ]; then 73 | echo "Error: Docker export failed." 74 | # Clean up container even on failure 75 | docker rm "$CONTAINER_NAME" 2>/dev/null || true 76 | exit 1 77 | fi 78 | 79 | # Clean up the Docker container 80 | echo "Cleaning up container '$CONTAINER_NAME'..." 81 | docker rm "$CONTAINER_NAME" 82 | if [ $? -ne 0 ]; then 83 | echo "Warning: Failed to remove the container '$CONTAINER_NAME' after export." 84 | fi 85 | 86 | echo "Script finished successfully. Export saved to '$EXPORT_FILE_PATH'." 87 | exit 0 -------------------------------------------------------------------------------- /clab_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srl-labs/wsl-containerlab/dd28698277bf6212fb6b1a60d3564eb69ec17de4/clab_icon.ico -------------------------------------------------------------------------------- /images/devpod_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srl-labs/wsl-containerlab/dd28698277bf6212fb6b1a60d3564eb69ec17de4/images/devpod_settings.png -------------------------------------------------------------------------------- /images/docker_desktop_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srl-labs/wsl-containerlab/dd28698277bf6212fb6b1a60d3564eb69ec17de4/images/docker_desktop_integration.png -------------------------------------------------------------------------------- /oobe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DEFAULT_UID='1000' 4 | 5 | function prompt_proxy { 6 | read -p "Are you behind a proxy that you want to configure now? (y/N) " -n 1 -r 7 | echo 8 | if [[ $REPLY =~ ^[Yy]$ ]]; then 9 | echo -e "\nPlease provide your HTTP_PROXY URL (e.g. http://proxy.example.com:8080):" 10 | read -r HTTP_PROXY 11 | echo -e "\nPlease provide your HTTPS_PROXY URL (often the same as HTTP_PROXY):" 12 | read -r HTTPS_PROXY 13 | echo -e "\nPlease provide your NO_PROXY list (default: localhost,127.0.0.1,::1):" 14 | read -r NO_PROXY 15 | [ -z "$NO_PROXY" ] && NO_PROXY="localhost,127.0.0.1,::1" 16 | 17 | echo -e "\nWriting proxy configuration to /etc/proxy.conf..." 18 | echo "HTTP_PROXY=$HTTP_PROXY" | sudo tee /etc/proxy.conf > /dev/null 19 | echo "HTTPS_PROXY=$HTTPS_PROXY" | sudo tee -a /etc/proxy.conf > /dev/null 20 | echo "NO_PROXY=$NO_PROXY" | sudo tee -a /etc/proxy.conf > /dev/null 21 | 22 | echo -e "\nConfiguring system-wide proxy using proxyman..." 23 | SUDO_USER=clab SUDO_UID=1000 SUDO_GID=1000 sudo proxyman set > /dev/null 2>&1 24 | echo -e "\nProxy has been set. You can run 'sudo proxyman unset' to remove it." 25 | eval "$(sudo /usr/local/bin/proxyman export)" 26 | echo -e "\n\033[32mWelcome to Containerlab's WSL distribution\033[0m" 27 | else 28 | echo -e "\nSkipping proxy configuration.\n" 29 | fi 30 | } 31 | 32 | function setup-bash-prompt { 33 | # Check if the prompt is already set up 34 | if grep -q "function promptcmd" /home/clab/.bashrc; then 35 | echo "Bash prompt already configured in .bashrc" 36 | return 1 37 | fi 38 | 39 | cat << 'EOF' >> /home/clab/.bashrc 40 | 41 | # Custom Bash Prompt Configuration 42 | WHITE='\[\033[1;37m\]'; LIGHTRED='\[\033[1;31m\]'; LIGHTGREEN='\[\033[1;32m\]'; LIGHTBLUE='\[\033[1;34m\]'; DEFAULT='\[\033[0m\]' 43 | cLINES=$WHITE; cBRACKETS=$WHITE; cERROR=$LIGHTRED; cSUCCESS=$LIGHTGREEN; cHST=$LIGHTGREEN; cPWD=$LIGHTBLUE; cCMD=$DEFAULT 44 | promptcmd() { 45 | PREVRET=$? 46 | PS1="\n" 47 | if [ $PREVRET -ne 0 ]; then 48 | PS1="${PS1}${cBRACKETS}[${cERROR}x${cBRACKETS}]${cLINES}\342\224\200" 49 | else 50 | PS1="${PS1}${cBRACKETS}[${cSUCCESS}*${cBRACKETS}]${cLINES}\342\224\200" 51 | fi 52 | PS1="${PS1}${cBRACKETS}[${cHST}\h${cBRACKETS}]${cLINES}\342\224\200" 53 | PS1="${PS1}[${cPWD}\w${cBRACKETS}]" 54 | PS1="${PS1}\n${cLINES}\342\224\224\342\224\200\342\224\200> ${cCMD}" 55 | } 56 | PROMPT_COMMAND=promptcmd 57 | EOF 58 | 59 | } 60 | 61 | function install_fonts { 62 | echo -e "\033[34m\nInstalling FiraCode Nerd Font...\033[0m" 63 | 64 | # Font name pattern to match any FiraCode Nerd Font variant 65 | FONT_NAME_PATTERN='FiraCode Nerd Font*' 66 | 67 | # Check if any FiraCode Nerd Font is already installed using PowerShell 68 | FONT_CHECK=$(powershell.exe -NoProfile -Command ' 69 | Add-Type -AssemblyName System.Drawing 70 | $fonts = [System.Drawing.Text.InstalledFontCollection]::new().Families 71 | $fontNamePattern = "'"$FONT_NAME_PATTERN"'" 72 | $found = $fonts | Where-Object { $_.Name -like $fontNamePattern } | Select-Object -First 1 73 | if ($found) { "yes" } else { "no" } 74 | ') 75 | 76 | if [[ "$FONT_CHECK" =~ "yes" ]]; then 77 | echo -e "\033[33mFiraCode Nerd Font is already installed. Skipping installation.\033[0m" 78 | else 79 | echo "Downloading FiraCode Nerd Font..." 80 | TMP_DIR=$(mktemp -d) 81 | cd "$TMP_DIR" 82 | curl -fLo "FiraCode.zip" https://github.com/ryanoasis/nerd-fonts/releases/latest/download/FiraCode.zip 83 | 84 | # Unzip the font files 85 | unzip -q FiraCode.zip -d FiraCodeNF 86 | 87 | # Convert the path to Windows format 88 | FONTS_PATH=$(wslpath -w "$TMP_DIR/FiraCodeNF") 89 | 90 | # Install fonts using PowerShell 91 | powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ' 92 | $fontFiles = Get-ChildItem -Path "'"$FONTS_PATH"'" -Filter "*.ttf" 93 | foreach ($fontFile in $fontFiles) { 94 | $shellApp = New-Object -ComObject Shell.Application 95 | $fontsFolder = $shellApp.NameSpace(0x14) 96 | $fontsFolder.CopyHere($fontFile.FullName, 16) 97 | } 98 | ' 99 | 100 | # Clean up 101 | cd ~ 102 | rm -rf "$TMP_DIR" 103 | 104 | echo -e "\033[32mFiraCode Nerd Font installed successfully.\033[0m" 105 | echo -e "\033[33mNote: You may need to restart Windows Terminal to see the new fonts.\033[0m" 106 | fi 107 | } 108 | 109 | function import_ssh_keys { 110 | KEY_CHECK=$(powershell.exe -NoProfile -Command ' 111 | $key_types = @("rsa", "ecdsa", "ed25519") 112 | 113 | foreach ( $type in $key_types ) 114 | { 115 | if( Test-Path $env:userprofile\.ssh\id_$type.pub ) 116 | { 117 | return $type 118 | } 119 | } 120 | Write-Output False 121 | ') 122 | 123 | mkdir -p /home/clab/.ssh 124 | 125 | case $KEY_CHECK in 126 | rsa*) 127 | KEY=$(powershell.exe -NoProfile -Command 'Get-Content $env:userprofile\.ssh\id_rsa.pub') 128 | echo $KEY | sudo tee -a /home/clab/.ssh/authorized_keys > /dev/null 129 | ;; 130 | ecdsa*) 131 | KEY=$(powershell.exe -NoProfile -Command 'Get-Content $env:userprofile\.ssh\id_ecdsa.pub') 132 | echo $KEY | sudo tee -a /home/clab/.ssh/authorized_keys > /dev/null 133 | ;; 134 | ed25519*) 135 | KEY=$(powershell.exe -NoProfile -Command 'Get-Content $env:userprofile\.ssh\id_ed25519.pub') 136 | echo $KEY | sudo tee -a /home/clab/.ssh/authorized_keys > /dev/null 137 | ;; 138 | False*) 139 | SSH_CMD="ssh-keygen -t rsa -b 4096 -f \$env:USERPROFILE\.ssh\id_rsa -N '\"\"'" 140 | powershell.exe -Command $SSH_CMD 141 | KEY=$(powershell.exe -NoProfile -Command 'Get-Content $env:userprofile\.ssh\id_rsa.pub') 142 | echo $KEY | sudo tee -a /home/clab/.ssh/authorized_keys > /dev/null 143 | ;; 144 | *) 145 | echo -e "\033[34m\nSSH: Could not detect key type. Please check or create a key.\033[0m" 146 | esac 147 | 148 | echo -e "\033[32mSSH keys successfully copied. You can SSH into Containerlab WSL passwordless with: 'ssh clab@localhost -p 2222' (Ensure Containerlab WSL is open)\033[0m" 149 | } 150 | 151 | ### CONTAINERLAB COMPLETIONS ### 152 | function install_clab_completions_bash { 153 | echo "Installing Containerlab completions for BASH..." 154 | 155 | mkdir -p /etc/bash_completion.d 156 | containerlab completion bash > /etc/bash_completion.d/containerlab 157 | 158 | # Also enable completions for the 'clab' alias 159 | { 160 | echo "" 161 | echo "# Also autocomplete for 'clab' alias" 162 | echo "complete -o default -F __start_containerlab clab" 163 | } | sudo tee -a /etc/bash_completion.d/containerlab > /dev/null # Use sudo tee here 164 | } 165 | 166 | function install_clab_completions_zsh { 167 | echo "Installing Containerlab completions for ZSH..." 168 | 169 | # oh-my-zsh custom completions directory 170 | local ZSH_COMPLETIONS_DIR="/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions" 171 | 172 | # Make sure directory exists 173 | mkdir -p "$ZSH_COMPLETIONS_DIR" 174 | 175 | containerlab completion zsh > "${ZSH_COMPLETIONS_DIR}/_containerlab" 176 | 177 | # Include 'clab' alias in the compdef 178 | sed -i 's/compdef _containerlab containerlab/compdef _containerlab containerlab clab/g' \ 179 | "${ZSH_COMPLETIONS_DIR}/_containerlab" 180 | 181 | chown clab:clab "${ZSH_COMPLETIONS_DIR}/_containerlab" 182 | } 183 | 184 | 185 | # --- Start OOBE logic --- 186 | echo -e "\033[32mWelcome to Containerlab's WSL distribution\033[0m" 187 | echo "echo clab | sudo -S mkdir -p /run/docker/netns" >> /home/clab/.bashrc 188 | 189 | # Check connectivity before anything else 190 | if ! curl -fsSL --connect-timeout 5 https://www.google.com -o /dev/null; then 191 | echo -e "\nIt seems we couldn't connect to the internet directly. You might be behind a proxy." 192 | prompt_proxy 193 | fi 194 | 195 | # --- Apply sysctl settings --- 196 | echo -e "\n\033[34mApplying system kernel settings for inotify...\033[0m" 197 | 198 | if sudo mkdir -p /etc/sysctl.d > /dev/null 2>&1 && \ 199 | echo -e "fs.inotify.max_user_watches=1048576\nfs.inotify.max_user_instances=512" | sudo tee /etc/sysctl.d/90-wsl-inotify.conf > /dev/null 2>&1 && \ 200 | sudo sysctl --system > /dev/null 2>&1 && \ 201 | sudo systemctl restart docker > /dev/null 2>&1; then 202 | echo -e "\033[32mSystem kernel settings applied.\033[0m" 203 | else 204 | echo -e "\033[31mWarning: Failed to apply all sysctl settings. Some features might be affected.\033[0m" 205 | fi 206 | # --- End sysctl settings --- 207 | 208 | PS3=" 209 | 210 | Please select which shell you'd like to use: " 211 | shell_opts=("zsh" "bash with two-line prompt" "bash (default WSL prompt)") 212 | select shell in "${shell_opts[@]}" 213 | do 214 | case $shell in 215 | "zsh") 216 | echo -e "\033[34m\nzsh selected\033[0m" 217 | echo -e "\033[33mNote: zsh with custom theme requires Nerd Font for proper symbol display.\033[0m" 218 | 219 | PS3=" 220 | 221 | Select zsh configuration: " 222 | zsh_opts=("Full featured (many plugins)" "Lean version (minimal plugins)") 223 | select zsh_config in "${zsh_opts[@]}" 224 | do 225 | case $zsh_config in 226 | "Full featured (many plugins)") 227 | echo -e "\033[34m\nConfiguring full featured zsh\033[0m" 228 | read -p "Would you like to install FiraCode Nerd Font? (y/N) " -n 1 -r 229 | echo 230 | if [[ $REPLY =~ ^[Yy]$ ]]; then 231 | install_fonts 232 | fi 233 | sudo -u clab cp /home/clab/.zshrc{,.bak} 234 | sudo -u clab cp /home/clab/.p10k.zsh{,.bak} 235 | break 2 236 | ;; 237 | "Lean version (minimal plugins)") 238 | echo -e "\033[34m\nConfiguring lean zsh\033[0m" 239 | read -p "Would you like to install FiraCode Nerd Font? (y/N) " -n 1 -r 240 | echo 241 | if [[ $REPLY =~ ^[Yy]$ ]]; then 242 | install_fonts 243 | fi 244 | sudo -u clab cp /home/clab/.zshrc{,.bak} 245 | sudo -u clab cp /home/clab/.p10k.zsh{,.bak} 246 | sudo -u clab cp /home/clab/.zshrc-lean /home/clab/.zshrc 247 | sudo -u clab cp /home/clab/.p10k-lean.zsh /home/clab/.p10k.zsh 248 | break 2 249 | ;; 250 | *) 251 | echo -e "\033[31m\n'$REPLY' is not a valid choice\033[0m" 252 | ;; 253 | esac 254 | done 255 | 256 | sudo chsh -s "$(which zsh)" clab 257 | 258 | # Install Containerlab completions for zsh 259 | install_clab_completions_zsh 260 | break 261 | ;; 262 | 263 | "bash with two-line prompt") 264 | echo -e "\033[34m\nbash with two-line prompt selected.\033[0m" 265 | read -p "Would you like to install FiraCode Nerd Font? (y/N) " -n 1 -r 266 | echo 267 | if [[ $REPLY =~ ^[Yy]$ ]]; then 268 | install_fonts 269 | fi 270 | # Backup .bashrc 271 | sudo -u clab cp /home/clab/.bashrc /home/clab/.bashrc.bak 272 | sudo chsh -s "$(which bash)" clab 273 | setup-bash-prompt 274 | 275 | # Install Containerlab completions for bash 276 | install_clab_completions_bash 277 | break 278 | ;; 279 | 280 | "bash (default WSL prompt)") 281 | echo -e "\033[34m\nbash selected\030[0m" # Corrected escape sequence 282 | sudo chsh -s "$(which bash)" clab 283 | 284 | # Install Containerlab completions for bash 285 | install_clab_completions_bash 286 | break 287 | ;; 288 | 289 | *) 290 | echo -e "\033[31m\n'$REPLY' is not a valid choice\033[0m" 291 | ;; 292 | esac 293 | done 294 | 295 | import_ssh_keys 296 | exit 0 -------------------------------------------------------------------------------- /profile: -------------------------------------------------------------------------------- 1 | # /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) 2 | # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...). 3 | 4 | 5 | ## COMMENTED OUT FOR WSL PATH TO WORK 6 | # if [ "$(id -u)" -eq 0 ]; then 7 | # PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 8 | # else 9 | # PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" 10 | # fi 11 | # export PATH 12 | 13 | if [ "${PS1-}" ]; then 14 | if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then 15 | # The file bash.bashrc already sets the default PS1. 16 | # PS1='\h:\w\$ ' 17 | if [ -f /etc/bash.bashrc ]; then 18 | . /etc/bash.bashrc 19 | fi 20 | else 21 | if [ "$(id -u)" -eq 0 ]; then 22 | PS1='# ' 23 | else 24 | PS1='$ ' 25 | fi 26 | fi 27 | fi 28 | 29 | if [ -d /etc/profile.d ]; then 30 | for i in /etc/profile.d/*.sh; do 31 | if [ -r $i ]; then 32 | . $i 33 | fi 34 | done 35 | unset i 36 | fi -------------------------------------------------------------------------------- /proxyman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # proxyman.sh 4 | # 5 | # Quick Start: 6 | # 1. Run: sudo ./proxyman.sh set 7 | # 2. Then run: eval "$(sudo ./proxyman.sh export)" to apply to current shell 8 | # 3. To remove: sudo ./proxyman.sh unset; eval "$(sudo ./proxyman.sh unexport)" 9 | # 4. To completely clean up: sudo ./proxyman.sh purge 10 | # 11 | # This script manages system-wide proxy settings on Linux systems (Debian/Ubuntu and RHEL/CentOS/Fedora). 12 | # 13 | # Features: 14 | # - Reads from /etc/proxy.conf or prompts interactively if missing. 15 | # - Configures proxies for: 16 | # * /etc/profile.d/custom_export.sh (to set env vars) 17 | # * Package manager (apt/dnf/yum) 18 | # * /etc/wgetrc 19 | # * Docker daemon (daemon.json + systemd drop-in) 20 | # * Per-user Docker config (~/.docker/config.json) 21 | # 22 | # On 'set', creates backups of configs (except custom_export.sh). 23 | # On 'unset', restores from backups and removes custom_export.sh. 24 | # On 'purge', removes all settings and backups. 25 | # 26 | # After setting proxy: 27 | # eval "$(sudo ./proxyman.sh export)" to apply proxy vars to current shell. 28 | # 29 | # After unsetting proxy: 30 | # eval "$(sudo ./proxyman.sh unexport)" to remove them from current shell. 31 | # 32 | # Run as root (sudo) because we modify system files. 33 | 34 | ########################## 35 | # ANSI colors for better UX 36 | ########################## 37 | GREEN="\e[32m" 38 | YELLOW="\e[33m" 39 | RED="\e[31m" 40 | BOLD="\e[1m" 41 | RESET="\e[0m" 42 | 43 | ########################## 44 | # Configuration file paths 45 | ########################## 46 | CONFIG_FILE="/etc/proxy.conf" 47 | WGET_CONF="/etc/wgetrc" 48 | DOCKER_CONF="/etc/docker/daemon.json" 49 | DOCKER_SYSTEMD_DIR="/etc/systemd/system/docker.service.d" 50 | DOCKER_SYSTEMD_PROXY_CONF="${DOCKER_SYSTEMD_DIR}/http-proxy.conf" 51 | 52 | # This is the new file for environment variables: 53 | CUSTOM_EXPORT_FILE="/etc/profile.d/custom_export.sh" 54 | 55 | ########################## 56 | # Backup file paths 57 | ########################## 58 | WGET_BAK="${WGET_CONF}.bak" 59 | DOCKER_BAK="${DOCKER_CONF}.bak" 60 | DOCKER_SYSTEMD_BAK="${DOCKER_SYSTEMD_PROXY_CONF}.bak" 61 | 62 | # Determine the user who invoked sudo (or root if not sudoed) 63 | if [ -n "$SUDO_USER" ]; then 64 | USERNAME="$SUDO_USER" 65 | else 66 | USERNAME="root" 67 | fi 68 | USER_HOME=$(eval echo "~$USERNAME") 69 | USER_DOCKER_DIR="${USER_HOME}/.docker" 70 | USER_DOCKER_CONF="${USER_DOCKER_DIR}/config.json" 71 | USER_DOCKER_BAK="${USER_DOCKER_CONF}.bak" 72 | 73 | ########################## 74 | # Detect package manager 75 | ########################## 76 | APT_EXISTS=$(command -v apt) 77 | DNF_EXISTS=$(command -v dnf) 78 | YUM_EXISTS=$(command -v yum) 79 | 80 | PM_CONF="" 81 | PM_BAK="" 82 | 83 | if [ -n "$APT_EXISTS" ]; then 84 | PM_CONF="/etc/apt/apt.conf" 85 | PM_BAK="${PM_CONF}.bak" 86 | elif [ -n "$DNF_EXISTS" ]; then 87 | PM_CONF="/etc/dnf/dnf.conf" 88 | PM_BAK="${PM_CONF}.bak" 89 | elif [ -n "$YUM_EXISTS" ]; then 90 | PM_CONF="/etc/yum.conf" 91 | PM_BAK="${PM_CONF}.bak" 92 | fi 93 | 94 | ########################## 95 | # Print help message 96 | ########################## 97 | print_help() { 98 | echo -e "${BOLD}Usage:${RESET} $0 {set|unset|list|export|unexport|purge|-h}" 99 | echo 100 | echo -e "${BOLD}Commands:${RESET}" 101 | echo " set - Set the proxy (from /etc/proxy.conf or interactively)" 102 | echo " unset - Unset the proxy and restore original configurations, removing $CUSTOM_EXPORT_FILE" 103 | echo " purge - Completely remove all proxy settings and backups" 104 | echo " list - List current proxy settings" 105 | echo " export - Print export commands for current shell" 106 | echo " unexport - Print unset commands for current shell" 107 | echo " -h - Show this help" 108 | echo 109 | echo "If /etc/proxy.conf is missing, you will be prompted interactively." 110 | echo 111 | echo "Examples:" 112 | echo " sudo $0 set" 113 | echo " eval \"\$(sudo $0 export)\"" 114 | echo " # Current shell now has proxy vars." 115 | echo 116 | echo " sudo $0 unset" 117 | echo " eval \"\$(sudo $0 unexport)\"" 118 | echo " # Current shell no longer has proxy vars." 119 | echo 120 | echo " sudo $0 purge" 121 | echo " # Remove all proxy settings and backups permanently." 122 | } 123 | 124 | ########################## 125 | # read_config: 126 | # Load proxy vars from /etc/proxy.conf or prompt user if missing. 127 | ########################## 128 | read_config() { 129 | if [ ! -f "$CONFIG_FILE" ]; then 130 | echo -e "${YELLOW}$CONFIG_FILE not found.${RESET}" 131 | echo -e "Would you like to set proxy interactively? [y/N]" 132 | read -r ans 133 | if [[ "$ans" =~ ^[Yy]$ ]]; then 134 | echo "Enter HTTP proxy (e.g. http://proxy.example.com:8080):" 135 | read -r HTTP_PROXY 136 | echo "Enter HTTPS proxy (e.g. http://proxy.example.com:8080):" 137 | read -r HTTPS_PROXY 138 | 139 | DEFAULT_NO_PROXY="localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,172.17.0.0/16" 140 | echo "Enter NO_PROXY [default: ${DEFAULT_NO_PROXY}]:" 141 | read -r NO_PROXY 142 | if [ -z "$NO_PROXY" ]; then 143 | NO_PROXY="$DEFAULT_NO_PROXY" 144 | fi 145 | 146 | if [ -z "$HTTP_PROXY" ] || [ -z "$HTTPS_PROXY" ] || [ -z "$NO_PROXY" ]; then 147 | echo -e "${RED}All required values (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) must be provided.${RESET}" 148 | exit 1 149 | fi 150 | 151 | echo "Save these settings to $CONFIG_FILE for future runs? [y/N]" 152 | read -r save_ans 153 | if [[ "$save_ans" =~ ^[Yy]$ ]]; then 154 | cat > "$CONFIG_FILE" < "$USER_DOCKER_CONF" < "$DOCKER_SYSTEMD_PROXY_CONF" </dev/null; then 252 | CONTAINERS=$(docker ps -q) 253 | if [ -n "$CONTAINERS" ]; then 254 | echo -e "${YELLOW}Docker service is running with active containers.${RESET}" 255 | echo "Restarting Docker will interrupt these containers." 256 | echo "Do you want to restart Docker now so changes take effect? [y/N]" 257 | read -r docker_ans 258 | if [[ ! "$docker_ans" =~ ^[Yy]$ ]]; then 259 | echo -e "${YELLOW}Skipping Docker restart. Changes won't take effect until Docker is restarted manually.${RESET}" 260 | return 261 | fi 262 | fi 263 | systemctl daemon-reload 264 | systemctl restart docker 265 | fi 266 | } 267 | 268 | ########################## 269 | # set_proxy: 270 | # Now stores env vars in CUSTOM_EXPORT_FILE instead of /etc/environment 271 | ########################## 272 | set_proxy() { 273 | read_config 274 | 275 | # Backup other files if needed (no backup for CUSTOM_EXPORT_FILE) 276 | backup_file_if_needed "$WGET_CONF" "${WGET_BAK}" 277 | backup_file_if_needed "$DOCKER_CONF" "${DOCKER_BAK}" 278 | backup_file_if_needed "$DOCKER_SYSTEMD_PROXY_CONF" "${DOCKER_SYSTEMD_BAK}" 279 | if [ -n "$PM_CONF" ]; then 280 | backup_file_if_needed "$PM_CONF" "$PM_BAK" 281 | fi 282 | 283 | # Create/update /etc/profile.d/custom_export.sh 284 | mkdir -p /etc/profile.d 285 | cat > "$CUSTOM_EXPORT_FILE" <> "$PM_CONF" 300 | echo "Acquire::HTTPS::Proxy \"$HTTPS_PROXY\";" >> "$PM_CONF" 301 | elif [ -n "$DNF_EXISTS" ]; then 302 | cp "$PM_BAK" "$PM_CONF" 303 | sed -i '/proxy=/d' "$PM_CONF" 304 | echo "proxy=$HTTP_PROXY" >> "$PM_CONF" 305 | elif [ -n "$YUM_EXISTS" ]; then 306 | cp "$PM_BAK" "$PM_CONF" 307 | sed -i '/proxy=/d' "$PM_CONF" 308 | echo "proxy=$HTTP_PROXY" >> "$PM_CONF" 309 | fi 310 | fi 311 | 312 | # Update /etc/wgetrc 313 | cp "$WGET_BAK" "$WGET_CONF" 314 | sed -i '/use_proxy\|http_proxy\|https_proxy\|no_proxy/d' "$WGET_CONF" 315 | { 316 | echo "use_proxy = on" 317 | echo "http_proxy = $HTTP_PROXY" 318 | echo "https_proxy = $HTTPS_PROXY" 319 | echo "no_proxy = $NO_PROXY" 320 | } >> "$WGET_CONF" 321 | 322 | # Update Docker configs 323 | cp "$DOCKER_BAK" "$DOCKER_CONF" 324 | cat > "$DOCKER_CONF" <*|<6->.*) ]] || return 10 | typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( os_icon dir vcs newline prompt_char ) 11 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( status command_execution_time background_jobs direnv asdf virtualenv anaconda pyenv goenv nodenv nvm nodeenv rbenv rvm fvm luaenv jenv plenv perlbrew phpenv scalaenv haskell_stack kubecontext terraform aws aws_eb_env azure gcloud google_app_cred toolbox context nordvpn ranger yazi nnn lf xplr vim_shell midnight_commander nix_shell chezmoi_shell todo timewarrior taskwarrior per_directory_history newline ) 12 | typeset -g POWERLEVEL9K_MODE=nerdfont-complete 13 | typeset -g POWERLEVEL9K_ICON_PADDING=none 14 | typeset -g POWERLEVEL9K_BACKGROUND= 15 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= 16 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' 17 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= 18 | typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=true 19 | typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false 20 | typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX= 21 | typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX= 22 | typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX= 23 | typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX= 24 | typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX= 25 | typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX= 26 | typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= 27 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL= 28 | typeset -g POWERLEVEL9K_SHOW_RULER=false 29 | typeset -g POWERLEVEL9K_RULER_CHAR='─' 30 | typeset -g POWERLEVEL9K_RULER_FOREGROUND=242 31 | typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' 32 | if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then 33 | typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242 34 | typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=' ' 35 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL=' ' 36 | typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}' 37 | typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}' 38 | fi 39 | typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND= 40 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76 41 | typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196 42 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' 43 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' 44 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V' 45 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶' 46 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true 47 | typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='' 48 | typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= 49 | typeset -g POWERLEVEL9K_DIR_FOREGROUND=31 50 | typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique 51 | typeset -g POWERLEVEL9K_SHORTEN_DELIMITER= 52 | typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103 53 | typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39 54 | typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true 55 | local anchor_files=( .bzr .citc .git .hg .node-version .python-version .go-version .ruby-version .lua-version .java-version .perl-version .php-version .tool-versions .mise.toml .shorten_folder_marker .svn .terraform CVS Cargo.toml composer.json go.mod package.json stack.yaml ) 56 | typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})" 57 | typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false 58 | typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1 59 | typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80 60 | typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40 61 | typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50 62 | typeset -g POWERLEVEL9K_DIR_HYPERLINK=false 63 | typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3 64 | typeset -g POWERLEVEL9K_VCS_BRANCH_ICON='\uF126 ' 65 | typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?' 66 | function my_git_formatter() { 67 | emulate -L zsh 68 | if [[ -n $P9K_CONTENT ]]; then 69 | typeset -g my_git_format=$P9K_CONTENT 70 | return 71 | fi 72 | if (( $1 )); then 73 | local meta='%f' 74 | local clean='%76F' 75 | local modified='%178F' 76 | local untracked='%39F' 77 | local conflicted='%196F' 78 | else 79 | local meta='%244F' 80 | local clean='%244F' 81 | local modified='%244F' 82 | local untracked='%244F' 83 | local conflicted='%244F' 84 | fi 85 | local res 86 | if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then 87 | local branch=${(V)VCS_STATUS_LOCAL_BRANCH} 88 | (( $#branch > 32 )) && branch[13,-13]="…" 89 | res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}" 90 | fi 91 | if [[ -n $VCS_STATUS_TAG && -z $VCS_STATUS_LOCAL_BRANCH ]]; then 92 | local tag=${(V)VCS_STATUS_TAG} 93 | (( $#tag > 32 )) && tag[13,-13]="…" 94 | res+="${meta}#${clean}${tag//\%/%%}" 95 | fi 96 | [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}" 97 | if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then 98 | res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" 99 | fi 100 | if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then 101 | res+=" ${modified}wip" 102 | fi 103 | if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then 104 | (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" 105 | (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" " 106 | (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" 107 | elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then 108 | fi 109 | (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" 110 | (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" " 111 | (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" 112 | (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}" 113 | [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}" 114 | (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" 115 | (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}" 116 | (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" 117 | (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}" 118 | (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─" 119 | typeset -g my_git_format=$res 120 | } 121 | functions -M my_git_formatter 2>/dev/null 122 | typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1 123 | typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~' 124 | typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true 125 | typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}' 126 | typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}' 127 | typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1 128 | typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76 129 | typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244 130 | typeset -g POWERLEVEL9K_VCS_BACKENDS=(git) 131 | typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76 132 | typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76 133 | typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178 134 | typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true 135 | typeset -g POWERLEVEL9K_STATUS_OK=false 136 | typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70 137 | typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔' 138 | typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true 139 | typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70 140 | typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔' 141 | typeset -g POWERLEVEL9K_STATUS_ERROR=false 142 | typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160 143 | typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘' 144 | typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true 145 | typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160 146 | typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false 147 | typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘' 148 | typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true 149 | typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160 150 | typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘' 151 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3 152 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 153 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=101 154 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' 155 | typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false 156 | typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=70 157 | typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178 158 | typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66 159 | typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global) 160 | typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false 161 | typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true 162 | typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB= 163 | typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=168 164 | typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=37 165 | typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=37 166 | typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=70 167 | typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=37 168 | typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=134 169 | typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=38 170 | typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=32 171 | typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=32 172 | typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=67 173 | typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=125 174 | typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=129 175 | typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=31 176 | typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=99 177 | typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=172 178 | typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70 179 | typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39 180 | typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION= 181 | typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION= 182 | typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178 183 | typeset -g POWERLEVEL9K_YAZI_FOREGROUND=178 184 | typeset -g POWERLEVEL9K_NNN_FOREGROUND=72 185 | typeset -g POWERLEVEL9K_LF_FOREGROUND=72 186 | typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72 187 | typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34 188 | typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=178 189 | typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74 190 | typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=33 191 | typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35 192 | typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220 193 | typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160 194 | typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90 195 | typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95 196 | typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false 197 | typeset -g POWERLEVEL9K_RAM_FOREGROUND=66 198 | typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96 199 | typeset -g POWERLEVEL9K_LOAD_WHICH=5 200 | typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66 201 | typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178 202 | typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166 203 | typeset -g POWERLEVEL9K_TODO_FOREGROUND=110 204 | typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true 205 | typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false 206 | typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110 207 | typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}' 208 | typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74 209 | typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=135 210 | typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=130 211 | typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172 212 | typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178 213 | typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=180 214 | typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=180 215 | typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m' 216 | typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m' 217 | typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m' 218 | typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION= 219 | typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37 220 | typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false 221 | typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false 222 | typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= 223 | typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37 224 | typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}' 225 | typeset -g POWERLEVEL9K_PYENV_FOREGROUND=37 226 | typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global) 227 | typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false 228 | typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true 229 | typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}' 230 | typeset -g POWERLEVEL9K_GOENV_FOREGROUND=37 231 | typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global) 232 | typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false 233 | typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true 234 | typeset -g POWERLEVEL9K_NODENV_FOREGROUND=70 235 | typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global) 236 | typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false 237 | typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true 238 | typeset -g POWERLEVEL9K_NVM_FOREGROUND=70 239 | typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false 240 | typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true 241 | typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=70 242 | typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false 243 | typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER= 244 | typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=70 245 | typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true 246 | typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=37 247 | typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true 248 | typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37 249 | typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true 250 | typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=134 251 | typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true 252 | typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=99 253 | typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true 254 | typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=161 255 | typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=32 256 | typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true 257 | typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false 258 | typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117 259 | typeset -g POWERLEVEL9K_RBENV_FOREGROUND=168 260 | typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global) 261 | typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false 262 | typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true 263 | typeset -g POWERLEVEL9K_RVM_FOREGROUND=168 264 | typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false 265 | typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false 266 | typeset -g POWERLEVEL9K_FVM_FOREGROUND=38 267 | typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=32 268 | typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global) 269 | typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false 270 | typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true 271 | typeset -g POWERLEVEL9K_JENV_FOREGROUND=32 272 | typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global) 273 | typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false 274 | typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true 275 | typeset -g POWERLEVEL9K_PLENV_FOREGROUND=67 276 | typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global) 277 | typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false 278 | typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true 279 | typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67 280 | typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true 281 | typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false 282 | typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99 283 | typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global) 284 | typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false 285 | typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true 286 | typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160 287 | typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global) 288 | typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false 289 | typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true 290 | typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172 291 | typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local) 292 | typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true 293 | typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl' 294 | typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=( '*' DEFAULT) 295 | typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=134 296 | typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}' 297 | typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false 298 | typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=( '*' OTHER) 299 | typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38 300 | typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38 301 | typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt' 302 | typeset -g POWERLEVEL9K_AWS_CLASSES=( '*' DEFAULT) 303 | typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208 304 | typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}' 305 | typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70 306 | typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt' 307 | typeset -g POWERLEVEL9K_AZURE_CLASSES=( '*' OTHER) 308 | typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=32 309 | typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil' 310 | typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32 311 | typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}' 312 | typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}' 313 | typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60 314 | typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt' 315 | typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=( '*' DEFAULT) 316 | typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=32 317 | typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}' 318 | typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178 319 | typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}' 320 | typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94 321 | typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81 322 | typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION= 323 | typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)' 324 | typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false 325 | typeset -g POWERLEVEL9K_IP_FOREGROUND=38 326 | typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}' 327 | typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*' 328 | typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68 329 | typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20 330 | typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160 331 | typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70 332 | typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178 333 | typeset -g POWERLEVEL9K_BATTERY_STAGES='\uf58d\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf578' 334 | typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false 335 | typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68 336 | typeset -g POWERLEVEL9K_TIME_FOREGROUND=66 337 | typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' 338 | typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false 339 | function prompt_example() { 340 | p10k segment -f 208 -i '⭐' -t 'hello, %n' 341 | } 342 | function instant_prompt_example() { 343 | prompt_example 344 | } 345 | typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off 346 | typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose 347 | typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true 348 | (( ! $+functions[p10k] )) || p10k reload 349 | } 350 | typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} 351 | (( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} 352 | builtin unset p10k_config_opts 353 | -------------------------------------------------------------------------------- /zsh/.p10k.zsh: -------------------------------------------------------------------------------- 1 | builtin local -a p10k_config_opts 2 | [[ ! -o aliases ]] || p10k_config_opts+=('aliases') 3 | [[ ! -o sh_glob ]] || p10k_config_opts+=('sh_glob') 4 | [[ ! -o no_brace_expand ]] || p10k_config_opts+=('no_brace_expand') 5 | builtin setopt no_aliases no_sh_glob brace_expand 6 | () { 7 | emulate -L zsh -o extended_glob 8 | unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' 9 | [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return 10 | local grey='242' 11 | local red='1' 12 | local yellow='3' 13 | local blue='4' 14 | local magenta='5' 15 | local cyan='6' 16 | local white='7' 17 | local darkgreen='#386641' 18 | typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( context dir vcs virtualenv command_execution_time newline prompt_char ) 19 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( time newline ) 20 | typeset -g POWERLEVEL9K_BACKGROUND= 21 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= 22 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' 23 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= 24 | typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= 25 | typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true 26 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta 27 | typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red 28 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' 29 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' 30 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='❮' 31 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false 32 | typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$darkgreen 33 | typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false 34 | typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= 35 | typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue 36 | typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f" 37 | typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f" 38 | typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION= 39 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5 40 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 41 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' 42 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow 43 | typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey 44 | typeset -g POWERLEVEL9K_VCS_LOADING_TEXT= 45 | typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0 46 | typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan 47 | typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind) 48 | typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= 49 | typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@' 50 | typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON= 51 | typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣' 52 | typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡' 53 | typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1 54 | typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }' 55 | typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey 56 | typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' 57 | typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false 58 | typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off 59 | typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose 60 | typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true 61 | (( ! $+functions[p10k] )) || p10k reload 62 | } 63 | typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} 64 | (( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} 65 | builtin unset p10k_config_opts 66 | -------------------------------------------------------------------------------- /zsh/.zshrc: -------------------------------------------------------------------------------- 1 | # init direnv 2 | # https://github.com/romkatv/powerlevel10k/blob/master/README.md#how-do-i-initialize-direnv-when-using-instant-prompt 3 | (( ${+commands[direnv]} )) && emulate zsh -c "$(direnv export zsh)" 4 | 5 | # Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. 6 | # Initialization code that may require console input (password prompts, [y/n] 7 | # confirmations, etc.) must go above this block; everything else may go below. 8 | if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then 9 | source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" 10 | fi 11 | 12 | (( ${+commands[direnv]} )) && emulate zsh -c "$(direnv hook zsh)" 13 | 14 | 15 | # Path to your oh-my-zsh installation. 16 | export ZSH="$HOME/.oh-my-zsh" 17 | 18 | ZSH_THEME="powerlevel10k/powerlevel10k" 19 | 20 | ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=40 21 | ZSH_AUTOSUGGEST_HISTORY_IGNORE="?(#c50,)" 22 | 23 | # history options 24 | export SAVEHIST=1000000000 25 | export HISTFILESIZE=1000000000 26 | setopt inc_append_history 27 | setopt extended_history 28 | setopt hist_ignore_all_dups 29 | setopt hist_ignore_space 30 | 31 | # auto complete configs -- menu-select+unambiguous to have tab menu auto complete setup 32 | zstyle ':autocomplete:tab:*' insert-unambiguous no 33 | zstyle ':autocomplete:*' widget-style menu-select 34 | # dont auto show next completions -- its very annoying 35 | zstyle ':autocomplete:*' min-input 1 36 | 37 | # limit history backward search (up arrow) 38 | zstyle ':autocomplete:history-search-backward:*' list-lines 20 39 | 40 | # auto completion list "bright white" - https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit 41 | zstyle ':completion:*' list-colors '=*=97' 42 | 43 | # disable simulated typing paste 44 | zstyle ':bracketed-paste-magic' active-widgets '.self-*' 45 | 46 | # Uncomment the following line to use case-sensitive completion. 47 | # CASE_SENSITIVE="true" 48 | 49 | # Uncomment the following line to use hyphen-insensitive completion. 50 | # Case-sensitive completion must be off. _ and - will be interchangeable. 51 | # HYPHEN_INSENSITIVE="true" 52 | 53 | # Uncomment one of the following lines to change the auto-update behavior 54 | # zstyle ':omz:update' mode disabled # disable automatic updates 55 | # zstyle ':omz:update' mode auto # update automatically without asking 56 | # zstyle ':omz:update' mode reminder # just remind me to update when it's time 57 | 58 | # Uncomment the following line to change how often to auto-update (in days). 59 | # zstyle ':omz:update' frequency 13 60 | 61 | # Uncomment the following line if pasting URLs and other text is messed up. 62 | # DISABLE_MAGIC_FUNCTIONS="true" 63 | 64 | # Uncomment the following line to disable colors in ls. 65 | # DISABLE_LS_COLORS="true" 66 | 67 | # Uncomment the following line to disable auto-setting terminal title. 68 | # DISABLE_AUTO_TITLE="true" 69 | 70 | # Uncomment the following line to enable command auto-correction. 71 | # ENABLE_CORRECTION="true" 72 | 73 | # Uncomment the following line to display red dots whilst waiting for completion. 74 | # You can also set it to another string to have that shown instead of the default red dots. 75 | # e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" 76 | # Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) 77 | # COMPLETION_WAITING_DOTS="true" 78 | 79 | # Uncomment the following line if you want to disable marking untracked files 80 | # under VCS as dirty. This makes repository status check for large repositories 81 | # much, much faster. 82 | # DISABLE_UNTRACKED_FILES_DIRTY="true" 83 | 84 | # Uncomment the following line if you want to change the command execution time 85 | # stamp shown in the history command output. 86 | # You can set one of the optional three formats: 87 | # "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" 88 | # or set a custom format using the strftime function format specifications, 89 | # see 'man strftime' for details. 90 | # HIST_STAMPS="mm/dd/yyyy" 91 | 92 | # Which plugins would you like to load? 93 | # Standard plugins can be found in $ZSH/plugins/ 94 | # Custom plugins may be added to $ZSH_CUSTOM/plugins/ 95 | # Example format: plugins=(rails git textmate ruby lighthouse) 96 | # Add wisely, as too many plugins slow down shell startup. 97 | plugins=(brew git pip python F-Sy-H zsh-autocomplete zsh-autosuggestions colored-man-pages kubectl) 98 | 99 | source $ZSH/oh-my-zsh.sh 100 | 101 | # User configuration 102 | 103 | # export MANPATH="/usr/local/man:$MANPATH" 104 | 105 | # You may need to manually set your language environment 106 | # export LANG=en_US.UTF-8 107 | 108 | # Preferred editor for local and remote sessions 109 | # if [[ -n $SSH_CONNECTION ]]; then 110 | # export EDITOR='vim' 111 | # else 112 | # export EDITOR='mvim' 113 | # fi 114 | 115 | # Compilation flags 116 | # export ARCHFLAGS="-arch x86_64" 117 | 118 | # Set personal aliases, overriding those provided by oh-my-zsh libs, 119 | # plugins, and themes. Aliases can be placed here, though oh-my-zsh 120 | # users are encouraged to define aliases within the ZSH_CUSTOM folder. 121 | # For a full list of active aliases, run `alias`. 122 | # 123 | # Example aliases 124 | # alias zshconfig="mate ~/.zshrc" 125 | # alias ohmyzsh="mate ~/.oh-my-zsh" 126 | 127 | [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh 128 | export PATH="$HOME/.atuin/bin:$PATH" 129 | eval "$(atuin init zsh)" 130 | 131 | # pyenv 132 | export PYENV_ROOT="$HOME/.pyenv" 133 | [[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" 134 | eval "$(pyenv init -)" 135 | 136 | # go path 137 | export PATH=$PATH:/usr/local/go/bin:~/go/bin 138 | 139 | # create /run/docker/netns without password prompt 140 | echo clab | sudo -S mkdir -p /run/docker/netns -------------------------------------------------------------------------------- /zsh/.zshrc-lean: -------------------------------------------------------------------------------- 1 | # === ~/.zshrc-lean === 2 | 3 | # 0. Recursively remove unsupported "-P" flags in all .zsh files of the zsh-autocomplete plugin. 4 | export ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" 5 | if [ -d "$ZSH_CUSTOM/plugins/zsh-autocomplete" ]; then 6 | sed -i 's/local -P/local/g' ~/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_autocomplete__command 7 | fi 8 | 9 | # 1. Instant prompt (Powerlevel10k) 10 | if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then 11 | source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" 12 | fi 13 | 14 | # 2. Set up oh-my-zsh and theme 15 | export ZSH="$HOME/.oh-my-zsh" 16 | ZSH_THEME="powerlevel10k/powerlevel10k" 17 | 18 | # 3. Add the containerlab completions directory to $fpath. 19 | fpath=("$ZSH_CUSTOM/plugins/zsh-autocomplete/Completions" $fpath) 20 | 21 | # 4. Use a lean set of plugins. 22 | plugins=(git) 23 | 24 | # 5. Initialize oh-my-zsh (which also calls compinit so completions are loaded). 25 | source "$ZSH/oh-my-zsh.sh" 26 | 27 | # 6. Load Powerlevel10k configuration if available. 28 | [[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh -------------------------------------------------------------------------------- /zsh/install-tools-completions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Generate gnmic, gnoic, and gh completions: 3 | gnmic completion zsh > "/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_gnmic" 4 | gnoic completion zsh > "/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_gnoic" 5 | gh completion -s zsh > "/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_gh" 6 | 7 | # Generate containerlab completions and add alias "clab": 8 | containerlab completion zsh > "/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_containerlab" 9 | sed -i 's/compdef _containerlab containerlab/compdef _containerlab containerlab clab/g' "/home/clab/.oh-my-zsh/custom/plugins/zsh-autocomplete/Completions/_containerlab" 10 | -------------------------------------------------------------------------------- /zsh/install-zsh-plugins.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install atuin 3 | curl -LsSf https://github.com/atuinsh/atuin/releases/download/v18.3.0/atuin-installer.sh | sh 4 | 5 | # Install Powerlevel10k theme 6 | git clone --depth 1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k" 7 | 8 | # Install zsh-autosuggestions and zsh-autocomplete plugins 9 | git clone --depth 1 https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" 10 | git clone --depth 1 https://github.com/marlonrichert/zsh-autocomplete.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autocomplete" 11 | # Patch zsh-autocomplete to remove unsupported "-P" option: 12 | sed -i 's/local -P/local/g' "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autocomplete/zsh-autocomplete.plugin.zsh" 13 | 14 | # Install syntax highlighting plugin 15 | git clone --depth 1 https://github.com/z-shell/F-Sy-H.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/F-Sy-H" 16 | 17 | # Install containerlab completions via the tools completions script: 18 | bash -c "/tmp/install-tools-completions.sh" --------------------------------------------------------------------------------