From ff199a8989fdec9b28aee7595e5b99eb465c19ca Mon Sep 17 00:00:00 2001 From: kicher-erbse Date: Wed, 20 May 2026 07:06:24 +0000 Subject: [PATCH 1/3] implement demo, fix oversights --- _demo/.gitignore | 7 +++ _demo/doth.sh | 94 +++++++++++++++++++++++++++++++ _demo/doth.yaml | 19 +++++++ _demo/modules/example/copy.yml | 1 + _demo/modules/example/link.yml | 1 + _demo/modules/example/module.yaml | 20 +++++++ _demo/modules/example/render.yml | 1 + _demo/target/README.md | 3 + cmd/deploy.go | 13 ++++- cmd/install.go | 23 ++++---- 10 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 _demo/.gitignore create mode 100755 _demo/doth.sh create mode 100644 _demo/doth.yaml create mode 100644 _demo/modules/example/copy.yml create mode 100755 _demo/modules/example/link.yml create mode 100644 _demo/modules/example/module.yaml create mode 100644 _demo/modules/example/render.yml create mode 100644 _demo/target/README.md diff --git a/_demo/.gitignore b/_demo/.gitignore new file mode 100644 index 0000000..7d052a4 --- /dev/null +++ b/_demo/.gitignore @@ -0,0 +1,7 @@ +# This is the .gitignore file. It defines which files and directories should be ignored by git. + +# Ignore the .doth directory, it contains local state and should not be committed to version control. +.doth/ + +# target folders +target/example/** \ No newline at end of file diff --git a/_demo/doth.sh b/_demo/doth.sh new file mode 100755 index 0000000..f4c5fb3 --- /dev/null +++ b/_demo/doth.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# This is a wrapper script for doth. It ensures that go and doth are installed, and if not, it installs it. Then it calls doth with the provided arguments. + +ROOT_DIR=$(dirname "$(realpath "$0")") + +GO_VERSION=1.26.2 +TARGET_FOLDER=${ROOT_DIR}/.doth/go +GOPATH_FOLDER=${ROOT_DIR}/.doth/gopath + +GO_COMMAND=${TARGET_FOLDER}/bin/go + +DOTH_LOCK=${ROOT_DIR}/doth.lock + +# set paths for this script +export GOPATH=${GOPATH_FOLDER} +export GOBIN=${GOPATH_FOLDER}/bin +export PATH=${TARGET_FOLDER}/bin:${GOBIN}:$PATH + +detect_platform() { + local os arch + os=$(uname -s | tr '[:upper:]' '[:lower:]') + arch=$(uname -m) + + case "$os" in + linux) GO_OS="linux" ;; + darwin) GO_OS="darwin" ;; + *) echo "Unsupported OS: $os"; exit 1 ;; + esac + + case "$arch" in + x86_64) GO_ARCH="amd64" ;; + aarch64|arm64) GO_ARCH="arm64" ;; + armv6l|armv7l) GO_ARCH="armv6l" ;; + *) echo "Unsupported architecture: $arch"; exit 1 ;; + esac +} + +setup_go() { + # check if Go is already installed + if command -v ${GO_COMMAND} &> /dev/null; then + return + fi + + detect_platform + echo "Installing Go ${GO_VERSION} (${GO_OS}-${GO_ARCH})..." + + # Install Go + mkdir -p ${TARGET_FOLDER} + mkdir -p ${GOBIN} + curl -Ls -o ${ROOT_DIR}/.doth/go.tar.gz https://go.dev/dl/go${GO_VERSION}.${GO_OS}-${GO_ARCH}.tar.gz + tar -C ${TARGET_FOLDER} --strip-components=1 -xzf ${ROOT_DIR}/.doth/go.tar.gz + rm ${ROOT_DIR}/.doth/go.tar.gz +} + +setup_doth() { + if [[ -f "${DOTH_LOCK}" ]]; then + NEW_VERSION=$(cat "${DOTH_LOCK}") + else + NEW_VERSION=$(curl -Ls https://github.com/5000K/doth/releases/latest/download/version.txt) + fi + echo "Installing doth ${NEW_VERSION}..." + ${GO_COMMAND} install github.com/5000K/doth@$NEW_VERSION + doth wrapper > "$0" +} + +update_doth() { + if [[ -f "${DOTH_LOCK}" ]]; then + return + fi + + NEW_VERSION=$(curl -Ls https://github.com/5000K/doth/releases/latest/download/version.txt) + CURRENT_VERSION=$(doth version --raw) + if [ "$NEW_VERSION" != "$CURRENT_VERSION" ]; then + echo "A new version of doth is available: $NEW_VERSION. You have $CURRENT_VERSION. Updating..." + chmod -R u+w ${GOPATH_FOLDER}/** + rm -rf ${GOPATH_FOLDER} + ${GO_COMMAND} install github.com/5000K/doth@$NEW_VERSION + echo "Updating wrapper script..." + doth wrapper > "$0" + chmod +x "$0" + fi +} + +# is doth available? +if ! command -v doth &> /dev/null; then + setup_go + setup_doth +else + update_doth +fi + +# call doth with the provided arguments +doth "$@" \ No newline at end of file diff --git a/_demo/doth.yaml b/_demo/doth.yaml new file mode 100644 index 0000000..dd90c1e --- /dev/null +++ b/_demo/doth.yaml @@ -0,0 +1,19 @@ +# This is the doth.yaml file. It defines the configuration for your doth project. + +# The directory where your modules are located. Relative to the current working directory. +modulePath: "./modules" + +# Set to true if the deploy command should fail if no config files are provided. +requireConfig: true + +# Here, you can define dependencies you want to install, and valid package sources for them. +# This is used by the "install" command to determine how to install a dependency based on the package sources configured/detected. +deps: + - name: curl + packages: + pacman: curl + apt: curl + brew: curl + +# This is the version of the doth file format. Do not edit this manually. +dothVersionDoNotEditManually: 1 diff --git a/_demo/modules/example/copy.yml b/_demo/modules/example/copy.yml new file mode 100644 index 0000000..ec933dc --- /dev/null +++ b/_demo/modules/example/copy.yml @@ -0,0 +1 @@ +# dummy file - should be copied over diff --git a/_demo/modules/example/link.yml b/_demo/modules/example/link.yml new file mode 100755 index 0000000..f4646e0 --- /dev/null +++ b/_demo/modules/example/link.yml @@ -0,0 +1 @@ +# dummy file - should be symlinked diff --git a/_demo/modules/example/module.yaml b/_demo/modules/example/module.yaml new file mode 100644 index 0000000..119ac29 --- /dev/null +++ b/_demo/modules/example/module.yaml @@ -0,0 +1,20 @@ +skip: false +target: ./target/example +files: + - name: copy.yml + strategy: copy + - name: link.yml + strategy: link + - name: render.yml + strategy: render +deps: + - name: curl + packages: + pacman: curl + apt: curl + brew: curl + - name: wget + packages: + pacman: wget + apt: wget + brew: wget diff --git a/_demo/modules/example/render.yml b/_demo/modules/example/render.yml new file mode 100644 index 0000000..7851811 --- /dev/null +++ b/_demo/modules/example/render.yml @@ -0,0 +1 @@ +renderedValue: {{ .value }} diff --git a/_demo/target/README.md b/_demo/target/README.md new file mode 100644 index 0000000..dce4b7f --- /dev/null +++ b/_demo/target/README.md @@ -0,0 +1,3 @@ +# target + +This is where the output of doth goes. Run `./doth.sh deploy` in the \_demo directory to see what it does. diff --git a/cmd/deploy.go b/cmd/deploy.go index fdb76a9..969368a 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -62,7 +62,7 @@ If a module has dependencies, they will be installed first using the appropriate _ = len(configs) - planDeploy(configs).Run( + planDeploy(configs, configPaths).Run( dry, verbose, model.CreatePipelineConfig( true, autoconfirm, ), @@ -106,7 +106,16 @@ func readModules() []model.Module { return modules } -func planDeploy(configs model.ConfigMap) *model.Pipeline { +func planDeploy(configs model.ConfigMap, configPaths []string) *model.Pipeline { + doth, err := model.LoadDothFileFromCwd() + if err != nil { + panic("failed to load doth file: " + err.Error()) + } + + if (len(configPaths) == 0) && doth.RequireConfig { + panic("no config provided, but required to deploy this doth project") + } + pipeline := model.NewPipeline() modules := readModules() diff --git a/cmd/install.go b/cmd/install.go index 63db810..8e532eb 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -63,55 +63,55 @@ var installCmd = &cobra.Command{ if cmd.Flags().Changed("apt") { sources = append(sources, model.PackageSource{ Name: "apt", - Command: "sudo apt install --yes {package}", + Command: "sudo apt install --yes '{package}'", }) } if cmd.Flags().Changed("apt-get") { sources = append(sources, model.PackageSource{ Name: "apt-get", - Command: "sudo apt-get install --yes {package}", + Command: "sudo apt-get install --yes '{package}'", }) } if cmd.Flags().Changed("dnf") { sources = append(sources, model.PackageSource{ Name: "dnf", - Command: "sudo dnf install --assumeyes {package}", + Command: "sudo dnf install --assumeyes '{package}'", }) } if cmd.Flags().Changed("pacman") { sources = append(sources, model.PackageSource{ Name: "pacman", - Command: "sudo pacman -S --noconfirm {package}", + Command: "sudo pacman -S --noconfirm '{package}'", }) } if cmd.Flags().Changed("yay") { sources = append(sources, model.PackageSource{ Name: "yay", - Command: "yay -S --noconfirm {package}", + Command: "yay -S --noconfirm '{package}'", }) } if cmd.Flags().Changed("paru") { sources = append(sources, model.PackageSource{ Name: "paru", - Command: "paru -S --noconfirm {package}", + Command: "paru -S --noconfirm '{package}'", }) } if cmd.Flags().Changed("go") { sources = append(sources, model.PackageSource{ Name: "go", - Command: "go install {package}", + Command: "go install '{package}'", }) } if cmd.Flags().Changed("npm") { sources = append(sources, model.PackageSource{ Name: "npm", - Command: "npm install -g {package}", + Command: "npm install -g '{package}'", }) } if cmd.Flags().Changed("brew") { sources = append(sources, model.PackageSource{ Name: "brew", - Command: "yes | brew install {package}", + Command: "yes | brew install '{package}'", }) } @@ -189,7 +189,7 @@ func planInstall(sources []model.PackageSource, silent bool) *model.Pipeline { } pipeline := model.NewPipeline(). - AddModule(model.NewConfirmStep("doth install gives this doth project shell execution rights. Use -d to preview the commands that would run.\nProceed?")) + AddModule(model.NewConfirmStep("\"doth install\" could be unsafe to run in some projects. Use -d to preview the commands that would run.\nProceed?")) deps := collectDependencies() @@ -209,6 +209,9 @@ func planInstall(sources []model.PackageSource, silent bool) *model.Pipeline { pipeline.AddModule(model.NewLogStep(fmt.Sprintf("\nInstall dependency %s using source %s ", dep.Name, source.Name), false)) } + packageName = strings.TrimSpace(packageName) + packageName = strings.Trim(packageName, "\"'") // remove any surrounding quotes + command := strings.ReplaceAll(source.Command, "{package}", packageName) pipeline.AddModule(model.NewExecuteShellCommandStep(command, silent)) break From 03a8fe27ef08876b88d3166653e6ad12d839a75c Mon Sep 17 00:00:00 2001 From: kicher-erbse Date: Wed, 20 May 2026 07:11:39 +0000 Subject: [PATCH 2/3] add folder examples --- _demo/config.yml | 1 + _demo/modules/example/copy-folder/fileinfolder.txt | 1 + _demo/modules/example/link-folder/fileinfolder.txt | 1 + _demo/modules/example/module.yaml | 6 ++++++ _demo/modules/example/render-folder/fileinfolder.txt | 2 ++ 5 files changed, 11 insertions(+) create mode 100644 _demo/config.yml create mode 100644 _demo/modules/example/copy-folder/fileinfolder.txt create mode 100644 _demo/modules/example/link-folder/fileinfolder.txt create mode 100644 _demo/modules/example/render-folder/fileinfolder.txt diff --git a/_demo/config.yml b/_demo/config.yml new file mode 100644 index 0000000..16bffaa --- /dev/null +++ b/_demo/config.yml @@ -0,0 +1 @@ +value: '"THIS IS FOR THE RENDER!"' diff --git a/_demo/modules/example/copy-folder/fileinfolder.txt b/_demo/modules/example/copy-folder/fileinfolder.txt new file mode 100644 index 0000000..9b803cf --- /dev/null +++ b/_demo/modules/example/copy-folder/fileinfolder.txt @@ -0,0 +1 @@ +This file should also be copied over, by simply being within a folder mentioned in the module.yaml. \ No newline at end of file diff --git a/_demo/modules/example/link-folder/fileinfolder.txt b/_demo/modules/example/link-folder/fileinfolder.txt new file mode 100644 index 0000000..55d774b --- /dev/null +++ b/_demo/modules/example/link-folder/fileinfolder.txt @@ -0,0 +1 @@ +This file should be untouched, the folder it is in will simply be symlinked. \ No newline at end of file diff --git a/_demo/modules/example/module.yaml b/_demo/modules/example/module.yaml index 119ac29..c18e8de 100644 --- a/_demo/modules/example/module.yaml +++ b/_demo/modules/example/module.yaml @@ -7,6 +7,12 @@ files: strategy: link - name: render.yml strategy: render + - name: copy-folder # copy an entire folder + strategy: copy + - name: link-folder # link an entire folder + strategy: link + - name: render-folder # render an entire folder + strategy: render deps: - name: curl packages: diff --git a/_demo/modules/example/render-folder/fileinfolder.txt b/_demo/modules/example/render-folder/fileinfolder.txt new file mode 100644 index 0000000..6d8a64f --- /dev/null +++ b/_demo/modules/example/render-folder/fileinfolder.txt @@ -0,0 +1,2 @@ +This file will be rendered, because the folder it is in is rendered. +Here is a rendered output: {{ .value }} From a5eeff4aedfe6ea1175786df442fa0dfbc0b4a7d Mon Sep 17 00:00:00 2001 From: kicher-erbse Date: Mon, 8 Jun 2026 07:07:58 +0000 Subject: [PATCH 3/3] add demo --- _demo/.gitignore | 2 +- _demo/README.md | 31 +++++++++++++++++++++++++++++++ _demo/{config.yml => config.yaml} | 0 _demo/target/README.md | 2 +- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 _demo/README.md rename _demo/{config.yml => config.yaml} (100%) diff --git a/_demo/.gitignore b/_demo/.gitignore index 7d052a4..8293351 100644 --- a/_demo/.gitignore +++ b/_demo/.gitignore @@ -4,4 +4,4 @@ .doth/ # target folders -target/example/** \ No newline at end of file +target/example/** diff --git a/_demo/README.md b/_demo/README.md new file mode 100644 index 0000000..008586b --- /dev/null +++ b/_demo/README.md @@ -0,0 +1,31 @@ +# doth demo + +## Run it + +Execute `./doth.sh deploy -c config.yaml -d` to see what it would do. + +Execute `./doth.sh deploy -c config.yaml` to actually run it (or `./doth.sh deploy -c config.yaml -v` for a verbose version). + +You don't need anything to run this demo besides the demo folder itself. doth.sh is the so called "doth wrapper", which will automatically setup the most current version of doth for you. + +## What it does + +The demo has a single module (modules/example), which has six configured steps in it's module.yaml: + +```yaml +files: + - name: copy.yml # copies copy.yml + strategy: copy + - name: link.yml # creates a symlink for link.yml + strategy: link + - name: render.yml # renders render.yml using the provided config + strategy: render + - name: copy-folder # copy an entire folder + strategy: copy + - name: link-folder # link an entire folder + strategy: link + - name: render-folder # render an entire folder using the provided config + strategy: render +``` + +The output folder is configured to be ./target/example - so take a look into this folder after you ran the demo! diff --git a/_demo/config.yml b/_demo/config.yaml similarity index 100% rename from _demo/config.yml rename to _demo/config.yaml diff --git a/_demo/target/README.md b/_demo/target/README.md index dce4b7f..62b4a88 100644 --- a/_demo/target/README.md +++ b/_demo/target/README.md @@ -1,3 +1,3 @@ # target -This is where the output of doth goes. Run `./doth.sh deploy` in the \_demo directory to see what it does. +This is where the output of doth goes. Run `./doth.sh deploy -c config.yaml` in the \_demo directory to see what it does.