diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a40537..7d8a518 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "1.26.2" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29ef5e2..e4c294e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,10 +23,10 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "1.26.2" @@ -48,6 +48,8 @@ jobs: mkdir -p dist cp model/_version.txt dist/version.txt cp model/constants/doth.sh dist/doth.sh + cp model/constants/doth-init.sh dist/doth-init.sh + cp LICENSE dist/LICENSE platforms=( "linux/amd64" @@ -64,14 +66,14 @@ jobs: if [ "$os" = "windows" ]; then ext=".exe" fi - output="doth-${os}-${arch}${ext}" + output="doth-${{ inputs.version }}-${os}-${arch}${ext}" CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build -ldflags="-s -w" -o "dist/$output" . chmod +x "dist/$output" echo "Built: dist/$output" done - name: Create Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ inputs.version }} files: dist/* diff --git a/LICENSE b/LICENSE index e69de29..a2c800e 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 5000K + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cmd/add.go b/cmd/add.go index 76d3bd3..6340864 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -23,6 +23,10 @@ If the target directory already exists, all existing files will be added with th This can be skipped with the --skip-existing flag (-s). `, Run: func(cmd *cobra.Command, args []string) { + if util.ConfirmRunIfRoot() == false { + return + } + name, err := cmd.Flags().GetString("name") if err != nil { fmt.Printf("Error getting name flag: %v\n", err) diff --git a/cmd/deploy.go b/cmd/deploy.go index 38aec5e..078b0ec 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -20,6 +20,9 @@ Takes in one or more config.yaml files that will be applied when deploying the m This command will copy, symlink, or render the files from the modules to their respective target locations based on the defined strategies. If a module has dependencies, they will be installed first using the appropriate strategies.`, Run: func(cmd *cobra.Command, args []string) { + if util.ConfirmRunIfRoot() == false { + return + } configPaths, err := cmd.Flags().GetStringSlice("config") if err != nil { diff --git a/cmd/init.go b/cmd/init.go index 5022038..acdca72 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -49,7 +49,10 @@ This will create a doth.yaml file with the provided configuration, and a modules pipeline := model.NewPipeline() - if destructive { + _, err = model.LoadDothFileFromCwd() + + // ask for confirmation if doth is already set up + if destructive && err == nil { pipeline.AddModule(model.NewConfirmStep("Force create a new project?")) } diff --git a/cmd/lock.go b/cmd/lock.go new file mode 100644 index 0000000..ec6fa34 --- /dev/null +++ b/cmd/lock.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/5000K/doth/model" + "github.com/5000K/doth/util" + "github.com/spf13/cobra" +) + +// lockCmd represents the lock command +var lockCmd = &cobra.Command{ + Use: "lock", + Short: "Locks the project to a specific version of doth", + Long: `Locks the project to a specific version of doth (defaults to the current version). + +This only affects the wrapper script. If you installed doth using another source, this won't have any effect.`, + Run: func(cmd *cobra.Command, args []string) { + version, err := cmd.Flags().GetString("version") + if err != nil { + fmt.Printf("Error getting version flag: %v\n", err) + return + } + + loc := util.CleanPath(model.DothLockFileLocation) + + if ok, _ := util.Exists(loc); ok { + err := util.Delete(loc) + if err != nil { + fmt.Printf("Failed to overwrite existing lock: %v\n", err) + return + } + } + + err = os.WriteFile(loc, []byte(version), 0644) + if err != nil { + fmt.Printf("Failed to lock the project: %v\n", err) + return + } + + fmt.Println("Project locked.") + }, +} + +// lockCmd represents the lock command +var unlockCmd = &cobra.Command{ + Use: "unlock", + Short: "Unlocks the project", + Long: `Unlocks the project. + +This only affects the wrapper script. If you installed doth using another source, this won't have any effect.`, + Run: func(cmd *cobra.Command, args []string) { + + loc := util.CleanPath(model.DothLockFileLocation) + + if ok, _ := util.Exists(loc); !ok { + fmt.Println("Project wasn't locked.") + return + } + + err := util.Delete(loc) + if err != nil { + fmt.Printf("Failed to unlock the project: %v\n", err) + return + } + + fmt.Println("Project unlocked.") + }, +} + +func init() { + versionCmd.AddCommand(lockCmd) + versionCmd.AddCommand(unlockCmd) + + lockCmd.Flags().StringP("version", "v", model.Version, "The version of doth to lock to (defaults to the current version).") +} diff --git a/cmd/version.go b/cmd/version.go index 53f6b6c..b88782f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,6 +1,3 @@ -/* -Copyright © 2026 NAME HERE -*/ package cmd import ( diff --git a/cmd/wrapper.go b/cmd/wrapper.go index c7f438a..5f1f0ae 100644 --- a/cmd/wrapper.go +++ b/cmd/wrapper.go @@ -1,6 +1,3 @@ -/* -Copyright © 2026 NAME HERE -*/ package cmd import ( diff --git a/model/constants.go b/model/constants.go index 18bd12d..cabebef 100644 --- a/model/constants.go +++ b/model/constants.go @@ -25,3 +25,5 @@ const GitignoreFileLocation = "./.gitignore" var GitignoreFileTemplate string const LocalStateDir = "./.doth" + +const DothLockFileLocation = "./doth.lock" diff --git a/model/constants/doth-init.sh b/model/constants/doth-init.sh new file mode 100755 index 0000000..c3b206b --- /dev/null +++ b/model/constants/doth-init.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# This is a one-time bootstrapping script to initialize a fully self-contained doth project, including the current wrapper. + +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 + + +# 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() { + echo "Installing the current version of doth..." + NEW_VERSION=$(curl -Ls https://github.com/5000K/doth/releases/latest/download/version.txt) + ${GO_COMMAND} install github.com/5000K/doth@$NEW_VERSION +} +# is doth available? +if ! command -v doth &> /dev/null; then + setup_go + setup_doth +fi + +# initialize a fresh doth project +doth init --modules ./modules --verbose + +echo "doth is initialized and ready to use!" +echo "instead of installing doth globally, use ./doth.sh to run the current version of doth." diff --git a/model/constants/doth.sh b/model/constants/doth.sh old mode 100644 new mode 100755 index d195f88..f4c5fb3 --- a/model/constants/doth.sh +++ b/model/constants/doth.sh @@ -10,6 +10,7 @@ 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} @@ -53,13 +54,21 @@ setup_go() { } setup_doth() { - echo "Installing the current version of doth..." - NEW_VERSION=$(curl -Ls https://github.com/5000K/doth/releases/latest/download/version.txt) + 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" } -check_doth_version() { +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 @@ -78,7 +87,7 @@ if ! command -v doth &> /dev/null; then setup_go setup_doth else - check_doth_version + update_doth fi # call doth with the provided arguments diff --git a/util/gates.go b/util/gates.go new file mode 100644 index 0000000..7816669 --- /dev/null +++ b/util/gates.go @@ -0,0 +1,17 @@ +package util + +import "os" + +func IsRoot() bool { + return os.Geteuid() == 0 +} + +const runAsRootConfirm = "Warning: Running doth as root is not recommended. Please run doth as a regular user.\nDo you want to continue anyway?" + +func ConfirmRunIfRoot() bool { + if IsRoot() { + return ConfirmAction(runAsRootConfirm) == nil + } + + return true +}