├── .dependencies ├── .editorconfig ├── .github ├── funding.yml └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .remarkrc ├── 0.root.sh ├── 0.user.$windows.ps1 ├── Makefile ├── _pont ├── docs └── images │ └── logo.svg ├── dot.plugin.zsh ├── example ├── .clash ├── .dependencies ├── .deprecated ├── .tags ├── .template │ └── dottargetfile ├── 0.root.apt-get.sh ├── 0.root.pacman.sh ├── 0.user.aura.sh ├── 0.user.fallback.sh ├── 2.user.sh ├── 4.root.sh ├── HOME.template │ └── homefile ├── XDG_CONFIG_HOME.template │ ├── .templaterc │ ├── alias.d │ │ └── .gitkeep │ ├── environment.d │ │ └── .gitkeep │ └── rc.d │ │ └── .gitkeep ├── readme.md └── update.user.sh ├── license ├── pont.1 ├── pont.ini ├── pont.sh ├── readme.md ├── template ├── readme.md └── {{name}} │ └── 0.root.pacman.sh.tpl ├── test ├── .gitignore ├── 00-example.test.sh ├── 01-symlinks.test.sh ├── 02-conditional_symlinks.test.sh ├── 03-check-installed.test.sh ├── 04-check-installed-with-base.test.sh ├── 05-script-permissions.test.sh ├── 06-wsl-systemctl.test.sh ├── 07-non-wsl-systemctl.test.sh ├── 08-list-presets.test.sh ├── 09-list-modules.test.sh ├── 10-list-outdated-modules.test.sh ├── 11-list-deprecated.test.sh ├── 12-list-tags.test.sh ├── 13-show-help.test.sh ├── 14-fallback.test.sh ├── 15-lone-fallback.test.sh ├── cleanup.sh ├── env.sh ├── modules │ ├── base │ │ ├── .gitkeep │ │ └── .tags │ ├── conditional_symlinks │ │ ├── .$DO_LINK.conditional_symlinks │ │ │ └── dolinkme │ │ ├── .$DO_NOT_LINK.conditional_symlinks │ │ │ └── dontlinkme │ │ └── init.user.sh │ ├── deprecated │ │ └── .deprecated │ ├── fallback │ │ ├── 0.user.fallback.sh │ │ └── 0.user.unavailable.sh │ ├── lone_fallback │ │ └── 0.user.fallback.sh │ ├── permissions │ │ ├── 0.root.sh │ │ ├── 0.user.sh │ │ └── readme.md │ ├── symlinks │ │ ├── .symlinks │ │ │ └── symlinksfile │ │ ├── ABSOLUTE_TARGET.symlinks │ │ │ └── absolute │ │ ├── RELATIVE_TARGET.symlinks │ │ │ └── relative │ │ └── init.user.sh │ └── systemd │ │ └── 0.user.systemctl.sh ├── presets │ ├── a.preset │ └── more │ │ └── b.preset └── readme.md └── todos.md /.dependencies: -------------------------------------------------------------------------------- 1 | # This file is used by `pont` itself 2 | # This module should not rely on other modules so it can be used without 3 | # modification. If you with to extend it, make a wrapper module and mark 4 | # this as it's dependency. See `pont-extra` among my dotmodules. 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | max_line_length = 80 12 | 13 | [*.{md,yml}] 14 | max_line_length = off 15 | trim_trailing_whitespace = false 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.txt] 20 | insert_final_newline = false 21 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: AlexAegis 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: AlexAegis 12 | custom: ["https://paypal.me/alexaegis"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | with: 15 | fetch-depth: 1 16 | - name: Environment 17 | run: | 18 | SUDO_AVAIL=$(if command -v sudo > /dev/null; then echo 1; fi) 19 | echo "SUDO_AVAIL=$SUDO_AVAIL" >> $GITHUB_ENV 20 | - name: Update repositories 21 | run: | 22 | ${SUDO_AVAIL:+sudo} apt-get update 23 | - name: Install dependencies 24 | run: | 25 | ${SUDO_AVAIL:+sudo} apt-get install -y make 26 | - name: Cache shellcheck 27 | id: cache-shellcheck 28 | uses: actions/cache@v1 29 | with: 30 | path: shellcheck-stable 31 | key: ${{ runner.os }}-shellcheck 32 | - name: Install shellcheck download only dependencies 33 | if: steps.cache-shellcheck.outputs.cache-hit != 'true' 34 | run: | 35 | ${SUDO_AVAIL:+sudo} apt-get install -y wget tar xz-utils 36 | - name: Download shellcheck and dependencies 37 | if: steps.cache-shellcheck.outputs.cache-hit != 'true' 38 | run: | 39 | wget -qO- "https://github.com/koalaman/shellcheck/releases\ 40 | /download/stable\ 41 | /shellcheck-stable.linux.x86_64.tar.xz" | tar -xJv 42 | ${SUDO_AVAIL:+sudo} \ 43 | cp "shellcheck-stable/shellcheck" /usr/bin/ 44 | - name: Install shellcheck 45 | run: | 46 | ${SUDO_AVAIL:+sudo} \ 47 | cp "shellcheck-stable/shellcheck" /usr/bin/ 48 | - name: Check shellcheck version 49 | run: shellcheck --version 50 | - name: Lint 51 | run: make lint 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | with: 15 | fetch-depth: 1 16 | - name: Environment 17 | run: | 18 | SUDO_AVAIL=$(if command -v sudo > /dev/null; then echo 1; fi) 19 | echo "SUDO_AVAIL=$SUDO_AVAIL" >> $GITHUB_ENV 20 | - name: Update repositories 21 | run: | 22 | ${SUDO_AVAIL:+sudo} apt-get update 23 | - name: Install dependencies 24 | run: | 25 | ${SUDO_AVAIL:+sudo} apt-get install -y make stow sudo wget curl 26 | - name: Cache kcov 27 | id: cache-kcov 28 | uses: actions/cache@v1 29 | with: 30 | path: kcov 31 | key: kcov-${{ runner.os }} 32 | - name: Install kcov install dependencies 33 | run: | 34 | ${SUDO_AVAIL:+sudo} apt-get install -y libcurl4-openssl-dev \ 35 | binutils-dev libiberty-dev libdw-dev 36 | - name: Install kcov compile only dependencies 37 | if: steps.cache-kcov.outputs.cache-hit != 'true' 38 | run: | 39 | ${SUDO_AVAIL:+sudo} apt-get install -y python3 tar xz-utils \ 40 | cmake zlib1g-dev libssl-dev build-essential 41 | - name: Build kcov 42 | if: steps.cache-kcov.outputs.cache-hit != 'true' 43 | run: | 44 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz 45 | tar xzf master.tar.gz 46 | mv kcov-master kcov 47 | cd kcov 48 | mkdir -p build 49 | cd build 50 | cmake .. 51 | make 52 | - name: Install Kcov 53 | run: | 54 | cd kcov/build 55 | sudo make install 56 | - name: Test 57 | timeout-minutes: 1 58 | run: make test 59 | - name: Upload coverage result to codecov 60 | run: bash <(curl -s https://codecov.io/bash) 61 | - name: Upload coverage result to codacy 62 | run: | 63 | export CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} 64 | bash <(curl -Ls https://coverage.codacy.com/get.sh) 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | # Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | .tarhash 13 | .DS_Store 14 | 15 | # logfiles 16 | *.log 17 | 18 | coverage 19 | test/target 20 | 21 | codecov-reporter.sh 22 | codacy-reporter.sh 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/.gitmodules -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "remark-preset-lint-recommended", 4 | [ 5 | "list-item-indent", 6 | false 7 | ] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /0.root.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If this module is installed with --no-root and this script doesn't 4 | # execute, worry not, if you have stow and xdg modules set up, it will be 5 | # available on your path. (Given that XDG_BIN_HOME is defined in the 6 | # xdg module, and on the PATH, and is sourced.) 7 | 8 | # when running directly, but from anywhere else than it's location 9 | script_location=$( 10 | cd "${0%/*}" || exit 11 | pwd 12 | ) 13 | 14 | # Using a symlink to make pont available without modifying the PATH 15 | 16 | ln -sf "$script_location/pont.sh" "/usr/local/bin/pont" 17 | 18 | ## Install man page 19 | 20 | # install -g 0 -o 0 -m 0644 pont.1 /usr/local/man/man8/ 21 | # gzip /usr/local/man/man8/pont.1 22 | -------------------------------------------------------------------------------- /0.user.$windows.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | 3 | # Custom folder on path, left here for reference 4 | 5 | # $appdata = [Environment]::GetFolderPath([Environment+SpecialFolder]::ApplicationData) 6 | # $userbin = "$appdata\Local\bin\" 7 | # New-Item -ItemType Directory -Force -Path $userbin 8 | # New-Item -ItemType HardLink -Force -Path "$PSScriptRoot\pont.sh" -Target "$userbin\pont" 9 | # 10 | # $currentUserPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::User) 11 | # 12 | # if (-not $currentUserPath -like "*$userbin*") { 13 | # [Environment]::SetEnvironmentVariable( 14 | # "Path", 15 | # "$currentUserPath;$userbin", 16 | # [EnvironmentVariableTarget]::User) 17 | # } 18 | 19 | # The same PATH variable is available from WSL and MINGW64 20 | New-Item -ItemType HardLink -Force -Path "$env:windir\pont" -Target "$PSScriptRoot\pont.sh" 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # To run a single test: `make test/1.test` 2 | # To run all tests: `make test` 3 | 4 | MAKEFLAGS += -k # keep running on failure 5 | MAKEFLAGS += -j1 # explicitly run on a single thread, 6 | # more would cause problems on coverage report merging 7 | 8 | SHELL := /bin/sh 9 | COV_COM:='kcov --include-pattern=pont.sh --exclude-pattern=coverage coverage' 10 | 11 | all_tests := $(basename $(wildcard test/*.test.sh)) 12 | all_lint_formats := $(addsuffix .lint, sh dash bash ksh) 13 | 14 | .PHONY: test all %.test lint %.lint clean 15 | 16 | list_tests: 17 | @echo $(all_tests) 18 | 19 | %.test: %.test.sh 20 | @IFS=' ' COVERAGE=$(COV_COM) $(SHELL) $@.sh && \ 21 | { echo "Test $@.sh successful!"; test/cleanup.sh; exit 0; } || \ 22 | { echo "Test $@.sh failed!"; test/cleanup.sh; exit 1; } 23 | 24 | test_all: $(all_tests) 25 | 26 | test: test_all 27 | @echo "Success, all tests passed." 28 | 29 | list_all_lint_formats: 30 | @echo $(all_lint_formats) 31 | 32 | %.lint: 33 | @git ls-tree -r master --name-only | \ 34 | grep -e '\.sh$$' -e '\.bash$$' -e '\.ksh$$' -e '\.bashrc$$' -e \ 35 | '\.bash_profile$$' -e '\.bash_login$$' -e '\.bash_logout$$' | \ 36 | xargs shellcheck && \ 37 | echo "Lint $(basename $@) successful" || \ 38 | echo "Lint $@ failed" 39 | 40 | lint_all: $(all_lint_formats) 41 | 42 | lint: lint_all 43 | @echo "Success, all lints passed." 44 | 45 | clean: 46 | @echo "Clean" 47 | rm -f .tarhash 48 | -------------------------------------------------------------------------------- /_pont: -------------------------------------------------------------------------------- 1 | #compdef _pont pont 2 | 3 | DOTFILES_HOME=${DOTFILES_HOME:-~/.config/dotfiles} 4 | 5 | function __get_presets { 6 | find ${PONT_PRESETS_HOME:-$DOTFILES_HOME/presets/*} -regex '.*.preset' \ 7 | -maxdepth 1 -mindepth 1 | 8 | sed -e 's|.*/||' -e 's/.preset//' -e 's/^/+/' -e 's/$/ /' 9 | } 10 | 11 | function __get_tags { 12 | find ${PONT_MODULES_HOME:-$DOTFILES_HOME/modules/*} \ 13 | -maxdepth 1 -mindepth 1 -name '.tags' \ 14 | -exec cat {} + | grep "^[^#;]" | 15 | sort | uniq | sed -e 's/^/:/' -e 's/$/ /' 16 | } 17 | 18 | function _pont_modules { 19 | _path_files -S " " -r " " -/ -W ${PONT_MODULES_HOME:-$DOTFILES_HOME/modules} 20 | } 21 | 22 | function _pont_presets { 23 | local presets=("${(@f)$(__get_presets)}") 24 | _multi_parts " " 'presets' 25 | } 26 | 27 | function _pont_tags { 28 | local tags=("${(@f)$(__get_tags)}") 29 | _multi_parts " " 'tags' 30 | } 31 | 32 | function _pont_all { 33 | _pont_modules 34 | _pont_presets 35 | _pont_tags 36 | } 37 | 38 | function _pont { 39 | _arguments -S -C \ 40 | '(-h --help)'{-h,--help}'[Print information on usage and flags then exit]' \ 41 | '(-V --version)'{-V,--version}'[Print script version then exit]' \ 42 | '(-l --log --log-level)'{-l,--log,--log-level}'[set log level, possible values are: 43 | 0, trace, TRACE 44 | 1, info, INFO 45 | 2, warning, WARNING, success, SUCCESS 46 | 3, error, ERROR 47 | 4, none, NONE 48 | each option in a line mean the same thing]'\ 49 | '(-v --verbose)'{-v,--verbose}'[log level 0 (trace)]' \ 50 | '(-q --quiet)'{-q,--quiet}'[log level 3 (error)]' \ 51 | '(-I --list-installed)'{-I,--list-installed}'[List all installed modules then exit]' \ 52 | '(-A --list-modules)'{-A,--list-modules}'[List all modules then exit]' \ 53 | '(-D --list-deprecated)'{-D,--list-deprecated}'[List all deprecated modules then exit]' \ 54 | '(-P --list-presets)'{-P,--list-presets}'[List all presets then exit]' \ 55 | '(-T --list-tags)'{-t,--list-tags}'[List all tags then exit]' \ 56 | '(-E --list-environment)'{-E,--list-environment}'[List the config environment then exit]' \ 57 | '(-L --list-install)'{-L,--list-install}'[List the resolved final module list then exit]' \ 58 | '(-Q --list-queue)'{-Q,--list-queue}'[List the execution queue then exit]' \ 59 | '(-O --list-outdated)'{-O,--list-outdated}'[List all outdated (installed but has hash mismatch) modules then exit]' \ 60 | '(-C --toggle-clean-symlinks)'{-C,--toggle-clean-symlinks}'[Removes broken symlinks in the target directory. (By default it turns on the feautre, but if it was turned on by the environment it turns it off.)]' \ 61 | '(-X --toggle-fix-permissions)'{-X,--toggle-fix-permissions}'[Adds user execute permissions to all module scripts before running them.]' \ 62 | '(-p --pull-dotfiles)'{-p,--pull-dotfiles}'[Perform git pull on the dotfiles home folder]' \ 63 | '(-u --update)'{-u,--update}'[Run all scrips starting with u in the selected modules.]' \ 64 | '(-x --execute --install)'{-x,--execute,--install}'[Run init scripts, stow configs, then run scripts starting with a number and the Makefile in all the selected modules.]' \ 65 | '(-r --remove)'{-r,--remove}'[Unstows every stow package in the selected modules. If this flag is added twice it will also run all scrips starting with r in the selected modules.]' \ 66 | '(-n --expand-none)'{-n,--expand-none}'[Expands only the abstract entries in the direct selection. No dependencies are resolved]' \ 67 | '(-e --expand-selected)'{-e,--expand-seleted}'[Expands the original selection (the argument list) down to its dependencies recursively. Use this for regular installations.]' \ 68 | '(-a --expand-all)'{-a,--expand-all}'[Expands every module regardless of the current selection.]' \ 69 | '(-i --expand-installed)'{-i,--expand-installed}'[Expands every installed module regardless of the current selection. Useful for batch running update and backup scripts]' \ 70 | '(-o --expand-outdated)'{-o,--expand-outdated}'[Expands every installed module regardless of the current selection thats saved hash is no longer matching a freshly calculated one. Useful for batch refreshing modules after modifying them]' \ 71 | '(-d --dry)'{-d,--dry}'[Disables modifications. No stowing, no script execution. Useful for testing flag combinations.]' \ 72 | '(-w --wet)'{-w,--wet}'[Enabled modifications. Stowing, Script execution. On by default.]' \ 73 | '(-b --skip-base)'{-b,--skip-base}'[Skip the base modules when expanding selection (Only useful before the -e flag).]' \ 74 | '(-f --force)'{-f,--force}'[Ignores hashfiles. To avoid accidentally installing large dependency trees, this automatically turns on --expand-none. Expansion can be changed after.]' \ 75 | '(-c --config)'{-c,--config}'[Instead of the selection in the argument list, select entries in a TUI with whiptail.]' \ 76 | '--root[Enables root privileged script execution. On by default.]' \ 77 | '(-R --skip-root)'{-R,--skip-root}'[Disables root privileged script execution.]' \ 78 | '(-s --scripts)'{-s,--scripts}'[Enables script execution. On by default.]' \ 79 | '(-S --skip-scripts)'{-S,--skip-scripts}'[Disables script execution. Its like dry execution but with stowing enabled.]' \ 80 | '(-m --make)'{-m,--make}'[Enables Makefile execution. On by default.]' \ 81 | '(-M --skip-make)'{-M,--skip-make}'[Disables Makefile execution.]' \ 82 | '(-t --target)'{-t,--target}'[Its value will specify PONT_TARGET for this execution.]' \ 83 | '(--scaffold --cpt)'{--scaffold,--cpt}'[Instead of executing modules, the selection now will used to scaffold modules based on a template folder at PONT_TEMPLATE_HOME]' \ 84 | '(-y --yank)'{-y,--yank}'[will yank the selection (with none expansion by default) to the target folder. Useful for copying modules along with their dependencies. (Used presets are also copied!)]' \ 85 | '(-Y --yank-expanded)'{-y,--yank}'[will yank the expanded selection (same as -ey) to the target folder.]' \ 86 | "*:: :{ _pont_all }" 87 | } 88 | 89 | -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dot.plugin.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Empty entry point so Antigen can pick it up and put the folder on fpath 4 | -------------------------------------------------------------------------------- /example/.clash: -------------------------------------------------------------------------------- 1 | # TODO: Clarify 2 | 3 | # This file containes modules that can't be present when this module 4 | # is installed. 5 | 6 | # When installing and two clashing modules are present then ask which one to 7 | # chose. If started with --no-interactive then just abort and print the clash. 8 | 9 | # When installing and resolving the selection, check the final module list 10 | # for clashes among itself. If there is any, print an error and ask for input 11 | # Then when its executing. Each time a module with a clashlist is executed. 12 | # Hard remove every installed, clashing module. 13 | -------------------------------------------------------------------------------- /example/.dependencies: -------------------------------------------------------------------------------- 1 | # Dependency list 2 | # Comments are supported 3 | # Each line has to match another module 4 | sh 5 | # dependencies can be conditional. Everything after the `?` will be `eval`-d 6 | rust ? [ $debian ] || [ $ubuntu ] # This is still a valid comment 7 | 8 | # For the list of supported variables check the readme.md 9 | -------------------------------------------------------------------------------- /example/.deprecated: -------------------------------------------------------------------------------- 1 | # If this file is present then the module is going to be ignored by `pont` and 2 | # a warning will be thrown when trying to install it. (Installation still can 3 | # be forced) 4 | 5 | # Here you can also list alternatives, then this file will be interpreted 6 | # as 'deprecated in favour of...' 7 | # In this case, these will be installed instead of this 8 | 9 | # Supports all expressions that the `dependencies` file supports 10 | 11 | # Example: 12 | 13 | other-arch-module ? [ $arch ] 14 | other-void-module ? [ $void ] 15 | +ubuntu-alternatives ? [ $ubuntu ] 16 | 17 | # Usually it's much simpler, and a single module is used 18 | 19 | # Why not just delete the module instead of marking it as deprecated? 20 | # You can, of course. But I don't delete modules for two reason: 21 | # - To keep them as reference 22 | # - To remind me that I already tried it, and how I did it 23 | # Later it might get some improvements that would make me rethink it's usage 24 | -------------------------------------------------------------------------------- /example/.tags: -------------------------------------------------------------------------------- 1 | # Each line here defines a tag 2 | # When installing modules by tag, `pont` will search for the presence 3 | # of the tags for each module. Tags are referenced with a : prefix 4 | # A dependency of `:font` means every module that is tagged as `font` 5 | base 6 | gui 7 | tui 8 | -------------------------------------------------------------------------------- /example/.template/dottargetfile: -------------------------------------------------------------------------------- 1 | # This file will be stowed to $PONT_TARGET which is by default $HOME 2 | -------------------------------------------------------------------------------- /example/0.root.apt-get.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file requires root as it installs packages using a package manager 4 | # for example with pacman or apt-get 5 | -------------------------------------------------------------------------------- /example/0.root.pacman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # When installing with pacman always include the --needed and --noconfirm flags 4 | # so `pont` can work uninterruptedly 5 | pacman -Syu --needed --noconfirm PACKAGE 6 | 7 | # When installing with yay you shoud never use sudo. 8 | # This will sudo back to the user of the original sudo command. 9 | # If both pacman and yay needed, it's easier to just make another, 10 | # non-root privileged install script 11 | ${SUDO_USER:+sudo -u $SUDO_USER} \ 12 | yay -Syu --needed --noconfirm PACKAGE 13 | -------------------------------------------------------------------------------- /example/0.user.aura.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # When installing with yay/paru/aura you shoud never use sudo. Always put this 4 | # in a non root privileged script. 5 | # Alternatively you can use this to sudo back if necessary: 6 | # ${SUDO_USER:+sudo -u $SUDO_USER} aura ... 7 | aura -A --noconfirm PACKAGE 8 | -------------------------------------------------------------------------------- /example/0.user.fallback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This particular fallback script would only run if nothing in the order group 4 | # 0 would run. Because neither pacman, nor yay, nor apt is available. 5 | # This file then would contain a platform agnostic way of installation 6 | # usually by compiling something by source. Since compilation can take a long 7 | # time, using prebuild packages is preferred where it's possible. 8 | -------------------------------------------------------------------------------- /example/2.user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Module path can be accessed through $PWD because `pont` changes directory to 3 | # the module before running anything 4 | 5 | # This script should never require root priviliges 6 | 7 | -------------------------------------------------------------------------------- /example/4.root.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # When some scripts need root privileges, use this name 4 | -------------------------------------------------------------------------------- /example/HOME.template/homefile: -------------------------------------------------------------------------------- 1 | # This file will always be stowed to $HOME 2 | -------------------------------------------------------------------------------- /example/XDG_CONFIG_HOME.template/.templaterc: -------------------------------------------------------------------------------- 1 | # This file would get stowed to the XDG_CONFIG_HOME folder 2 | # and so are the other folders 3 | -------------------------------------------------------------------------------- /example/XDG_CONFIG_HOME.template/alias.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/example/XDG_CONFIG_HOME.template/alias.d/.gitkeep -------------------------------------------------------------------------------- /example/XDG_CONFIG_HOME.template/environment.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/example/XDG_CONFIG_HOME.template/environment.d/.gitkeep -------------------------------------------------------------------------------- /example/XDG_CONFIG_HOME.template/rc.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/example/XDG_CONFIG_HOME.template/rc.d/.gitkeep -------------------------------------------------------------------------------- /example/readme.md: -------------------------------------------------------------------------------- 1 | # Dotmodule example 2 | 3 | Read the individual files for explanation 4 | 5 | ## Why do I use separate files for package managers 6 | 7 | - Easier to use 8 | - Some modules need multiple packages installed of which I don't want to make 9 | a module. (module != package) 10 | -------------------------------------------------------------------------------- /example/update.user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # For any kind of maintanence 4 | # If a module requires manual update sometimes, `pont` can help you run update 5 | # scipts on all installed dotmodules using `pont -au` (all update) 6 | 7 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Győri Sándor (AlexAegis) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pont.1: -------------------------------------------------------------------------------- 1 | MAN 2 | -------------------------------------------------------------------------------- /pont.ini: -------------------------------------------------------------------------------- 1 | # pont.ini alternative config concept 2 | [Dependencies] 3 | xdg 4 | stow 5 | 6 | [Tags] 7 | dotmodule_manager 8 | 9 | [Clashes] 10 | 11 | [Deprecated] 12 | 13 | [Packages] 14 | 15 | [Services] 16 | -------------------------------------------------------------------------------- /pont.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # /$$ 3 | # | $$ 4 | # /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ 5 | # /$$__ $$ /$$__ $$| $$__ $$|_ $$_/ 6 | # | $$ \ $$| $$ \ $$| $$ \ $$ | $$ 7 | # | $$ | $$| $$ | $$| $$ | $$ | $$ /$$ 8 | # | $$$$$$$/| $$$$$$/| $$ | $$ | $$$$/ 9 | # | $$____/ \______/ |__/ |__/ \___/ 10 | # | $$ 11 | # | $$ 12 | # |__/ 13 | # 14 | # - The dotmodule manager 15 | # 16 | # Copyright (c) 2020-2024 Győri Sándor (AlexAegis) 17 | # 18 | # Permission is hereby granted, free of charge, to any person obtaining a copy 19 | # of this software and associated documentation files (the "Software"), to deal 20 | # in the Software without restriction, including without limitation the rights 21 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | # copies of the Software, and to permit persons to whom the Software is 23 | # furnished to do so, subject to the following conditions: 24 | # 25 | # The above copyright notice and this permission notice shall be included in all 26 | # copies or substantial portions of the Software. 27 | # 28 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | # SOFTWARE. 35 | # 36 | 37 | C_RESET='\033[0m' 38 | C_RED='\033[0;31m' 39 | C_GREEN='\033[0;32m' 40 | C_YELLOW='\033[0;33m' 41 | C_BLUE='\033[0;34m' 42 | C_CYAN='\033[0;36m' 43 | 44 | # Make all the variables (except IFS) in this script available to the subshells 45 | set -a 46 | 47 | is_installed() { 48 | # explicit check of wsl and systemd pairings 49 | if [ "$1" = "systemctl" ] && [ "$wsl" ]; then 50 | return 1 51 | fi 52 | command -v "$1" 2>/dev/null 1>/dev/null 53 | } 54 | 55 | get_home() { 56 | # This solution returns the home folder of the original invoker of sudo 57 | if is_installed getent; then 58 | getent passwd "${SUDO_USER:-$USER}" | cut -d: -f6 59 | else 60 | # On MINGW getent is not available, but elevated privileges don't 61 | # change the home folder either, so this should be enough 62 | echo "$HOME" 63 | fi 64 | } 65 | 66 | # Environment 67 | user_home=$(get_home) 68 | uname_result="$(uname -s)" 69 | 70 | case "${uname_result}" in 71 | Linux*) os_type="linux";; 72 | Darwin*) os_type="mac";; 73 | CYGWIN*|MINGW32*|MSYS*|MINGW*) os_type="windows";; 74 | FreeBSD*) os_type="freebsd";; 75 | *) os_type="unknown:${uname_result}" 76 | esac 77 | 78 | # Normalize XDG variables according to the spec (Set it only if absent) 79 | XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$user_home/.config"} 80 | XDG_CACHE_HOME=${XDG_CACHE_HOME:-"$user_home/.cache"} 81 | XDG_DATA_HOME=${XDG_DATA_HOME:-"$user_home/.local/share"} 82 | XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-"$user_home/.cache/run"} 83 | XDG_BIN_HOME=${XDG_BIN_HOME:-"$user_home/.local/bin"} 84 | 85 | # Environmental config 86 | 87 | script_location=$( 88 | cd "${0%\/*}" || exit 89 | pwd 90 | ) 91 | # if it has been installed 92 | if [ -L "$script_location/pont" ]; then 93 | script_link_location=$(readlink "$script_location/pont") 94 | if ! [ "${script_link_location#\/}" = "$script_link_location" ]; then 95 | # absulte link 96 | script_location="$script_link_location" 97 | else 98 | # relative link 99 | script_location="$script_location/$script_link_location" 100 | fi 101 | script_location=${script_location%\/*} # cut filename 102 | fi 103 | 104 | 105 | ## This where the packages will be stowed to. Can also be set with -t 106 | PONT_TARGET=${PONT_TARGET:-"$user_home"} 107 | ## By default, assume this script is two levels in the dotfiles folder 108 | DOTFILES_HOME=${DOTFILES_HOME:-"$script_location/../.."} 109 | # TODO: Support multiple folders $IFS separated, quote them 110 | PONT_MODULES_HOME=${PONT_MODULES_HOME:-"$DOTFILES_HOME/modules"} 111 | PONT_PRESETS_HOME=${PONT_PRESETS_HOME:-"$DOTFILES_HOME/presets"} 112 | PONT_TEMPLATE_HOME=${PONT_TEMPLATE_HOME:-"$DOTFILES_HOME/template"} 113 | 114 | # Config 115 | PONT_LOG_LEVEL=${PONT_LOG_LEVEL:-1} 116 | PONT_CONFIG_FLAG=${PONT_CONFIG_FLAG:-0} 117 | PONT_DRY_FLAG=${PONT_DRY_FLAG:-0} 118 | PONT_FORCE_FLAG=${PONT_FORCE_FLAG:-0} 119 | PONT_NO_BASE_FLAG=${PONT_NO_BASE_FLAG:-0} 120 | PONT_ROOT_FLAG=${PONT_ROOT_FLAG:-1} 121 | PONT_SCRIPTS_ENABLED=${PONT_SCRIPTS_ENABLED:-1} 122 | PONT_MAKE_ENABLED=${PONT_MAKE_ENABLED:-1} 123 | PONT_PRESET_EXTENSION=${PONT_PRESET_EXTENSION:-".preset"} 124 | PONT_HASHFILE_NAME=${PONT_HASHFILE_NAME:-".tarhash"} 125 | PONT_DEPRECATIONFILE_NAME=${PONT_DEPRECATIONFILE_NAME:-".deprecated"} 126 | PONT_DEPENDENCIESFILE_NAME=${PONT_DEPENDENCIESFILE_NAME:-".dependencies"} 127 | PONT_CONDITIONFILE_NAME=${PONT_CONDITIONFILE_NAME:-".condition"} 128 | PONT_CLASHFILE_NAME=${PONT_CLASHFILE_NAME:-".clash"} 129 | PONT_TAGSFILE_NAME=${PONT_TAGSFILE_NAME:-".tags"} 130 | PONT_DEFAULT_EXPANSION_ACTION=${PONT_DEFAULT_EXPANSION_ACTION:-"action_expand_none"} 131 | PONT_CLEAN_SYMLINKS=${PONT_CLEAN_SYMLINKS:-0} 132 | PONT_FIX_PERMISSIONS=${PONT_FIX_PERMISSIONS:-0} 133 | 134 | HASH_COMMAND= 135 | if is_installed sha1sum; then 136 | HASH_COMMAND="sha1sum" 137 | elif is_installed shasum; then 138 | HASH_COMMAND="shasum" 139 | fi 140 | 141 | if [ -z "$HASH_COMMAND" ]; then 142 | echo 'No hasher available: sha1sum or shasum' >&2 143 | exit 1 144 | fi 145 | 146 | ## Precalculated environmental variables for modules 147 | 148 | # OS 149 | linux=$(if [ "$os_type" = "linux" ]; then echo 1; fi) 150 | export linux 151 | mac=$(if [ "$os_type" = "mac" ]; then echo 1; fi) 152 | export mac 153 | windows=$(if [ "$os_type" = "windows" ]; then echo 1; fi) 154 | export windows 155 | bsd=$(if [ "$os_type" = "freebsd" ]; then echo 1; fi) 156 | export bsd 157 | 158 | wsl=$(if grep -qEi "(Microsoft|WSL)" /proc/version \ 159 | 2>/dev/null 1>/dev/null; then echo 1; fi) 160 | export wsl 161 | # wsl is always headless, others should be configured in pontrc 162 | headless=$wsl 163 | export headless 164 | # Package manager 165 | pacman=$(if is_installed pacman; then echo 1; fi) 166 | export pacman 167 | apt=$(if is_installed apt; then echo 1; fi) 168 | export apt 169 | xbps=$(if is_installed xbps; then echo 1; fi) 170 | export xbps 171 | emerge=$(if is_installed emerge; then echo 1; fi) 172 | export emerge 173 | # Init system 174 | sysctl=$(if is_installed sysctl; then echo 1; fi) 175 | export sysctl 176 | systemctl=$(if is_installed systemctl; then echo 1; fi) 177 | export systemctl 178 | systemd=$systemctl # alias 179 | export systemd 180 | openrc=$(if is_installed rc-service; then echo 1; fi) 181 | export openrc 182 | # other features 183 | if is_installed ldd; then 184 | pam=$(if ldd /bin/su | grep -q pam; then echo 1; fi) 185 | fi 186 | export pam 187 | # Distribution 188 | # ! May not be available on some systems 189 | distribution=$(grep "^NAME" /etc/os-release 2>/dev/null | grep -oh "=.*" | \ 190 | tr -d '="') 191 | export distribution 192 | # It uses if and not && because when using && a new line would 193 | # return on false evaluation. `If` captures the output of test 194 | alarm=$(if [ "$distribution" = 'Arch Linux ARM' ]; then echo 1; fi) 195 | export alarm 196 | archx86=$(if [ "$distribution" = 'Arch Linux' ]; then echo 1; fi) 197 | export archx86 198 | arch=$(if [ "$alarm" ] || [ "$archx86" ]; then echo 1; fi) 199 | export arch 200 | gentoo=$(if [ "$distribution" = 'Gentoo' ]; then echo 1; fi) 201 | export gentoo 202 | void=$(if [ "$distribution" = 'Void Linux' ]; then echo 1; fi) 203 | export void 204 | debian=$(if [ "$distribution" = 'Debian GNU/Linux' ]; then echo 1; fi) 205 | export debian 206 | ubuntu=$(if [ "$distribution" = 'Ubuntu' ]; then echo 1; fi) 207 | export ubuntu 208 | fedora=$(if [ "$distribution" = 'Fedora' ]; then echo 1; fi) 209 | export fedora 210 | 211 | # Config file sourcing 212 | # shellcheck disable=SC1090 213 | [ -e "$XDG_CONFIG_HOME/pont/pontrc" ] && . "$XDG_CONFIG_HOME/pont/pontrc" 214 | # shellcheck disable=SC1090 215 | [ -e "$XDG_CONFIG_HOME/pontrc" ] && . "$XDG_CONFIG_HOME/pontrc" 216 | # shellcheck disable=SC1090 217 | [ -e "$user_home/.pontrc" ] && . "$user_home/.pontrc" 218 | # shellcheck disable=SC1091 219 | [ -e "./.pontrc" ] && . "./.pontrc" 220 | 221 | set +a 222 | 223 | # Inner variables that are not allowed to be changed using pontrc 224 | all_modules= 225 | all_presets= 226 | all_installed_modules= 227 | all_outdated_modules= 228 | all_deprecated_modules= 229 | all_tags= 230 | yank_target= 231 | resolved= 232 | expanded_presets= 233 | entries_selected= 234 | final_module_list= 235 | expand_abstract_only= 236 | 237 | # Newline separated list of actions. Used to preserve order of flags 238 | execution_queue= 239 | 240 | ## Internal functions 241 | 242 | is_deprecated() { 243 | # TODO: Implement conditional deprecation 244 | [ -e "$PONT_MODULES_HOME/$1/$PONT_DEPRECATIONFILE_NAME" ] 245 | } 246 | 247 | module_exists() { 248 | [ ! "$all_modules" ] && get_all_modules 249 | echo "$all_modules" | grep -x "$1" 250 | } 251 | 252 | get_all_modules() { 253 | all_modules=$(find "$PONT_MODULES_HOME/" -maxdepth 1 -mindepth 1 \ 254 | -type d | sed 's|.*/||' | sort) 255 | } 256 | 257 | get_all_presets() { 258 | all_presets=$(find "$PONT_PRESETS_HOME/" -mindepth 1 \ 259 | -type f -name '*.preset' | sed -e 's|.*/||' -e 's/.preset//' | sort) 260 | } 261 | 262 | get_all_installed_modules() { 263 | #shellcheck disable=SC2016 264 | all_installed_modules=$(grep -l -- "" \ 265 | "$PONT_MODULES_HOME"/**/"$PONT_HASHFILE_NAME" | \ 266 | sed -r 's_^.*/([^/]*)/[^/]*$_\1_g' | sort) 267 | } 268 | 269 | echo_all_outdated_modules() { 270 | [ ! "$all_modules" ] && get_all_modules 271 | for mod in $all_modules; do 272 | if [ -e "$PONT_MODULES_HOME/$mod/$PONT_HASHFILE_NAME" ]; then 273 | fresh_hash="$(do_hash "$mod")" 274 | old_hash="$(cat "$PONT_MODULES_HOME/$mod/$PONT_HASHFILE_NAME")" 275 | [ "$fresh_hash" != "$old_hash" ] && echo "$mod" 276 | fi 277 | done 278 | } 279 | 280 | get_all_outdated_modules() { 281 | all_outdated_modules=$(echo_all_outdated_modules) 282 | } 283 | 284 | get_all_deprecated_modules() { 285 | #shellcheck disable=SC2016 286 | all_deprecated_modules=$(find "$PONT_MODULES_HOME"/*/ -maxdepth 1 \ 287 | -mindepth 1 -type f -name "$PONT_DEPRECATIONFILE_NAME" | \ 288 | sed -r 's_^.*/([^/]*)/[^/]*$_\1_g' | sort) 289 | } 290 | 291 | get_all_tags() { 292 | all_tags=$(find "$PONT_MODULES_HOME"/*/ -maxdepth 1 -mindepth 1 \ 293 | -type f -name '.tags' -exec cat {} + | \ 294 | sed -e 's/ *#.*$//' -e '/^$/d' | sort | uniq) 295 | } 296 | 297 | rename_module() { 298 | # $1 from 299 | # $2 to 300 | log_info "Rename module $1 to $2" 301 | # TODO: Check if from exists, fail if not 302 | # TODO: Check if to exists, fail if is 303 | # TODO: Rename module folder, 304 | # TODO: Rename all packages ending inside it 305 | # TODO: rename all references in dependency files, presets 306 | # TODO: expand it to tags and presets 307 | # TODO: reinstall if it was installed 308 | echo "Not implemented!" 309 | } 310 | 311 | # Unused, left here for reference 312 | # dequeue() { 313 | # # remove last or remove the supplied items 314 | # if [ ! "$1" ]; then 315 | # execution_queue=$(echo "$execution_queue" | sed '$ d') 316 | # return 317 | # fi 318 | # while [ "$1" ]; do 319 | # execution_queue=$(echo "$execution_queue" | grep -v "$1") 320 | # shift 321 | # done 322 | # } 323 | 324 | enqueue() { 325 | log_trace "Enqueuing $*" 326 | while [ "$1" ]; do 327 | if [ "$execution_queue" ]; then 328 | execution_queue="${execution_queue}${IFS:-\0}${1}" 329 | else 330 | execution_queue="${1}" 331 | fi 332 | shift 333 | done 334 | } 335 | 336 | # Unused, left here for reference 337 | # enqueue_front() { 338 | # log_trace "Enqueuing to the front $*" 339 | # while [ "$1" ]; do 340 | # if [ "$execution_queue" ]; then 341 | # execution_queue="${1}${IFS:-\0}${execution_queue}" 342 | # else 343 | # execution_queue="${1}" 344 | # fi 345 | # shift 346 | # done 347 | # } 348 | 349 | # Logging, the default log level is 1 meaning only trace logs are omitted 350 | log_trace() { 351 | # Visible at and under log level 0 352 | [ "${PONT_LOG_LEVEL:-1}" -le 0 ] && \ 353 | echo "${C_CYAN}[ Trace ]: $*${C_RESET}" >&2 354 | } 355 | 356 | log_info() { 357 | # Visible at and under log level 1 358 | [ "${PONT_LOG_LEVEL:-1}" -le 1 ] && \ 359 | echo "${C_BLUE}[ Info ]: $*${C_RESET}" >&2 360 | } 361 | 362 | log_warning() { 363 | # Visible at and under log level 2 364 | [ "${PONT_LOG_LEVEL:-1}" -le 2 ] && \ 365 | echo "${C_YELLOW}[ Warning ]: $*${C_RESET}" >&2 366 | } 367 | 368 | log_success() { 369 | # Visible at and under log level 2, same as warning but green 370 | [ "${PONT_LOG_LEVEL:-1}" -le 2 ] && \ 371 | echo "${C_GREEN}[ Success ]: $*${C_RESET}" >&2 372 | } 373 | 374 | log_error() { 375 | # Visible at and under log level 3 376 | [ "${PONT_LOG_LEVEL:-1}" -le 3 ] && \ 377 | echo "${C_RED}[ Error ]: $*${C_RESET}" >&2 378 | } 379 | 380 | show_help() { 381 | echo "$(show_version) 382 | -h, --help -- Print information on usage and flags then 383 | exit 384 | -V, --version -- Print script version then exit 385 | -l , 386 | --log , 387 | --log-level , -- set log level, possible values are: 388 | 0, trace, TRACE 389 | 1, info, INFO 390 | 2, warning, WARNING, success, SUCCESS 391 | 3, error, ERROR 392 | 4, none, NONE 393 | each option in a line mean the same thing 394 | -v, --verbose -- log level 0 (trace) 395 | -q, --quiet -- log level 3 (error) 396 | -I, --list-installed -- List all installed modules then exit 397 | -A, --list-modules -- List all modules then exit 398 | -D, --list-deprecated -- List all deprecated modules then exit 399 | -P, --list-presets -- List all presets then exit 400 | -T, --list-tags -- List all tags then exit 401 | -E, --list-environment -- List the config environment then exit 402 | -L, --list-install -- List the resolved final module list then exit 403 | -Q, --list-queue -- List the execution queue then exit 404 | -O, --list-outdated -- List all outdated (installed but has hash 405 | mismatch) modules then exit 406 | -C, --toggle-clean-symlinks -- Removes broken symlinks in the target 407 | directory. (By default it turns on the 408 | feautre, but if it was turned on by 409 | the environment it turns it off.) 410 | -X, --toggle-fix-permissions -- Adds user execute permissions to all module 411 | scripts before running them. 412 | -p, --pull-dotfiles -- Perform git pull on the dotfiles home folder 413 | -u, --update -- Run all scrips starting with u in the 414 | selected modules. 415 | -x, --execute, --install -- Run init scripts, stow configs, then run 416 | scripts starting with a number and the 417 | Makefile in all the selected modules. 418 | -r, --remove -- Unstows every stow package in the selected 419 | modules. If this flag is added twice it will 420 | also run all scrips starting with r in the 421 | selected modules. 422 | -n, --expand-none -- Expands only the abstract entries in the 423 | selection. No dependencies are resolved. 424 | -e, --expand-seleted -- Expands the original selection (the 425 | argument list) down to its dependencies 426 | recursively. Use this for regular 427 | installations. 428 | -a, --expand-all -- Expands every module regardless of the 429 | current selection. 430 | -i, --expand-installed -- Expands every installed module regardless 431 | of the current selection. Useful for batch 432 | running update and backup scripts. 433 | -o, --expand-outdated -- Expands every installed module regardless 434 | of the current selection thats saved hash 435 | is no longer matching a freshly calculated 436 | one. Useful for batch refreshing modules 437 | after modifying them. 438 | -d, --dry -- Disables modifications. No stowing, no script 439 | execution. Useful for testing flag 440 | combinations. 441 | -w, --wet -- Enabled modifications. Stowing, Script 442 | execution. On by default. 443 | -b, --skip-base -- Skip the base modules when expanding selection 444 | (Only useful before the -e flag). 445 | -f, --force -- Ignores hashfiles. To avoid accidentally 446 | installing large dependency trees, this 447 | automatically turns on --expand-none. 448 | Expansion can be changed after. 449 | -c --config -- Instead of the selection in the argument 450 | list, select entries in a TUI with whiptail. 451 | --root -- Enables root privileged script execution. 452 | On by default. 453 | -R, --skip-root -- Disables root privileged script execution. 454 | -s, --scripts -- Enables script execution. On by default. 455 | -S, --skip-scripts -- Disables script execution. Its like dry 456 | execution but with stowing enabled. 457 | -m, --make -- Enables Makefile execution. On by default. 458 | -M, --skip-make -- Disables Makefile execution. 459 | -t , --target -- Its value will specify PONT_TARGET for this 460 | execution. 461 | --scaffold, --cpt -- Instead of executing modules, the selection 462 | now will used to scaffold modules based on a 463 | template folder at PONT_TEMPLATE_HOME 464 | -y , --yank -- will yank the selection (with none expansion 465 | by default) to the target folder. Useful for 466 | copying modules along with their 467 | dependencies. (Used presets are also copied!) 468 | -Y , --yank-expanded -- will yank the expanded selection 469 | (same as -ey) to the target folder. 470 | 471 | Some scenarios: 472 | 473 | - Update all installed modules 474 | 475 | pont -iu 476 | 477 | # Expand installed modules, update them 478 | 479 | - Reinstall all modified modules after a editing a lot of them 480 | 481 | pont -ox 482 | 483 | # Expand outdated modules, execute them 484 | 485 | - Safe force. Install dependencies of the selection on demand 486 | and then force install the selection 487 | 488 | pont -exfx zsh 489 | 490 | # expand, execute, force (select none), execute 491 | " 492 | exit 0 493 | } 494 | 495 | show_version() { 496 | echo "pont version: 0.9.0" && exit 0 497 | } 498 | 499 | clean_symlinks() { 500 | # recusively removes every broken symlink in a directory 501 | # used to clean before installation and after uninstallation 502 | # ! TODO: skip protected files, on mac it tries to delete and fail many 503 | # ! extended file permission bits are used 504 | find "${1-$PWD}" -type l -exec \ 505 | sh -c 'for x; do [ -e "$x" ] || rm "$x"; done' _ {} + 2>/dev/null 506 | } 507 | 508 | scaffold() { 509 | # TODO scaffold 510 | # cpt template and pont --scaffold command to create from template 511 | # Use the remaining inputs as module folders to scaffold using cpt 512 | # If cpt is not available then try install it with cargo first 513 | # If no cargo is available then prompt the user to install it 514 | log_info "Scaffolding module $1 using cpt and $PONT_TEMPLATE_HOME \ 515 | as the template" 516 | } 517 | 518 | # Listings 519 | 520 | action_list_execution_queue() { 521 | log_trace "Listing execution queue:" 522 | echo "$execution_queue" 523 | } 524 | 525 | action_list_installed_modules() { 526 | log_trace "All installed modules:" 527 | [ ! "$all_installed_modules" ] && get_all_installed_modules 528 | echo "$all_installed_modules" 529 | } 530 | 531 | action_list_deprecated() { 532 | log_trace "All deprecated modules:" 533 | [ ! "$all_deprecated_modules" ] && get_all_deprecated_modules 534 | echo "$all_deprecated_modules" 535 | } 536 | 537 | action_list_modules() { 538 | log_trace "All available modules:" 539 | [ ! "$all_modules" ] && get_all_modules 540 | echo "$all_modules" 541 | } 542 | 543 | action_list_presets() { 544 | log_trace "All available presets:" 545 | [ ! "$all_presets" ] && get_all_presets 546 | echo "$all_presets" 547 | } 548 | 549 | action_list_tags() { 550 | log_trace "All available tags:" 551 | [ ! "$all_tags" ] && get_all_tags 552 | echo "$all_tags" 553 | } 554 | 555 | action_list_outdated() { 556 | log_trace "Listing outdated modules:" 557 | [ ! "$all_outdated_modules" ] && get_all_outdated_modules 558 | echo "$all_outdated_modules" 559 | } 560 | 561 | action_list_environment() { 562 | log_info "All configurable variables:" 563 | echo "PONT_DRY_FLAG=${PONT_DRY_FLAG:-0}" \ 564 | "PONT_FORCE_FLAG=${PONT_FORCE_FLAG:-0}" \ 565 | "PONT_ROOT_FLAG=${PONT_ROOT_FLAG:-1}" \ 566 | "PONT_CONFIG_FLAG=${PONT_CONFIG_FLAG:-0}" \ 567 | "PONT_PRESET_EXTENSION=$PONT_PRESET_EXTENSION" \ 568 | "PONT_HASHFILE_NAME=$PONT_HASHFILE_NAME" \ 569 | "PONT_DEPENDENCIESFILE_NAME=$PONT_DEPENDENCIESFILE_NAME" \ 570 | "PONT_CLASHFILE_NAME=$PONT_CLASHFILE_NAME" \ 571 | "PONT_TAGSFILE_NAME=$PONT_TAGSFILE_NAME" \ 572 | "PONT_DEFAULT_EXPANSION_ACTION=$PONT_DEFAULT_EXPANSION_ACTION" \ 573 | "wsl=$wsl" \ 574 | "headless=$headless" \ 575 | "pacman=$pacman" \ 576 | "emerge=$emerge" \ 577 | "apt=$apt" \ 578 | "xbps=$xbps" \ 579 | "sysctl=$sysctl" \ 580 | "systemctl=$systemctl" \ 581 | "systemd=$systemd" \ 582 | "openrc=$openrc" \ 583 | "distribution=$distribution" \ 584 | "archx86=$archx86" \ 585 | "alarm=$alarm" \ 586 | "arch=$arch" \ 587 | "gentoo=$gentoo" \ 588 | "void=$void" \ 589 | "debian=$debian" \ 590 | "ubuntu=$ubuntu" \ 591 | "fedora=$fedora" && exit 0 592 | } 593 | 594 | action_list_modules_to_execute() { 595 | # Print the to-be installed modules 596 | log_info "List modules to execute:" 597 | echo "$final_module_list" 598 | } 599 | 600 | ## Argument handling 601 | 602 | expand_single_args() { 603 | var="${1#-}" # cut off first, and only dash 604 | while [ "$var" ]; do 605 | next="${var#?}" 606 | first_char="${var%"$next"}" 607 | echo "-$first_char" 608 | var="$next" # next 609 | done 610 | } 611 | 612 | # POSIX compliant argument parser. 613 | # This function will parse and return a separated argument list. It handles 614 | # long arguments and checks for missing or extra values. 615 | # it handles both whitespace and '=' separated values 616 | # it also treats quoted parameters as one 617 | # It does NOT check for unknown variables as there is no list of allowed args 618 | # but those are easy to handle later 619 | parse_args() { 620 | # first parameter is a single string, an IFS separated list of arguments 621 | # that should have a single value 622 | with_parameters="$1" 623 | shift 624 | while [ "$1" ]; do 625 | single_cut_with_equalparam="${1##-}" 626 | single_cut="${single_cut_with_equalparam%%=*}" # = value cut pff 627 | double_cut_with_equalparam="${1##--}" 628 | double_cut="${double_cut_with_equalparam%%=*}" # = value cut pff 629 | equalparam=${1##*=} 630 | if [ "$equalparam" = "$1" ]; then 631 | equalparam='' 632 | fi 633 | # starts with one dash but not two 634 | if ! [ "$single_cut_with_equalparam" = "$1" ] && [ "$double_cut_with_equalparam" = "$1" ]; then 635 | split_args=$(expand_single_args "$single_cut") 636 | shift 637 | if [ -n "$equalparam" ]; then 638 | set -- "$equalparam" "$@" 639 | fi 640 | # shellcheck disable=SC2086 641 | set -- $split_args "$@" 642 | # two dash 643 | elif ! [ "$double_cut_with_equalparam" = "$1" ]; then 644 | shift 645 | if [ -n "$equalparam" ]; then 646 | set -- "$equalparam" "$@" 647 | fi 648 | set -- "--$double_cut" "$@" 649 | fi 650 | 651 | has_parameter='' 652 | for a in $with_parameters; do 653 | if [ "$a" = "$1" ]; then 654 | has_parameter='1' 655 | break 656 | fi 657 | done 658 | 659 | if [ -n "$has_parameter" ]; then 660 | if [ -z "$2" ] || ! [ "${2##-}" = "$2" ]; then 661 | echo "$1 is missing its parameter!" >&2 662 | exit 1 663 | fi 664 | fi 665 | 666 | printf "%s\n" "$1" 667 | shift 668 | done 669 | } 670 | 671 | _args_with_params='-l 672 | --log-level 673 | -t 674 | --target 675 | -y 676 | --yank 677 | -Y 678 | --yank-expanded 679 | --rename 680 | ' 681 | # TODO: --rename second argument is not handled 682 | 683 | interpret_args() { 684 | while [ "${1-\0}" != '\0' ]; do 685 | case $1 in 686 | -h | -\? | --help) show_help ;; 687 | -V | --version) show_version ;; 688 | -l | --log | --log-level) 689 | case $2 in 690 | 'trace' | 'TRACE' | '0') PONT_LOG_LEVEL='0' ;; 691 | 'info' | 'INFO' | '1') PONT_LOG_LEVEL='1' ;; 692 | 'warning' | 'WARNING' | '2') PONT_LOG_LEVEL='2' ;; 693 | 'success' | 'SUCCESS') PONT_LOG_LEVEL='2' ;; 694 | 'error' | 'ERROR' | '3') PONT_LOG_LEVEL='3' ;; 695 | 'none' | 'NONE' | '4') PONT_LOG_LEVEL='4' ;; 696 | *) log_error "Invalid loglevel: $2"; exit 1 ;; 697 | esac 698 | shift 699 | ;; 700 | -v | --verbose) PONT_LOG_LEVEL=0 ;; # Log level trace 701 | -q | --quiet) PONT_LOG_LEVEL=3 ;; # Log level error 702 | -I | --list-installed) action_list_installed_modules; exit 0 ;; 703 | -A | --list-modules) action_list_modules; exit 0 ;; 704 | -D | --list-deprecated) action_list_deprecated; exit 0 ;; 705 | -P | --list-presets) action_list_presets; exit 0 ;; 706 | -T | --list-tags) action_list_tags; exit 0 ;; 707 | -E | --list-environment) action_list_environment; exit 0 ;; 708 | -L | --list-install) enqueue "action_list_modules_to_install" ;; 709 | -Q | --list-queue) action_list_execution_queue; exit 0 ;; 710 | -O | --list-outdated) action_list_outdated; exit 0 ;; 711 | -C | --toggle-clean-symlinks) 712 | PONT_CLEAN_SYMLINKS=$((1-PONT_CLEAN_SYMLINKS)) ;; 713 | -X | --toggle-fix-permissions) 714 | PONT_FIX_PERMISSIONS=$((1-PONT_FIX_PERMISSIONS)) ;; 715 | -p | --pull-dotfiles) 716 | enqueue "pull_dotfiles" ;; 717 | -u | --update) enqueue "action_expand_default_if_not_yet" \ 718 | "action_update_modules" ;; 719 | -x | --execute | --install) enqueue \ 720 | "action_expand_default_if_not_yet" "action_execute_modules" ;; 721 | -r | --remove) # behaves differently when called multiple times 722 | remove_count=$((${remove_count:-0} + 1)) 723 | [ ${remove_count:-0} = 1 ] && \ 724 | enqueue "action_expand_default_if_not_yet" \ 725 | "action_remove_modules" ;; 726 | -n | --expand-none) enqueue "action_expand_none" ;; 727 | -e | --expand-selected) enqueue "action_expand_selected" ;; 728 | -a | --expand-all) enqueue "action_expand_all" ;; 729 | -i | --expand-installed) enqueue "action_expand_installed" ;; 730 | -o | --expand-outdated) enqueue "action_expand_outdated" ;; 731 | -d | --dry) PONT_DRY_FLAG=1 ;; 732 | -w | --wet) PONT_DRY_FLAG=0 ;; 733 | -b | --skip-base) PONT_NO_BASE_FLAG=1 ;; 734 | -f | --force) PONT_FORCE_FLAG=1; enqueue "action_expand_none" ;; 735 | -c | --config) PONT_CONFIG_FLAG=1 ;; 736 | --root) PONT_ROOT_FLAG=1 ;; 737 | -R | --skip-root) PONT_ROOT_FLAG=0 ;; 738 | -s | --scripts) PONT_SCRIPTS_ENABLED=1 ;; 739 | -S | --skip-scripts) PONT_SCRIPTS_ENABLED=0 ;; 740 | -m | --make) PONT_MAKE_ENABLED=1 ;; 741 | -M | --skip-make) PONT_MAKE_ENABLED=0 ;; 742 | -t | --target) # package installation target 743 | if [ -d "$2" ]; then 744 | PONT_TARGET="$2" 745 | else 746 | log_error "Invalid target: $2"; exit 1 747 | fi 748 | shift 749 | ;; 750 | --scaffold | --cpt) # Ask for everything 751 | shift 752 | scaffold "$@" 753 | exit 0 754 | ;; 755 | -y | --yank) 756 | enqueue "action_expand_default_if_not_yet" "action_yank" 757 | if [ -d "$2" ]; then 758 | yank_target="$2" 759 | else 760 | log_error "Invalid target: $2"; exit 1 761 | fi 762 | echo "yank_target $yank_target" 763 | shift 764 | ;; 765 | -Y | --yank-expanded) 766 | enqueue "action_expand_selected" "action_yank" 767 | if [ -d "$2" ]; then 768 | yank_target="$2" 769 | else 770 | log_error "Invalid target: $2"; exit 1 771 | fi 772 | shift 773 | ;; 774 | --rename) 775 | shift 776 | rename_module "$2" "$3" 777 | exit 0 778 | ;; 779 | --) ;; 780 | -?*) log_error "Unknown option (ignored): $1";; 781 | *) # The rest are selected modules 782 | # TODO: Pre validate them 783 | if [ "$1" ]; then 784 | if [ "$entries_selected" ]; then 785 | entries_selected="$entries_selected${IFS:-\0}$1" 786 | else 787 | entries_selected="$1" 788 | fi 789 | log_trace "Initially selected: 790 | $entries_selected" 791 | else 792 | break 793 | fi 794 | ;; 795 | esac 796 | shift 797 | done 798 | } 799 | 800 | trim_around() { 801 | # removes the first and last characters from every line 802 | last_removed=${$1::-1} 803 | echo "${last_removed:1}" 804 | } 805 | 806 | has_tag() { 807 | # Returns every dotmodule that contains any of the tags 808 | # shellcheck disable=SC2016 809 | grep -lRxEm 1 -- "$1 ?#?.*" \ 810 | "$PONT_MODULES_HOME"/*/"$PONT_TAGSFILE_NAME" | 811 | sed -r 's_^.*/([^/]*)/[^/]*$_\1_g' 812 | } 813 | 814 | in_preset() { 815 | # returns every entry in a preset 816 | find "$PONT_PRESETS_HOME" -mindepth 1 -name "$1$PONT_PRESET_EXTENSION" \ 817 | -type f -print0 | xargs -0 sed -e 's/ *#.*$//' -e '/^$/d' 818 | } 819 | 820 | get_clashes() { 821 | if [ -f "$PONT_MODULES_HOME/$1/$PONT_CLASHFILE_NAME" ]; then 822 | sed -e 's/ *#.*$//' -e '/^$/d' \ 823 | "$PONT_MODULES_HOME/$1/$PONT_CLASHFILE_NAME" 824 | fi 825 | } 826 | 827 | get_dependencies() { 828 | if [ -f "$PONT_MODULES_HOME/$1/$PONT_DEPENDENCIESFILE_NAME" ]; then 829 | sed -e 's/ *#.*$//' -e '/^$/d' \ 830 | "$PONT_MODULES_HOME/$1/$PONT_DEPENDENCIESFILE_NAME" 831 | fi 832 | } 833 | 834 | get_entry() { 835 | echo "$1" | cut -d '?' -f 1 | cut -d '#' -f 1 | sed 's/ $//' 836 | } 837 | 838 | get_condition() { 839 | echo "$1" | cut -d '?' -s -f 2- | cut -d '#' -f 1 | sed 's/^ //' 840 | } 841 | 842 | pull_dotfiles() { 843 | log_trace "Performing git pull on DOTFILES_HOME ($DOTFILES_HOME)" 844 | if [ -d "$DOTFILES_HOME/.git" ]; then 845 | ( 846 | cd "$DOTFILES_HOME" || exit 1 847 | git pull 848 | git submodule update --init 849 | ) 850 | else 851 | log_error "DOTFILES_HOME ($DOTFILES_HOME) is not a git folder" 852 | fi 853 | } 854 | 855 | execute_scripts_for_module() { 856 | # 1: module name 857 | # 2: scripts to run 858 | # 3: sourcing setting, if set, user privileged scripts will be sourced 859 | cd "$PONT_MODULES_HOME/$1" || exit 1 860 | group_result=0 861 | successful_scripts=0 862 | for script in $2; do 863 | result=0 864 | if [ "${PONT_DRY_FLAG:-0}" = 0 ] && \ 865 | [ "${PONT_SCRIPTS_ENABLED:-1}" = 1 ]; then 866 | log_trace "Running $script..." 867 | 868 | privilege='user' 869 | [ "$(echo "$script" | grep -o '\.' | wc -w)" -gt 1 ] && \ 870 | privilege=$(echo "$script" | cut -d '.' -f 2 | sed 's/-.*//') 871 | 872 | if [ "$privilege" = "root" ] || 873 | [ "$privilege" = "sudo" ]; then 874 | if [ "${PONT_ROOT_FLAG:-1}" = 1 ]; then 875 | ( 876 | sudo --preserve-env="PATH" -E \ 877 | "$PONT_MODULES_HOME/$1/$script" 878 | ) 879 | else 880 | log_info "Skipping $script because root execution" \ 881 | "is disabled" 882 | fi 883 | else 884 | if [ "$SUDO_USER" ]; then 885 | ( 886 | sudo --preserve-env="PATH" -E \ 887 | -u "$SUDO_USER" "$PONT_MODULES_HOME/$1/$script" 888 | ) 889 | else 890 | if [ "$3" ]; then 891 | set -a 892 | # shellcheck disable=SC1090 893 | . "$PONT_MODULES_HOME/$1/$script" 894 | set +a 895 | else 896 | ( 897 | "$PONT_MODULES_HOME/$1/$script" 898 | ) 899 | fi 900 | fi 901 | fi 902 | result=$? 903 | group_result=$((group_result + result)) 904 | if [ $result = 0 ]; then 905 | successful_scripts=$((successful_scripts + 1)) 906 | fi 907 | else 908 | log_trace "Skipping $script..." 909 | fi 910 | done 911 | } 912 | 913 | do_expand_entries() { 914 | while [ "$1" ]; do 915 | # Extracting condition, if there is 916 | condition="$(get_condition "$1")" 917 | # TODO: .condition files and $HEADLESS variable !!! 918 | log_trace "Trying to expand $(get_entry "$1")..." 919 | 920 | [ "$condition" ] && log_trace "...with condition $condition..." 921 | 922 | if ! eval "$condition"; then 923 | log_info "Condition ($condition) for $1 did not met, skipping" 924 | shift 925 | continue 926 | fi 927 | 928 | log_trace "Already resolved entries are: $resolved" 929 | if [ "$(echo "$resolved" | grep -x "$1")" = "" ]; then 930 | if [ -z "$resolved" ]; then 931 | resolved="$1" 932 | else 933 | resolved="$resolved${IFS:-\0}$1" 934 | fi 935 | case "$1" in 936 | +*) # presets 937 | # collect expanded presets in case a yank action needs it 938 | if [ -z "$expanded_presets" ]; then 939 | expanded_presets="$1" 940 | else 941 | expanded_presets="$expanded_presets${IFS:-\0}$1" 942 | fi 943 | # shellcheck disable=SC2046 944 | do_expand_entries \ 945 | $(in_preset "$(get_entry "$1" | cut -c2-)") 946 | ;; 947 | :*) # tags 948 | # shellcheck disable=SC2046 949 | do_expand_entries \ 950 | $(has_tag "$(get_entry "$1" | cut -c2-)") 951 | ;; 952 | *) # modules 953 | # shellcheck disable=SC2046 954 | if [ "${expand_abstract_only:-0}" = 0 ]; then 955 | do_expand_entries \ 956 | $(get_dependencies "$(get_entry "$1")") 957 | fi 958 | get_entry "$1" 959 | ;; 960 | esac 961 | log_trace "...done resolving $1" 962 | else 963 | log_trace "...already resolved $1" 964 | fi 965 | shift 966 | done 967 | } 968 | 969 | expand_entries() { 970 | final_module_list="$(do_expand_entries "$@")" 971 | } 972 | 973 | expand_abstract_entries() { 974 | expand_abstract_only=1 975 | final_module_list="$(do_expand_entries "$@")" 976 | expand_abstract_only= 977 | } 978 | 979 | init_modules() { 980 | log_info "Initializing modules $*" 981 | while [ "$1" ]; do 982 | init_sripts_in_module=$(find "$PONT_MODULES_HOME/$1/" \ 983 | -mindepth 1 -maxdepth 1 -type f | sed 's|.*/||' \ 984 | | grep '^i.*\..*\..*$' | sort) 985 | execute_scripts_for_module "$1" "$init_sripts_in_module" "1" 986 | shift 987 | done 988 | } 989 | 990 | source_modules_envs() { 991 | log_info "Sourcing modules envs $*" 992 | while [ "$1" ]; do 993 | env_sripts_in_module=$(find "$PONT_MODULES_HOME/$1/" \ 994 | -mindepth 1 -maxdepth 1 -type f | sed 's|.*/||' \ 995 | | grep '^e.*\..*\..*$' | sort) 996 | log_trace "Environmental scripts in $1 are $env_sripts_in_module" 997 | execute_scripts_for_module "$1" "$env_sripts_in_module" "1" 998 | shift 999 | done 1000 | } 1001 | 1002 | update_modules() { 1003 | log_info "Updating modules $*" 1004 | while [ "$1" ]; do 1005 | if ! check_module_condition "$1"; then 1006 | shift 1007 | continue 1008 | fi 1009 | 1010 | # Source env 1011 | source_modules_envs "$1" 1012 | 1013 | update_sripts_in_module=$(find "$PONT_MODULES_HOME/$1/" \ 1014 | -mindepth 1 -maxdepth 1 -type f | sed 's|.*/||' \ 1015 | | grep '^u.*\..*\..*$' | sort) 1016 | execute_scripts_for_module "$1" "$update_sripts_in_module" 1017 | shift 1018 | done 1019 | } 1020 | 1021 | remove_modules() { 1022 | log_trace "Removing modules $*" 1023 | while [ "$1" ]; do 1024 | if ! check_module_condition "$1"; then 1025 | shift 1026 | continue 1027 | fi 1028 | 1029 | # Source env 1030 | source_modules_envs "$1" 1031 | 1032 | # Only run the remove scripts with -rr, a single r just unstows 1033 | if [ "$remove_count" -ge 2 ]; then 1034 | log_info "Hard remove $1" 1035 | remove_sripts_in_module=$(find "$PONT_MODULES_HOME/$1/" \ 1036 | -mindepth 1 -maxdepth 1 -type f | sed 's|.*/||' | 1037 | grep -v '^restore.*$' | grep '^r.*\..*\..*$' | sort) 1038 | execute_scripts_for_module "$1" "$remove_sripts_in_module" 1039 | else 1040 | log_info "Soft remove $1" 1041 | fi 1042 | 1043 | # Stowing is hard disabled on windows 1044 | if [ -z "$windows" ]; then 1045 | unstow_modules "$1" 1046 | fi 1047 | 1048 | # remove hashfile to mark as uninstalled 1049 | [ -e "$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" ] && 1050 | rm "$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" 1051 | 1052 | shift 1053 | done 1054 | } 1055 | 1056 | do_stow() { 1057 | # $1: the packages parent directory 1058 | # $2: target directory 1059 | # $3: package name 1060 | # $4: stowmode "stow" | "unstow" 1061 | 1062 | log_trace "Stowing package $3 to $2 from $1" 1063 | 1064 | if ! is_installed stow; then 1065 | log_error "stow is not installed!" 1066 | exit 1 1067 | fi 1068 | if [ ! -d "$1" ]; then 1069 | log_error "package not found! 1070 | $1 1071 | $2 1072 | $3" 1073 | exit 1 1074 | fi 1075 | if [ "$2" ] && [ ! -d "$2" ]; then 1076 | log_warning "target directory does not exist, creating! 1077 | $1 1078 | $2 1079 | $3" 1080 | mkdir -p "$2" 1081 | fi 1082 | if [ ! "$3" ]; then 1083 | log_error "no package name! 1084 | $1 1085 | $2 1086 | $3" 1087 | exit 1 1088 | fi 1089 | 1090 | if [ "${PONT_DRY_FLAG:-0}" != 1 ]; then 1091 | # Module target symlinks are always cleaned 1092 | clean_symlinks "$2" 1093 | if [ "$SUDO_USER" ]; then 1094 | sudo --preserve-env="PATH" -E -u "$SUDO_USER" \ 1095 | stow -D -d "$1" -t "$2" "$3" 1096 | [ "$stow_mode" = "stow" ] && \ 1097 | sudo --preserve-env="PATH" -E -u "$SUDO_USER" \ 1098 | stow -S -d "$1" -t "$2" "$3" 1099 | else 1100 | # https://github.com/aspiers/stow/issues/69 1101 | stow -D -d "$1" -t "$2" "$3" 1102 | [ "$stow_mode" = "stow" ] && \ 1103 | stow -S -d "$1" -t "$2" "$3" 1104 | fi 1105 | log_trace "Stowed $1" 1106 | fi 1107 | } 1108 | 1109 | stow_package() { 1110 | # recieves a stowmode "stow" | "unstow" 1111 | # then a list of directories of packages inside modules 1112 | stow_mode="$1" 1113 | log_trace "Stowing packages $*" 1114 | shift 1115 | while [ "$1" ]; do 1116 | 1117 | if [ "$(basename "$1" | cut -d '.' -f 3)" ]; then 1118 | stow_condition="$(basename "$1" | cut -d '.' -f 2)" 1119 | if [ "${stow_condition#\$}" = "$stow_condition" ]; then 1120 | log_trace "Stow condition $stow_condition is a command" 1121 | is_installed "$stow_condition" || { shift; continue; } 1122 | else 1123 | log_trace "Stow condition $stow_condition is a variable" 1124 | [ "$(eval "echo $stow_condition")" ] || { shift; continue; } 1125 | fi 1126 | fi 1127 | 1128 | log_trace "Do stowing $1" 1129 | do_stow "$(echo "${1%/*}" | sed 's|^$|/|')" \ 1130 | "$(/bin/sh -c "echo \$$(basename "$1" | cut -d '.' -f 1)" | \ 1131 | sed -e "s|^\$$|$PONT_TARGET|" \ 1132 | -e "s|^[^/]|$PONT_TARGET/\0|")" \ 1133 | "$(basename "$1")" \ 1134 | "$stow_mode" 1135 | shift 1136 | done 1137 | 1138 | } 1139 | 1140 | stow_modules() { 1141 | log_trace "Stow modules $*" 1142 | while [ "$1" ]; do 1143 | # shellcheck disable=SC2046 1144 | # TODO: Mixed splitting, find outputs new line splits 1145 | stow_package "stow" $(find "$PONT_MODULES_HOME/$1" \ 1146 | -mindepth 1 -maxdepth 1 -type d -iname "*.$1") 1147 | shift 1148 | done 1149 | } 1150 | 1151 | unstow_modules() { 1152 | log_trace "Unstow modules $*" 1153 | while [ "$1" ]; do 1154 | # shellcheck disable=SC2046 1155 | stow_package "unstow" $(find "$PONT_MODULES_HOME/$1" \ 1156 | -mindepth 1 -maxdepth 1 -type d -iname "*.$1") 1157 | shift 1158 | done 1159 | } 1160 | 1161 | make_module() { 1162 | if [ "${PONT_MAKE_ENABLED:-1}" = 1 ] \ 1163 | && [ -e "$PONT_MODULES_HOME/$1/Makefile" ]; then 1164 | if ! is_installed "make"; then 1165 | log_error "Make not available"; return 1 1166 | fi 1167 | # It's already cd'd in. 1168 | # Makefiles are always executed using user rights 1169 | if [ "$SUDO_USER" ]; then 1170 | sudo --preserve-env="PATH" -E -u "$SUDO_USER" make 1171 | else 1172 | make 1173 | fi 1174 | fi 1175 | } 1176 | 1177 | powershell_script_filter() { 1178 | if [ "$windows" ]; then 1179 | grep "^.*ps1$" 1180 | else 1181 | grep -v "^.*ps1$" 1182 | fi 1183 | } 1184 | 1185 | get_install_scripts_in_module() { 1186 | find "$PONT_MODULES_HOME/$1/" -mindepth 1 -maxdepth 1 \ 1187 | -type f 2>/dev/null | sed 's|.*/||' | grep "^[0-9].*\..*\..*$" | 1188 | sort | powershell_script_filter 1189 | } 1190 | 1191 | install_module() { 1192 | sripts_in_module=$(get_install_scripts_in_module "$1") 1193 | log_trace "Scripts in module for $1 are: 1194 | $sripts_in_module" 1195 | groups_in_module=$(echo "$sripts_in_module" | sed 's/\..*//g' | uniq) 1196 | for group in $groups_in_module; do 1197 | group_scripts=$(echo "$sripts_in_module" | grep "^${group}..*$" ) 1198 | group_scripts_to_run= 1199 | for script in $group_scripts; do 1200 | # at least 4 section long, so there is a condition 1201 | if [ "$(echo "$script" | cut -d '.' -f 4)" ]; then 1202 | script_condition="$(echo "$script" | cut -d '.' -f 3)" 1203 | if [ "$script_condition" = "fallback" ]; then 1204 | log_trace "fallback script" 1205 | group_scripts_to_run="$group_scripts_to_run\ 1206 | ${IFS:-\0}$script" 1207 | elif [ "${script_condition#\$}" = "$script_condition" ]; then 1208 | log_trace "condition $script_condition is a command" 1209 | is_installed "$script_condition" && \ 1210 | group_scripts_to_run="$group_scripts_to_run\ 1211 | ${IFS:-\0}$script" 1212 | else 1213 | log_trace "condition $script_condition is a variable" 1214 | [ "$(eval "echo $script_condition")" ] && \ 1215 | group_scripts_to_run="$group_scripts_to_run\ 1216 | ${IFS:-\0}$script" 1217 | fi 1218 | else 1219 | # else it has no condition 1220 | group_scripts_to_run="$group_scripts_to_run${IFS:-\0}$script" 1221 | fi 1222 | done 1223 | group_scripts_without_fallback=$(echo "$group_scripts_to_run" | 1224 | grep -v 'fallback') 1225 | 1226 | log_trace "scripts to execute after conditions 1227 | $group_scripts_without_fallback" 1228 | 1229 | execute_scripts_for_module "$1" "$group_scripts_without_fallback" 1230 | 1231 | group_fallback_scripts=$(echo "$group_scripts_to_run" | 1232 | grep 'fallback') 1233 | if [ $successful_scripts = 0 ] && [ "$group_fallback_scripts" ]; then 1234 | log_info "Installing group $group for $1 was not successful, \ 1235 | trying fallbacks: 1236 | $group_fallback_scripts" 1237 | 1238 | execute_scripts_for_module "$1" "$group_fallback_scripts" 1239 | fi 1240 | 1241 | total_result=$((total_result + group_result)) 1242 | done 1243 | } 1244 | 1245 | do_hash() { 1246 | tar --exclude="$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" \ 1247 | -c "$PONT_MODULES_HOME/$1" 2> /dev/null | "$HASH_COMMAND" 1248 | } 1249 | 1250 | do_hash_module() { 1251 | do_hash "$1" >"$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" 1252 | } 1253 | 1254 | hash_module() { 1255 | if [ "${PONT_DRY_FLAG:-0}" = 0 ]; then 1256 | log_success "Successfully installed $1" 1257 | 1258 | if [ "$SUDO_USER" ]; then 1259 | sudo --preserve-env="PATH" -E -u "$SUDO_USER" do_hash_module "$1" 1260 | else 1261 | do_hash_module "$1" 1262 | fi 1263 | fi 1264 | } 1265 | 1266 | check_module_condition() { 1267 | if [ -e "$PONT_MODULES_HOME/$1/$PONT_CONDITIONFILE_NAME" ]; then 1268 | contition="$(cat "$PONT_MODULES_HOME/$1/$PONT_CONDITIONFILE_NAME" \ 1269 | | sed -e 's/ *#.*$//' -e '/^$/d')" 1270 | if ! eval "$contition"; then 1271 | log_info "Condition on $1 failed. Skipping. $contition" 1272 | return 1 1273 | fi 1274 | fi 1275 | return 0 1276 | } 1277 | 1278 | execute_modules() { 1279 | while [ "$1" ]; do 1280 | total_result=0 1281 | log_trace "Checking if module exists: $PONT_MODULES_HOME/$1" 1282 | if [ ! -d "$PONT_MODULES_HOME/$1" ]; then 1283 | log_error "Module $1 not found. Skipping" 1284 | shift 1285 | continue 1286 | fi 1287 | 1288 | if ! check_module_condition "$1"; then 1289 | shift 1290 | continue 1291 | fi 1292 | 1293 | log_info "Installing $1" 1294 | 1295 | # Only calculate the hashes if we going to use it 1296 | if [ "${PONT_FORCE_FLAG:-0}" = 0 ]; then 1297 | old_hash=$(cat "$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" \ 1298 | 2>/dev/null) 1299 | new_hash=$(do_hash "$1") 1300 | 1301 | if [ "$old_hash" = "$new_hash" ]; then 1302 | log_trace "${C_GREEN}hash match $old_hash $new_hash" 1303 | else 1304 | log_trace "${C_RED}hash mismatch $old_hash $new_hash" 1305 | fi 1306 | fi 1307 | 1308 | # Source env, regardless, so the environment of the dependencies 1309 | # are available 1310 | source_modules_envs "$1" 1311 | 1312 | if [ "${PONT_FORCE_FLAG:-0}" = 1 ] \ 1313 | || [ "$old_hash" != "$new_hash" ]; then 1314 | 1315 | if [ "${PONT_FORCE_FLAG:-0}" != 1 ] && is_deprecated "$1"; 1316 | then 1317 | log_warning "$1 is deprecated" 1318 | shift 1319 | continue 1320 | fi 1321 | 1322 | if [ "${PONT_DRY_FLAG:-0}" = 1 ]; then 1323 | log_trace "Dotmodule $1 would be installed" 1324 | else 1325 | log_trace "Applying dotmodule $1" 1326 | fi 1327 | 1328 | init_modules "$1" 1329 | 1330 | # Stowing is hard disabled on windows 1331 | if [ -z "$windows" ]; then 1332 | stow_modules "$1" 1333 | fi 1334 | 1335 | # Make isn't a separate step because there only 1336 | # should be one single install step so that the hashes 1337 | # and the result can be determined in a single step 1338 | # It's mutually exclusive with normal scripts, will only run 1339 | # When no numbered scripts exist 1340 | if [ -z "$(get_install_scripts_in_module "$1")" ];then 1341 | make_module "$1" 1342 | fi 1343 | 1344 | install_module "$1" 1345 | 1346 | if [ "$total_result" = 0 ]; then 1347 | # Calculate fresh hash on success 1348 | hash_module "$1" 1349 | else 1350 | log_error "Installation failed $1" 1351 | [ -e "$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" ] && 1352 | rm "$PONT_MODULES_HOME/$1/$PONT_HASHFILE_NAME" 1353 | fi 1354 | 1355 | else 1356 | log_info "$1 is already installed and no changes are detected" 1357 | fi 1358 | shift 1359 | done 1360 | } 1361 | 1362 | ## Actions 1363 | 1364 | action_quit() { 1365 | exit "${1:-0}" 1366 | } 1367 | 1368 | action_fix_permissions() { 1369 | # Fix permissions, except in submodules 1370 | log_info "Fixing permissions in $DOTFILES_HOME... " 1371 | ( 1372 | cd "$DOTFILES_HOME" || exit 1373 | subs=$(git submodule status | sed -e 's/^ *//' -e 's/ *$//') 1374 | submodules=$( 1375 | echo "${subs% *}" | cut -d ' ' -f 2- | 1376 | sed -e 's@^@-not -path "**/@' -e 's@$@/*"@' | tr '\n' ' ' 1377 | ) 1378 | eval "find $PONT_MODULES_HOME -type f \( $submodules \) \ 1379 | -regex '.*\.\(sh\|zsh\|bash\|fish\|dash\)' -exec chmod u+x {} \;" 1380 | ) 1381 | } 1382 | 1383 | action_clean_symlinks() { 1384 | # Remove incorrect symlinks in PONT_TARGET 1385 | clean_symlinks "$PONT_TARGET" 1386 | } 1387 | 1388 | action_expand_selected() { 1389 | log_info "Set final module list to every selected and expanded module" 1390 | final_module_list= 1391 | 1392 | if [ "$PONT_NO_BASE_FLAG" != 1 ]; then 1393 | old_ifs=$IFS 1394 | IFS=' ' 1395 | for base_module in $PONT_BASE_MODULES; do 1396 | IFS=$old_ifs 1397 | entries_selected="${base_module}${IFS:-\0}${entries_selected}" 1398 | done 1399 | IFS=$old_ifs 1400 | fi 1401 | # shellcheck disable=SC2086 1402 | expand_entries $entries_selected 1403 | log_info "Final module list is: 1404 | $final_module_list" 1405 | } 1406 | 1407 | action_expand_all() { 1408 | log_info "Set final module list to every module, expanding them." 1409 | final_module_list= 1410 | [ ! "$all_modules" ] && get_all_modules 1411 | # shellcheck disable=SC2086 1412 | expand_entries $all_modules 1413 | log_info "Final module list is: 1414 | $final_module_list" 1415 | } 1416 | 1417 | action_expand_installed() { 1418 | log_info "Set final module list to every installed and expanded module." 1419 | final_module_list= 1420 | [ ! "$all_installed_modules" ] && get_all_installed_modules 1421 | # shellcheck disable=SC2086 1422 | expand_entries $all_installed_modules 1423 | log_info "Final module list is: 1424 | $final_module_list" 1425 | } 1426 | 1427 | action_expand_outdated() { 1428 | log_info "Set final module list to every installed, outdated module," \ 1429 | "expanding them." 1430 | final_module_list= 1431 | [ ! "$all_outdated_modules" ] && get_all_outdated_modules 1432 | # shellcheck disable=SC2086 1433 | expand_entries $all_outdated_modules 1434 | log_info "Final module list is: 1435 | $final_module_list" 1436 | } 1437 | 1438 | action_expand_default_if_not_yet() { 1439 | # If no expansion happened at this point, execute the default one 1440 | [ ! "$final_module_list" ] && "$PONT_DEFAULT_EXPANSION_ACTION" 1441 | } 1442 | 1443 | action_expand_none() { 1444 | log_info "Set final module list only to the selected modules," \ 1445 | "no dependency expansion." 1446 | final_module_list= 1447 | # shellcheck disable=SC2086 1448 | expand_abstract_entries $entries_selected 1449 | log_info "Final module list is: 1450 | $final_module_list" 1451 | } 1452 | 1453 | action_list_modules_to_install() { 1454 | log_info "List modules to install:" 1455 | echo "$final_module_list" 1456 | } 1457 | 1458 | action_remove_modules() { 1459 | # shellcheck disable=SC2086 1460 | remove_modules $final_module_list 1461 | } 1462 | 1463 | action_execute_modules() { 1464 | # shellcheck disable=SC2086 1465 | execute_modules $final_module_list 1466 | } 1467 | 1468 | action_update_modules() { 1469 | # shellcheck disable=SC2086 1470 | update_modules $final_module_list 1471 | } 1472 | 1473 | do_yank() { 1474 | mkdir -p "$yank_target" 1475 | # Copy all modules 1476 | while [ "$1" ]; do 1477 | log_info "Yanking $PONT_MODULES_HOME/$1 to $yank_target/$1" 1478 | cp -r "$PONT_MODULES_HOME/$1" "$yank_target/$1" 1479 | shift 1480 | done 1481 | # Copy all used presets 1482 | for preset in $expanded_presets; do 1483 | preset_file_name="$(echo "$preset" | cut -d '+' -f 2-).preset" 1484 | cp "$(find "$PONT_PRESETS_HOME" -type f -name "$preset_file_name")" \ 1485 | "$yank_target/$preset_file_name" 1486 | done 1487 | } 1488 | 1489 | action_yank() { 1490 | # shellcheck disable=SC2086 1491 | do_yank $final_module_list 1492 | } 1493 | 1494 | ask_entries() { 1495 | ! is_installed whiptail && log_error "No whiptail installed" \ 1496 | && exit 1 1497 | 1498 | [ ! "$all_modules" ] && get_all_modules 1499 | [ ! "$all_tags" ] && get_all_tags 1500 | [ ! "$all_presets" ] && get_all_presets 1501 | 1502 | preset_options="$(echo "$all_presets" | \ 1503 | awk '{printf ":%s :%s OFF ", $1, $1}')" 1504 | tag_options="$(echo "$all_tags" | \ 1505 | awk '{printf ":%s :%s OFF ", $1, $1}')" 1506 | module_options="$(echo "$all_modules" | \ 1507 | awk '{printf "%s %s OFF ", $1, $1}')" 1508 | 1509 | log_trace "Manual module input" 1510 | 1511 | entries_selected=$(eval "whiptail --separate-output --clear --notags \ 1512 | --title 'Select modules to install' \ 1513 | --checklist 'Space changes selection, enter approves' \ 1514 | 0 0 0 $preset_options $tag_options $module_options \ 1515 | 3>&1 1>&2 2>&3 3>&- | sed 's/ /\n/g'") 1516 | } 1517 | 1518 | execute_queue() { 1519 | log_trace "executing queue: $*" 1520 | for action in "$@"; do 1521 | log_info "Executing: $action" 1522 | $action 1523 | done 1524 | } 1525 | 1526 | ## Execution 1527 | 1528 | IFS=' 1529 | ' 1530 | # shellcheck disable=SC2046 1531 | interpret_args $(parse_args "$_args_with_params" "$@") 1532 | 1533 | # if nothing is selected, ask for modules 1534 | if [ "${PONT_CONFIG_FLAG:-0}" = 1 ] || [ $# -eq 0 ]; then 1535 | ask_entries # config checked again to avoid double call on ask_entries 1536 | fi 1537 | 1538 | # if nothing is in the execution queue, assume expand and execute 1539 | [ ! "$execution_queue" ] \ 1540 | && enqueue "action_expand_selected" "action_execute_modules" 1541 | 1542 | log_trace "Execution queue: 1543 | $execution_queue" 1544 | 1545 | [ "$PONT_FIX_PERMISSIONS" = 1 ] && action_fix_permissions 1546 | # shellcheck disable=SC2086 1547 | execute_queue $execution_queue 1548 | 1549 | [ "$PONT_CLEAN_SYMLINKS" = 1 ] && action_clean_symlinks 1550 | 1551 | set +a 1552 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [pont](./pont.sh) 2 | 3 | [![Test](https://github.com/AlexAegis/pont/workflows/Test/badge.svg)](https://github.com/AlexAegis/pont/actions?query=workflow%3ATest) [![Lint](https://github.com/AlexAegis/pont/workflows/Lint/badge.svg)](https://github.com/AlexAegis/pont/actions?query=workflow%3ALint) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/64033c40165747fa8abe7d5a6d706a65)](https://www.codacy.com/manual/AlexAegis/pont?utm_source=github.com&utm_medium=referral&utm_content=AlexAegis/pont&utm_campaign=Badge_Grade) [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/64033c40165747fa8abe7d5a6d706a65)](https://www.codacy.com/manual/AlexAegis/pont?utm_source=github.com&utm_medium=referral&utm_content=AlexAegis/pont&utm_campaign=Badge_Coverage) [![codecov](https://codecov.io/gh/AlexAegis/pont/branch/master/graph/badge.svg)](https://codecov.io/gh/AlexAegis/pont) 4 | 5 | > Check my [dotfiles](https://github.com/alexaegis/dotfiles) as a live example 6 | 7 | ![logo](./docs/images/logo.svg) 8 | 9 | ## What is `pont` 10 | 11 | It's a single shell script designed to install programs and personal configs 12 | in bulk with minimal config. Depending on your setup, it's capable of 13 | installing every program you use with their configurations from a fresh 14 | install. 15 | 16 | The programs and configurations are stored in `dotmodules` which is just a 17 | directory with scripts and folders named in a conventional manner. The names 18 | of these files are the configuration itself. If I'd use a configuration file 19 | it would require mentioning the directories you want to interact with. I've 20 | cut out the config and store every metadata in filenames. More on this in the 21 | [dotmodules](#dotmodules) section. 22 | 23 | For example, using this script and an extensive set of dotmodules, on a fresh 24 | arch install after downloading my dotfiles repository I can just issue this 25 | command: 26 | 27 | ```sh 28 | pont +arch 29 | ``` 30 | 31 | And then it will install every single thing I specified there without 32 | assistance. In order. 33 | 34 | _But that could be done by having a single set of applications in an install 35 | script, and all my config in a repository, right?_ 36 | 37 | Yes, but what if you use multiple setups at once? Or you'd like to configure 38 | just only a few things on a remote server? Why would you add your X config to 39 | WSL? What if you distro hop a lot because you're experimenting? 40 | 41 | But even if you use it for a single system it does have it's advantages. 42 | Separating everything into it's little bundles helps keeping track of things. 43 | If you wan't to tweak some configurations you only have to go to one place, and 44 | **you get to see all related files in one module**. You can see what other 45 | modules you made this one depend on. 46 | 47 | Configuration is just one part. But oftentime things require a little setup. 48 | Downloading a repository, compilation, installation. Installing and enabling 49 | services. These things can also be in an install script. You don't have to 50 | remember things. With these scripts you can always replay the setup you did. 51 | 52 | A modular design allows you to focus on one thing at once, and organize your 53 | configuration. 54 | 55 | > It's my first shell script project so please leave issues, if you have any 56 | 57 | ![xkcd - Automation](https://imgs.xkcd.com/comics/automation.png) 58 | 59 | ## Why the name 60 | 61 | This project was originally named `dot` but there is a `graphviz` utility 62 | by that name already. Another candidate was 63 | `dotman` but there are dozens of `dotman` repositories already. 64 | 65 | But `pont` was free and it means `dot` in hungarian, and bridge in french. 66 | Which I guess makes sense given the heavy use of symlinks. 67 | 68 | ## Prerequisites 69 | 70 | A mostly [POSIX](https://en.wikipedia.org/wiki/POSIX) compatible shell on 71 | `/bin/sh` to run `pont` itself. `bash` and `zsh` will work fine. It was 72 | developed on `dash` and it does not utilize local variables. 73 | 74 | A key component of `pont` is `stow` which handles the symlinks between the 75 | modules and the target, but it's not needed to run `pont` itself and execute 76 | modules that do not require linking. Meaning on a fresh install `pont` can set 77 | this dependency up for you if you have a module that deals with this and do 78 | not utilize linking. 79 | 80 | ### On Windows 81 | 82 | `pont` works but has somewhat limited capabilities when running on a Windows 83 | POSIX compatibility layer such as `MINGW`. But is usable as a conditional, 84 | dependency tree resolving script runner. 85 | 86 | > Windows Support is limited as I only use basic functinalities and is not 87 | > tested beyond that. 88 | 89 | Caveats on Windows: 90 | 91 | - Stow is not available (It would be disabled even if it were as it would 92 | just cause problems) 93 | - Limited to running PowerShell scripts with `*.ps1` file extension. 94 | - Since the PowerShell scripts are not running directly from PowerShell 95 | a shebang is needed in every script: `#!/usr/bin/env powershell` 96 | - Has to be invoked from an elevated environment (For example `Git Bash` when 97 | running with `Run As Administrator`) manually if the scrips you try to run 98 | require this. 99 | - Scripts must not have `.root.` in it's 100 | [second segment](#Second-segment,-privileges) because `sudo` is not 101 | available in these environments and `pont` would try to invoke it when a 102 | scripts demands privilege elevation. 103 | 104 | > This enables modules to be installed on both kind of systems without 105 | > conflict or excessive configuration. Most of the time they would be mutually 106 | > exclusive so you would either need a lot of 107 | > [`conditionfiles`](#Conditional-modules) (To limit modules into certain OS') 108 | > or condition everything in it to windows or not windows. Disabling automatic 109 | > symlinking on Windows altogether is the easier choice. If needed you can 110 | > always write the PowerShell script to do the symlinking. Same thing with 111 | > PowerShell scripts on linux, since only the file extension is checked, you 112 | > can just give it something else and have the same shebang. 113 | 114 | ## Installation 115 | 116 | This repository can be used to download and install `pont`. It can also act 117 | as a dotmodule itself. You can inclide this repository as a submodule to your 118 | dotfiles repo. 119 | 120 | TODO: Make online installation script, let it download itself. 121 | 122 | ### Using `pont` itself 123 | 124 | If you have this repository as a submodule among your modules, you can install 125 | it with itself. It's linking it to `~/.local/bin/pont` and if it has `root` 126 | privileges then `/usr/local/bin/pont` too, with the manpage. 127 | 128 | ```sh 129 | ~/.dotfiles/modules/pont/pont.sh pont 130 | ``` 131 | 132 | ### As a zsh plugin 133 | 134 | This repository also contains `zsh` autocompletions 135 | 136 | If you're using [Antidote](https://getantidote.github.io/) then add 137 | this entry to your plugin file to get `pont` autocompletions: 138 | 139 | ```sh 140 | alexaegis/pont 141 | ``` 142 | 143 | > The zsh plugin does not put `pont` to the path as using both solution would 144 | > cause confusion 145 | 146 | Autocompletions will list all presets, tags and modules when triggered. When 147 | typing a `-`, triggering the autocompletion will list all flags. 148 | 149 | ## Usage 150 | 151 | Whether you chose to put it on your path or not, using it is the same. 152 | 153 | If you decide to use `pont` in to manage your dotfiles, please add `pont` as 154 | a tag! 155 | 156 | ### Configuration 157 | 158 | It does not require any configuration, but most things can be configured 159 | through a `pontrc` file which is read from every common configuration directory 160 | in this order: 161 | 162 | ```sh 163 | ${XDG_CONFIG_HOME:-"$HOME/.config"}/pont/pontrc 164 | ${XDG_CONFIG_HOME:-"$HOME/.config"}/pontrc 165 | $HOME/.pontrc 166 | ./.pontrc 167 | ``` 168 | 169 | The configuration file is a standard shell script and will be simply sourced 170 | in `pont` after the defaults have been set but before the flags have been set 171 | from the command line. Meaning you can override the defaults. 172 | To see what you can set, check the script, search for the sourcing of the 173 | config file. Everything above that is overridable. 174 | 175 | #### Examples 176 | 177 | These ones can be only configured through the files, or by hand, when 178 | executing `pont` like `PONT_TARGET="./target" pont ...` 179 | 180 | ```sh 181 | # The default target of module packages 182 | PONT_TARGET=${PONT_TARGET:-"$HOME"} 183 | # Your dotfiles location. Not directly utilized, only through the next two 184 | DOTFILES_HOME=${DOTFILES_HOME:-"$HOME/.dotfiles"} 185 | # I suggest keeping these relative to DOTFILES_HOME 186 | # Dotmodules will be searched here. Only direct descendant folders. 187 | DOT_MODULES_FOLDER=${DOT_MODULES_FOLDER:-"$DOTFILES_HOME/modules"} 188 | # Presets will be searched from here, recursively 189 | DOT_PRESETS_FOLDER=${DOT_PRESETS_FOLDER:-"$DOTFILES_HOME/presets"} 190 | # Modules to always include regardless of selection 191 | DOT_BASE_MODULES="base sys" 192 | ``` 193 | 194 | ```sh 195 | # Will always remove broken symlinks after execution in PONT_TARGET 196 | PONT_CLEAN_SYMLINKS=1 197 | # Makes it always execute `chmod u+x` on '.*.(sh|zsh|bash|fish|dash)' files in 198 | # the modules. I use it so when making scripts I don't have to deal with it. 199 | PONT_FIX_PERMISSIONS=1 200 | ``` 201 | 202 | ### Installing dotmodules 203 | 204 | You can optionally set flags, then as much modules/presets/tags as you want. 205 | 206 | ```sh 207 | pont [-flags] modules... 208 | ``` 209 | 210 | ## Dotmodules 211 | 212 | > For reference you can also check the [template](./template) directory, 213 | > which containes examples and short description on every available file 214 | > or some of my modules. 215 | 216 | When installing a dotmodule successfully, a `.tarhash` file will be created 217 | in the modules directory. (Should be .gitignored). This containes a hash 218 | calculated from the content of the module at the time of install. This enables 219 | `pont` to skip any module that is **already installed** and 220 | **has not changed since**. This is especially important when installing 221 | modules with large dependency trees. 222 | 223 | > The `.tarhash` file marks a module installed. 224 | 225 | Modules that are already installed can be forced to be reinstalled using the 226 | `-f` or `--force` flags. It just makes it ignore the hashfile. 227 | 228 | ### Listing installed modules 229 | 230 | Installed modules can be listed and `sort`ed using the `-I` or 231 | `--list-installed` flags. These flags makes `pont` exit immediately. 232 | 233 | ```sh 234 | pont -I 235 | ``` 236 | 237 | Tip: use `wc -l` to count the result of these queries 238 | 239 | ```sh 240 | pont -I | wc -l 241 | ``` 242 | 243 | ### Listing available modules 244 | 245 | Available modules can be listed and `sort`ed using the `-A` or 246 | `--list-modules` flags. These flags make `pont` exit immediately. 247 | 248 | ```sh 249 | pont -A 250 | ``` 251 | 252 | ### Listing deprecated modules 253 | 254 | Deprecated modules (Explicitly marked as deprecated) can be listed and 255 | `sort`ed using the `-D` or `--list-deprecated` flags. 256 | These flags make `pont` exit immediately. 257 | 258 | ```sh 259 | pont -D 260 | ``` 261 | 262 | ### Listing outdated modules 263 | 264 | Outdated modules (Modules with changed hash since their last installation) 265 | can be listed and `sort`ed using the `-O` or `--list-outdated` flags. 266 | These flags make `pont` exit immediately. 267 | 268 | > Since this command re-hashes all install modules it can take a while 269 | 270 | ```sh 271 | pont -O 272 | ``` 273 | 274 | ## Content of a module 275 | 276 | Every module can have 3 kinds of files inside, each of them being optional. 277 | An empty module is a valid module too. 278 | 279 | ## Installation scripts 280 | 281 | > Using [script selection flags](#Flagged-scripts), install scripts are 282 | > getting automatically excluded from execution but re-adding the `-x` flag 283 | > after these, these scripts can be re-enabled. 284 | 285 | These scripts are what supposed to install packages for your module to work. 286 | And they will run by default when installing a module. 287 | 288 | > They are not the only way to describe the installation process of a module. 289 | > You can use a `Makefile` with an `install` target. Both will execute, so 290 | > either only create one type of installation or set a flag to disable one 291 | > of them. See the [Makefiles](#Makefiles) section for more. 292 | 293 | Since package management is unique to each system, and not just by having 294 | different commands to install a package. Often their name is different. Or 295 | on a particular distribution you need to install more, or different packages, 296 | or having to add `PPA`s on debian systems etc etc. 297 | 298 | So instead of making an overcomplicated solution I let the modules decide 299 | how to install something. This gives great control since you're the one making 300 | the script, `pont` just executes them. 301 | 302 | > I may make an easy install for simple cases but this solution will stay 303 | > as it can be used for anything, not just installation 304 | 305 | These scripts can also be used to do some other things, like copying a 306 | configuration file into `/etc` 307 | 308 | ### The scripts names decide how and when they will run 309 | 310 | Their names can be separated into up to 4 segments, separated by 3 periods. 311 | Everything after the 3rd period is unused. At least 3 segments (two periods in 312 | the filename) is needed for `pont` to recognize it as a runnable script. So 313 | you can add other, non-managed scripts in the module folder when needed. But 314 | you can also just put them into a separate folder. 315 | 316 | > \* If you skip the dependency segment then the extension will be read as 317 | > the dependency, but since it's `sh` anyway it will always be true. 318 | 319 | ### First segment, order and grouping 320 | 321 | > **1**.user.sh 322 | 323 | The first segment is used for ordering. If multiple scripts are in one 324 | order, they are considered a **script group**. 325 | 326 | The only mechanic related to this are `fallback` scripts, more on that in the 327 | [third segment](#Fallback) 328 | 329 | ### Second segment, privileges 330 | 331 | > 0.**root**.sh 332 | 333 | is the privilege and it can be two things: 334 | 335 | - user 336 | - root 337 | 338 | No matter whether you run `pont` with sudo or not, if it has to run a `user` 339 | script it will always `sudo` back to `$SUDO_USER` (If it has to) and it will 340 | always `sudo` into `root` (While keeping your environment) if it's running 341 | `root` scripts. 342 | 343 | **This makes sure that your `HOME` folder will only contain items owned by you, 344 | and not `root`.** 345 | 346 | > `sudo` will be executed as many time it needs to as I described it above 347 | > most distributions have a timeout enabled by default but if not, prepare 348 | > to write your password in a few times. 349 | 350 | In every script, you can also be sure that `.` means the root of that module 351 | as `pont` will `cd` into the module before executing anything. 352 | 353 | Using the `-nr` or `--no-root` flags, scripts with `sudo` privileges can be 354 | skipped. 355 | 356 | > If you are on a system where you don't have `root` access, but the programs 357 | > are installed and you only need your configurations, you can set this flag 358 | > permamently in a `pontrc`, and only use the `stow`ing mechanism of `pont`. 359 | > The variable controlling this is `root` and is `1` by default. 360 | 361 | ### Third segment, condition 362 | 363 | > 0.root.**pacman**.sh 364 | 365 | While modules can have module dependencies, scripts can also have conditions 366 | which can be executable dependencies and variable dependencies, if prefixed 367 | with `$`. If it is, and that variable is set and not empty, it will be 368 | executed. If it's not prefixed with `$`, it will be checked that 369 | `command -v` returns something for it or not. If yes, it will be executed. 370 | 371 | > Currently you can only specify 1 dependency on 1 script. 372 | 373 | My common usecase for this is checking package managers. So I can have 374 | a separate install script for `pacman` systems, `apt` systems and `xbps` 375 | systems. And each will only execute on their respective platforms. 376 | 377 | For variables, it's for things that cant be checked by a simple presence of 378 | an executable. Like if I want to run a script only on `wsl`, or `arch` I can 379 | name my script like `1.user.$wsl.sh`. 380 | 381 | #### Fallback 382 | 383 | > 0.user.**fallback**.sh 384 | 385 | There is an extra special dependency setting called `fallback`, and this is 386 | where `script groups` come into play. If in a script group (Defined by a 387 | common ordering segment) none got executed, `fallback` will. If `fallback` is 388 | alone it will also be executed. 389 | 390 | > A usecase for this is the AUR. A lot of packages are available there, so on 391 | > `arch` systems you can probably install anyting using `pacman` or with an 392 | > AUR helper. If that package is only available on AUR, and it can't be 393 | > installed with any other package managers, you can try compile it from 394 | > source or download it using a custom installer script that they provide. Or 395 | > install it with a different program. (Like Rust programs can be installed 396 | > using `cargo install`, or `node` programs with `npm -g`) But instead of 397 | > having a separate script for each system, or a custom script that skips on 398 | > `pacman` systems, just have a `fallback` script. 399 | 400 | ### Init Scripts 401 | 402 | > `init.user.sh` 403 | 404 | Scripts starting with `init` instead of the ordering first segment are run 405 | before any other script do. And if it's `user` privileged it will not just 406 | run, but it will be sourced so everything that it defines will be available 407 | later. 408 | 409 | > Thorough every module after that 410 | 411 | These scripts are run before [stowing the stow packages](#Stow-packages) so 412 | they can manually create folders that you don't want stow to 413 | [fold](https://www.gnu.org/software/stow/manual/stow.html#Tree-folding). 414 | These are usually folders that you want to interact with from multiple 415 | packages. Like `~/.config/`, `~/.local/bin` or `~/.config/systemd/user`. 416 | 417 | > If you're installing two modules, both to stow to the same path, where 418 | > there is no directory, `stow` for the first module would no create a 419 | > directory, just a single, _folded_ symlink. The second module then would 420 | > just not stow. 421 | > 422 | > There is, in theory a [tree unfolding][stow-un] mechanism in `stow`, but it 423 | > didn't work for me. It's maybe just because I can't use it in these 424 | > conditions because it does not know of the original package that put that 425 | > folded symlink there in the first place. 426 | 427 | ### Flagged scripts 428 | 429 | There can also be some special scripts that are not executed during 430 | standard installation. They are identified by their special first segment and 431 | they can be enabled using flags. 432 | 433 | #### Remove 434 | 435 | > `remove.sudo.sh` 436 | 437 | Using the `-r` flag, scripts with `remove` as their first segment will be run. 438 | This also causes `pont` to `unstow` every **stow package** from the module, 439 | and also removes the `.tarhash` file, marking the module uninstalled. 440 | 441 | Specifying the flag twice causes it to also run scripts that start with an `r` 442 | 443 | #### Update 444 | 445 | > `update.sudo.sh` 446 | 447 | using the `-u` flag, scripts with `update` as their first segment will be run. 448 | Non installed modules can't be updated. This won't expand the dependency graph 449 | and only the mentioned modules will be updated. You can force expanding with 450 | the `-e` flag after the `-u` though to update every dependency too. 451 | 452 | > Alternatively create a `Makefile` with an `update` target. 453 | 454 | ## Config files 455 | 456 | There are some files that are used for configuration but they are really 457 | simple and do not follow a common format 458 | 459 | ### Makefiles 460 | 461 | > This is meant for simpler modules where `root` is not needed and direct 462 | > dependencies are not used. For more granular installation (while it can be 463 | > implemented inside the `Makefile`) the regular 464 | > [Installation scripts](#Installation-scripts) are much easier to use. 465 | 466 | Makefiles provide an alternative or complementary mode of defining the 467 | installation, update and remove procedures using make targets. 468 | If there is a `Makefile` in the module, it will be executed if `make` is 469 | available, and running makefiles are enabled. (It is by default) 470 | 471 | They will always execute after the regular installation scripts and 472 | are always executed using `user` privileges. 473 | 474 | ### Dependencies 475 | 476 | > .dependencies 477 | 478 | This file lists all the dependencies the module has. It supports comments, 479 | lines starting with `#`. 480 | Every other line is a dependency and the first word can be the name of a 481 | [module](#Dotmodules) if its not prefixed with anything. It can also be a 482 | [tag](#Tags) if prefixed with `:` or a [preset](#Presets) 483 | if with `+`. More about those in their own sections. 484 | 485 | > The same format is used in presets too 486 | 487 | #### Dependency resolvment 488 | 489 | > Simple [DFS](https://en.wikipedia.org/wiki/Depth-first_search) 490 | 491 | When installing a module, first every single dependency it has will be 492 | installed before. **It avoids circular dependencies** by simply stopping at 493 | entries that are already executed and moves on. 494 | 495 | > Using the `-sm` or `--show-modules` flag, instead of installing (can be 496 | > turned back on) you only get the list of modules that are gonna be 497 | > executed 498 | 499 | ##### Base modules 500 | 501 | Modules listed in the `PONT_BASE_MODULES` variable (space separated) are always 502 | treated as selected. This can be used to define global dependencies. 503 | Base modules are placed at the beginning of selected modules, so they are 504 | executed earlier. 505 | 506 | #### Conditional dependencies 507 | 508 | Dependencies can be conditional. After the dependency statement you can put 509 | a `?`. Everything after that will be `eval`d and then `test`ed. If it's not 510 | true, the dependency will be skipped. 511 | 512 | An example usecase for this would be the counterpart of the 513 | [fallback section](#Fallback). There I defined how that module should build 514 | in case no other install scripts can install it. With conditional dependencies 515 | I can also list those that are required only for the fallback, only when 516 | the fallback will actually run. If a rust program can be installed with 517 | `pacman` on arch based systems and on any other systems you want to use 518 | `cargo` you'll end up with 2 install scripts like so: 519 | 520 | ```sh 521 | 0.root.pacman.sh 522 | 0.user.cargo.sh 523 | ``` 524 | 525 | and at least one dependency entry: 526 | 527 | ```sh 528 | rust ? [ ! $pacman ] 529 | ``` 530 | 531 | This tells `pont` only install the `rust` module while installing this module 532 | when there is no `pacman` available. 533 | 534 | > There are some pre calculated variables for these use-cases but you can use 535 | > anything, and you can expand it with your `pontrc` as it will be sourced from 536 | > `pont` 537 | 538 | Some of them: 539 | 540 | ```sh 541 | # Package managers 542 | pacman 543 | apt 544 | xbps 545 | # Init systems 546 | systemd 547 | # Distributions 548 | distribution # name in /etc/os-release 549 | arch # if distribution = 'Arch Linux', and so on 550 | void 551 | debian 552 | ubuntu 553 | fedora 554 | ``` 555 | 556 | #### Conditional modules 557 | 558 | > .condition 559 | 560 | Sometimes a module doesn't makes sense in an environment, but is used by 561 | many others as a non-essential dependency. In a headless environment like 562 | `wsl`, fonts are like this. It makes no sense to install fonts in `wsl`. But 563 | some of your modules might end up depending on them for convinience. 564 | 565 | Instead of marking each dependency with a condition, the module itself can be. 566 | For this, create a file name `.condition` in the modules root. It's content 567 | will simply be `eval`d before executing anything in the module. So not even 568 | `init` will run if `.condition` is false. 569 | 570 | ### Tags 571 | 572 | > .tags 573 | 574 | This file also supports comments, and each line defines a tag. 575 | This is used to define a module group on module level. 576 | 577 | Tags can be installed using the `:` prefix like so: 578 | 579 | ```sh 580 | pont :shell 581 | ``` 582 | 583 | This will install every module that has a `.tags` file with the line `shell`. 584 | 585 | Tags can both appear in `.dependencies` files and in `*.preset` files. 586 | 587 | ### Listing available tags 588 | 589 | Available tags can be listed and `sort`ed using the `-T` or `--list-tags` 590 | flags. These flag makes `pont` immediately exit. 591 | 592 | ```sh 593 | pont -T 594 | ``` 595 | 596 | ## Stow packages 597 | 598 | Every directory directly in a module that ends with `.` is 599 | a stow package. 600 | 601 | > So in a module named `zsh`, the `.zsh` directory is a stowable package. 602 | 603 | Just like scripts, stow package names are too divided by periods into segments. 604 | The last one as mentioned is for marking a directory as a stow package. 605 | 606 | ### First segment, target 607 | 608 | By default, stow packages will be stowed to `PONT_TARGET` (can be overriden 609 | in a `pontrc` file) which is just `HOME` by default. 610 | 611 | To make stowing more dynamic, stow modules can have variables before the `.` 612 | in their names. These variables will then be expanded. If it's an absolute 613 | path it will be treated as such (Ignoring `PONT_TARGET`) but if its a relative 614 | path (it doesn't start with `/`) it will be appended after `PONT_TARGET`. 615 | This path then will be used as the final target to stow to. 616 | 617 | > This variable can be set in the `init` script too if you wan't to be module 618 | > specific. These scripts are run before stowing and everything they define 619 | > is available during the installation of the module. 620 | 621 | ### Second segment, condition 622 | 623 | Just like packages, the second segment can be prefixed with `$`. In this case 624 | `pont` checks if that variable is set or not. If it's not prefixed, it will 625 | check with `command -v` if that it's a valid executable. 626 | 627 | ## Presets 628 | 629 | Presets basically standalone dependency files without anything to install. 630 | They have to have a `.preset` extension and they are searched under 631 | `$PONT_PRESETS_FOLDER` which by default the `presets` directory in your 632 | dotfiles directory. 633 | 634 | They can handle everything a normal dependency file can. 635 | 636 | You can reference a preset with the `+` prefix. If you have a preset called 637 | `shells.preset`, you can install it like so: 638 | 639 | ```sh 640 | pont +shells 641 | ``` 642 | 643 | Presets can be included in other presets and dependency files the same way 644 | 645 | `.dependencies` 646 | 647 | ```sh 648 | # this modules dependencies are all the shells I'm using 649 | +shells 650 | # and my vim setup 651 | vim 652 | ``` 653 | 654 | ### Listing available presets 655 | 656 | Available presets can be listed and `sort`ed using the `-P` or 657 | `--list-presets` flags. These flags makes `pont` immediately exit. 658 | 659 | ```sh 660 | pont -P 661 | ``` 662 | 663 | ## Tips and Tricks 664 | 665 | ### Junction modules and presets 666 | 667 | Having a dependency list, (A preset or a module with a .dependencies file) 668 | if you have a condition for each entry so that they are mutually exclusive, 669 | you can create an entity thats sole purpose is to conditionally redirect 670 | the dependency resolution. 671 | 672 | One such usecase would be to have a base `sys` module/preset that has 673 | dependencies on platform specific modules like `sys-debian`, `sys-arch` etc, 674 | with conditions so that they only run on their respective platforms. 675 | 676 | > You can further simplify it be having `.condition` files in the modules, 677 | > which is a stronger assurance that the module will only be installed on 678 | > the correct platform. In this case the junction dependency list doesn't 679 | > even need conditions! 680 | 681 | Then, other modules only have to reference this on entity, it will be resolved 682 | to the correct one. 683 | 684 | ## Troubleshooting 685 | 686 | ### Scripts 687 | 688 | If a script doesn't want to run, check if it has execute permissions. 689 | 690 | ```sh 691 | stat script.sh 692 | # or 693 | ls -l script.sh 694 | ``` 695 | 696 | Or let `pont` automatically fix them by using the `-X` or 697 | `--toggle-fix-permissions` flags. **Or** by setting the 698 | `PONT_FIX_PERMISSIONS=` variable manually to `1` in your environment or 699 | `pontrc` file. 700 | 701 | ## Far plans 702 | 703 | Once it's done, I might do a Rust rewrite for easier implementation of 704 | paralell execution while respecting the dependency tree. 705 | Which could be done in a script too but having all of the outputs and logs 706 | managed would be hard. The dotmodule "specification" won't really change, 707 | but it can expand. 708 | 709 | [stow-un]: https://www.gnu.org/software/stow/manual/stow.html#Tree-unfolding 710 | -------------------------------------------------------------------------------- /template/readme.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | This folder contains a module template which can be used by `cpt` to 4 | quickly scaffold you a module with your commonly used files 5 | 6 | ## Requirements 7 | 8 | Rust and cargo installed 9 | 10 | If cargo is available and cpt is not, `pont` will attempt to install it 11 | on scaffolding 12 | 13 | ## Usage 14 | 15 | ```sh 16 | pont --scaffold 17 | ``` 18 | 19 | This will copy this folder as many times as many arguments you gave to it 20 | overwriting every intance of `{{name}}` with the modules name. 21 | 22 | If a module already exists, it will be ignored. 23 | 24 | Only files with a `.tpl` extension will be affected, the rest is just copied 25 | the `.tpl` extension will be ommitted. (`a.sh.tpl` will become `a.sh`) 26 | -------------------------------------------------------------------------------- /template/{{name}}/0.root.pacman.sh.tpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pacman -Syu --needed --noconfirm {{module}} 4 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | dummy* 2 | -------------------------------------------------------------------------------- /test/00-example.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This is an example test. 6 | # First every test should set the flag `-e` to error out on any failure 7 | # immediately. 8 | # Then they all should source the test environment, so the script only 9 | # interacts in this folder. 10 | 11 | # Then run whatever the test should run 12 | 13 | # Then do assertions, and before that, call `sync` so changes are written to 14 | # the disk. If a test fails, the test should exit with 1, and optionally 15 | # print a message 16 | 17 | # Assertions 18 | sync 19 | var="1" 20 | [ 1 = "$var" ] || 21 | { echo "Base not linked"; exit 1; } 22 | 23 | # This example test always succeeds, you can use it to trigger cleanup 24 | # through `make` 25 | # ```sh 26 | # make test/00-example.test 27 | # ``` 28 | -------------------------------------------------------------------------------- /test/01-symlinks.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the "symlinks" module which have multiple 6 | # linkable packages inside, two of which use relative paths and will be linked 7 | # to PONT_TARGET without mentioning it. One that's an absolute path, and one 8 | # that has no variable target, and will default to PONT_TARGET. 9 | $COVERAGE ./pont.sh -q symlinks 10 | 11 | # Assertions 12 | sync 13 | echo "$PONT_TARGET/base" 14 | [ -e "$PONT_TARGET/symlinksfile" ] || 15 | { echo "Base not linked"; exit 1; } 16 | [ -e "$PONT_TARGET/symlinks/target_relative/relative" ] || 17 | { echo "Relative not linked"; exit 1; } 18 | [ -e "$PONT_TARGET/symlinks/target_absolute/absolute" ] || 19 | { echo "Absolute not linked"; exit 1; } 20 | -------------------------------------------------------------------------------- /test/02-conditional_symlinks.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the "conditional_symlinks" module in which 6 | # there are two modules, one that should be linked because the variable 7 | # DO_LINK is set and another that should not be linked because DO_NOT_LINK 8 | # is empty 9 | $COVERAGE ./pont.sh -q conditional_symlinks 10 | 11 | # Assertions 12 | sync 13 | [ -e "$PONT_TARGET/dolinkme" ] || 14 | { echo "Allowed conditional not linked"; exit 1; } 15 | [ ! -e "$PONT_TARGET/dontlinkme" ] || 16 | { echo "Disallowed conditional linked"; exit 1; } 17 | -------------------------------------------------------------------------------- /test/03-check-installed.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the "symlinks" module which have multiple 6 | # linkable packages inside, two of which use relative paths and will be linked 7 | # to PONT_TARGET without mentioning it. One that's an absolute path, and one 8 | # that has no variable target, and will default to PONT_TARGET. 9 | 10 | $COVERAGE ./pont.sh -q symlinks conditional_symlinks 11 | expected_installed="conditional_symlinks 12 | symlinks" 13 | $COVERAGE ./pont.sh -I > /dev/null 14 | installed=$(./pont.sh -I) 15 | echo "installed $installed" 16 | # Assertions 17 | sync 18 | [ "$installed" = "$expected_installed" ] || 19 | { echo "Installed list not correct"; exit 1; } 20 | -------------------------------------------------------------------------------- /test/04-check-installed-with-base.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the "symlinks" module while having the 6 | # PONT_BASE_MODULES set to "base" which should make that module install too. 7 | export PONT_BASE_MODULES="base" 8 | $COVERAGE ./pont.sh -q symlinks 9 | expected_installed="base 10 | symlinks" 11 | $COVERAGE ./pont.sh -I > /dev/null 12 | installed=$(./pont.sh -I) 13 | # Assertions 14 | sync 15 | [ "$installed" = "$expected_installed" ] || 16 | { echo "Installed list not correct"; exit 1; } 17 | -------------------------------------------------------------------------------- /test/05-script-permissions.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the `permissions` module. 6 | $COVERAGE ./pont.sh -q permissions 7 | # Assertions 8 | # The module itself fails when the `user` script runs on root level and when 9 | # the `root` script not. 10 | -------------------------------------------------------------------------------- /test/06-wsl-systemctl.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the `permissions` module. 6 | # faking wsl 7 | echo "export wsl=1" > .pontrc 8 | result=$(./pont.sh -q systemd) 9 | rm .pontrc 10 | # Assertions 11 | sync 12 | [ "$result" = "" ] || 13 | { echo "Result is not empty, systemd script ran on wsl"; exit 1; } 14 | -------------------------------------------------------------------------------- /test/07-non-wsl-systemctl.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test tries to install the `systemd` module on a systemd system 6 | # being a systemd system is enforced by mocking the is_installed function 7 | # in the .pontrc file 8 | # shellcheck disable=SC2016 9 | echo ' 10 | is_installed() { 11 | if [ "$1" = "systemctl" ]; then 12 | return 0 13 | fi 14 | command -v "$1" 2>/dev/null 1>/dev/null 15 | } 16 | ' > .pontrc 17 | result=$(./pont.sh -q systemd) 18 | rm .pontrc 19 | 20 | # Assertions 21 | sync 22 | [ "$result" = "systemd script" ] || 23 | { echo "Result is empty, systemd script did not run!"; exit 1; } 24 | -------------------------------------------------------------------------------- /test/08-list-presets.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if the script can find all presets 6 | $COVERAGE ./pont.sh -P 7 | result=$(./pont.sh -P) 8 | # Assertions 9 | sync 10 | [ "$result" = "a 11 | b" ] || 12 | { echo "Not all presets have been listed!"; exit 1; } 13 | -------------------------------------------------------------------------------- /test/09-list-modules.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if the script can find all presets 6 | $COVERAGE ./pont.sh -A > /dev/null 7 | result=$(./pont.sh -A) 8 | # Assertions 9 | sync 10 | [ "$result" = "base 11 | conditional_symlinks 12 | deprecated 13 | fallback 14 | lone_fallback 15 | permissions 16 | symlinks 17 | systemd" ] || 18 | { echo "Not all presets have been listed!"; exit 1; } 19 | -------------------------------------------------------------------------------- /test/10-list-outdated-modules.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if the script can find all outdated modules, modules 6 | # that have been changed since the last installation 7 | 8 | make_dummy_module dummy test1 9 | ./pont.sh -q dummy 10 | make_dummy_module dummy test2 11 | 12 | $COVERAGE ./pont.sh -O 13 | result=$(./pont.sh -O) 14 | clear_dummy_module dummy 15 | # Assertions 16 | sync 17 | [ "$result" = "dummy" ] || 18 | { echo "Not all outdated modules have been listed!"; exit 1; } 19 | -------------------------------------------------------------------------------- /test/11-list-deprecated.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if the script can find all deprecated modules 6 | $COVERAGE ./pont.sh -D 7 | result=$(./pont.sh -D) 8 | # Assertions 9 | sync 10 | [ "$result" = "deprecated" ] || 11 | { echo "Not all deprecated modules have been listed!"; exit 1; } 12 | -------------------------------------------------------------------------------- /test/12-list-tags.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if the script can find all deprecated modules 6 | $COVERAGE ./pont.sh -T 7 | result=$(./pont.sh -T) 8 | # Assertions 9 | sync 10 | [ "$result" = "basetag" ] || 11 | { echo "Not all deprecated modules have been listed!"; exit 1; } 12 | -------------------------------------------------------------------------------- /test/13-show-help.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | # This test checks if that both the help and version functions work 6 | # And that their first lines are the same 7 | $COVERAGE ./pont.sh -h > /dev/null 8 | $COVERAGE ./pont.sh -V > /dev/null 9 | help_result=$(./pont.sh -h | sed 1q) 10 | version_result=$(./pont.sh -V) 11 | # Assertions 12 | sync 13 | [ "$help_result" = "$version_result" ] || 14 | { echo "Help and version mismatch!"; exit 1; } 15 | -------------------------------------------------------------------------------- /test/14-fallback.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | 6 | $COVERAGE ./pont.sh -q fallback 7 | 8 | # Assertions 9 | sync 10 | [ -e "$PONT_TARGET/fallback/output" ] || 11 | { echo "Fallback script did not run"; exit 1; } 12 | [ ! -e "$PONT_TARGET/fallback/unavailable" ] || 13 | { echo "Unavailable script ran"; exit 1; } 14 | -------------------------------------------------------------------------------- /test/15-lone-fallback.test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # shellcheck disable=SC1091 4 | . ./test/env.sh 5 | 6 | $COVERAGE ./pont.sh -q lone_fallback 7 | 8 | # Assertions 9 | sync 10 | [ -e "$PONT_TARGET/lone_fallback/output" ] || 11 | { echo "Fallback script did not run"; exit 1; } 12 | -------------------------------------------------------------------------------- /test/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=SC1091 3 | . ./test/env.sh 4 | 5 | echo "Cleaning up target folder: $PONT_TARGET" 6 | rm -rf "${PONT_TARGET:?}" 7 | 8 | echo "Cleaning up hashfiles named: ${PONT_HASHFILE_NAME:-.tarhash}" 9 | find test -iname "${PONT_HASHFILE_NAME:-.tarhash}" -exec rm {} \; 10 | -------------------------------------------------------------------------------- /test/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir=$(dirname "$(readlink -f "$0")") 4 | echo "env set up into $script_dir" 5 | 6 | export DOTFILES_HOME="$script_dir/" 7 | export PONT_MODULES_HOME="$script_dir/modules" 8 | export PONT_PRESETS_HOME="$script_dir/presets" 9 | export PONT_TEMPLATE_HOME="$script_dir/template" 10 | export PONT_TARGET="$script_dir/target" 11 | export PONT_BASE_MODULES= 12 | 13 | mkdir -p "$PONT_TARGET" 14 | 15 | # Constructs a temporary module 16 | make_dummy_module() { 17 | mkdir -p "${PONT_MODULES_HOME}/${1:-dummy}" 18 | echo "${2:-test}" > "${PONT_MODULES_HOME}/${1:-dummy}/dummyfile" 19 | } 20 | 21 | clear_dummy_module() { 22 | rm -r "${PONT_MODULES_HOME}/${1:-dummy}" 23 | } 24 | -------------------------------------------------------------------------------- /test/modules/base/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/base/.gitkeep -------------------------------------------------------------------------------- /test/modules/base/.tags: -------------------------------------------------------------------------------- 1 | basetag 2 | -------------------------------------------------------------------------------- /test/modules/conditional_symlinks/.$DO_LINK.conditional_symlinks/dolinkme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/conditional_symlinks/.$DO_LINK.conditional_symlinks/dolinkme -------------------------------------------------------------------------------- /test/modules/conditional_symlinks/.$DO_NOT_LINK.conditional_symlinks/dontlinkme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/conditional_symlinks/.$DO_NOT_LINK.conditional_symlinks/dontlinkme -------------------------------------------------------------------------------- /test/modules/conditional_symlinks/init.user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export DO_NOT_LINK= 4 | export DO_LINK="1" 5 | -------------------------------------------------------------------------------- /test/modules/deprecated/.deprecated: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/deprecated/.deprecated -------------------------------------------------------------------------------- /test/modules/fallback/0.user.fallback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p "$PONT_TARGET/fallback/" 4 | > "$PONT_TARGET/fallback/output" 5 | -------------------------------------------------------------------------------- /test/modules/fallback/0.user.unavailable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p "$PONT_TARGET/fallback/" 4 | > "$PONT_TARGET/fallback/unavailable" 5 | -------------------------------------------------------------------------------- /test/modules/lone_fallback/0.user.fallback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p "$PONT_TARGET/lone_fallback/" 4 | > "$PONT_TARGET/lone_fallback/output" 5 | -------------------------------------------------------------------------------- /test/modules/permissions/0.root.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$(id -u)" != 0 ]; then 4 | echo "Can't run without root priviliges!" 5 | exit 1 6 | fi 7 | -------------------------------------------------------------------------------- /test/modules/permissions/0.user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$(id -u)" = 0 ]; then 4 | echo "Can't run with root priviliges!" 5 | exit 1 6 | fi 7 | -------------------------------------------------------------------------------- /test/modules/permissions/readme.md: -------------------------------------------------------------------------------- 1 | # Permissions 2 | 3 | By installing this module the permissions are tested, `?.user.*` scripts should 4 | run unpriviliged, as the user while `?.root.*` scripts should run as root 5 | -------------------------------------------------------------------------------- /test/modules/symlinks/.symlinks/symlinksfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/symlinks/.symlinks/symlinksfile -------------------------------------------------------------------------------- /test/modules/symlinks/ABSOLUTE_TARGET.symlinks/absolute: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/symlinks/ABSOLUTE_TARGET.symlinks/absolute -------------------------------------------------------------------------------- /test/modules/symlinks/RELATIVE_TARGET.symlinks/relative: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/modules/symlinks/RELATIVE_TARGET.symlinks/relative -------------------------------------------------------------------------------- /test/modules/symlinks/init.user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export RELATIVE_TARGET="symlinks/target_relative" 4 | export ABSOLUTE_TARGET="$PONT_TARGET/symlinks/target_absolute" 5 | export DO_NOT_LINK= 6 | export DO_LINK="1" 7 | -------------------------------------------------------------------------------- /test/modules/systemd/0.user.systemctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "systemd script" 4 | -------------------------------------------------------------------------------- /test/presets/a.preset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/presets/a.preset -------------------------------------------------------------------------------- /test/presets/more/b.preset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexAegis/pont/386b64a2d9fee3a50d5a2cccde7047ad2d3beef2/test/presets/more/b.preset -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | # Pont Tests 2 | 3 | ## Problems with coverage 4 | 5 | kcov is quite problematic, it can't track coverage in sub-shells and when 6 | running the coverage itself in a sub-shell, it doesn't forward the standard 7 | output, so I can't capture it for assertions. 8 | 9 | ### Workaround 10 | 11 | Where the operation is repeatable I run once for coverage and once for 12 | capture. 13 | 14 | ```sh 15 | # This would provide the same result every time and does not write anything 16 | $COVERAGE ./pont.sh -A 17 | result=$(./pont.sh -A) 18 | ``` 19 | 20 | Where it's not, but I need the captured output, I simply skip the coverage 21 | 22 | ```sh 23 | # This would not provide the same result after a second run 24 | result=$(./pont.sh module) 25 | ``` 26 | 27 | Thankfully, usually capturing the direct output of something to assert what 28 | happened is not needed because it did write something, so i can assert the 29 | written file. 30 | 31 | ```sh 32 | # This would not provide the same result after a second run 33 | $COVERAGE ./pont.sh module 34 | # check the file that this operation created 35 | ``` 36 | -------------------------------------------------------------------------------- /todos.md: -------------------------------------------------------------------------------- 1 | # Todos 2 | 3 | TODO: Single dotmodule file for dependencies / clashes / etc, ini or toml 4 | 5 | TODO: Multirequirements using `:` 0.root.pacman:cargo.sh 6 | 7 | TODO: use make on update and remove too use Make targets, check if exits 8 | 9 | TODO: deprecation alternatives prompt, check nvm and fnm 10 | 11 | TODO: Experiment with `sudo -l` to find out you have sudo access or not 12 | TODO: If not, automatically turn on `skip-root` and print some message 13 | 14 | TODO: Clash support. Use .clash file, if two modules clash, ask which to use 15 | TODO: If a clashing module is already installed, abort, ask if interactive, 16 | TODO: remove other if forced. Ignore deprecated modules 17 | 18 | TODO: clash feature support tags, see if something from that tag is installed 19 | 20 | TODO: track dangling dependencies. When installing leave a file in the module 21 | TODO: that will store a snapshot of the dependencies. During uninstall check 22 | TODO: If there is a dependency somewhere that is not directly installed. 23 | TODO: (Or maybe dont and leave this to pont2) 24 | 25 | TODO: If the module contains a git submodule. Check it out / update it 26 | 27 | TODO: Experiment with paralell execution (sort dependencies into a tree) 28 | TODO: Right now every dependency is sorted into a column and executed 29 | TODO: sequentially. The new executor would treat everything in one column 30 | TODO: and one indentation as modules that can be executed paralelly 31 | TODO: then pass everything below as a dependency list to it with one level 32 | TODO: of indentation removed 33 | TODO: make a test modules directory with modules with logs and sleeps 34 | TODO: Also a buffer output is needed to display this 35 | TODO: It should keep a buffer as high as many modules are currently being 36 | TODO: installed and then do the logging above it like normal 37 | TODO: Or have an indented section below each entry with a preview log 38 | TODO: or both 39 | TODO: investigate if feasible 40 | TODO: add flags to disable or enable paralell work 41 | TODO: add a .lock file with PID into each module just in case and remove after 42 | 43 | TODO: forced clash on every input module. This is useful when you want to 44 | TODO: have a menu to install something in a category. combine with 45 | TODO: no-uninstall flags 46 | --------------------------------------------------------------------------------