├── .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 | 
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 | 
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"
--------------------------------------------------------------------------------