├── .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 |
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 | [](https://github.com/AlexAegis/pont/actions?query=workflow%3ATest) [](https://github.com/AlexAegis/pont/actions?query=workflow%3ALint) [](https://www.codacy.com/manual/AlexAegis/pont?utm_source=github.com&utm_medium=referral&utm_content=AlexAegis/pont&utm_campaign=Badge_Grade) [](https://www.codacy.com/manual/AlexAegis/pont?utm_source=github.com&utm_medium=referral&utm_content=AlexAegis/pont&utm_campaign=Badge_Coverage) [](https://codecov.io/gh/AlexAegis/pont)
4 |
5 | > Check my [dotfiles](https://github.com/alexaegis/dotfiles) as a live example
6 |
7 | 
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 | 
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 |
--------------------------------------------------------------------------------