diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 36a2da4..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: basebuild - -on: - pull_request: - push: - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - - name: Run tests - run: go test ./... - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} diff --git a/.gitignore b/.gitignore index f6ede02..8c9ba86 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ go.work *.log lcg dist/ +shell-code/build.env +bin-linux-amd64/* +bin-linux-arm64/* +binaries-for-upload/* \ No newline at end of file diff --git a/Dockerfiles/ImageBuild/Dockerfile b/Dockerfiles/ImageBuild/Dockerfile new file mode 100644 index 0000000..c36711c --- /dev/null +++ b/Dockerfiles/ImageBuild/Dockerfile @@ -0,0 +1,25 @@ +FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS builder + +ARG TARGETARCH + +RUN apk add git && go install mvdan.cc/garble@latest + +WORKDIR /app + +COPY . . + +RUN echo $BUILDPLATFORM > buildplatform +RUN echo $TARGETARCH > targetarch + +# RUN GOOS=linux GOARCH=$TARGETARCH go build -o output/go-ansible-vault +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} garble -literals -tiny build -ldflags="-w -s" -o /app/go-lcg . + +FROM alpine:latest + +WORKDIR /root + +# COPY --from=builder /app/buildplatform . +# COPY --from=builder /app/targetarch . +COPY --from=builder /app/go-lcg /root/lcg + +ENTRYPOINT ["/root/lcg"] \ No newline at end of file diff --git a/Dockerfiles/LocalCompile/Dockerfile b/Dockerfiles/LocalCompile/Dockerfile new file mode 100644 index 0000000..08ce606 --- /dev/null +++ b/Dockerfiles/LocalCompile/Dockerfile @@ -0,0 +1,23 @@ +FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS build +ARG TARGETOS +ARG TARGETARCH +RUN apk add git && go install mvdan.cc/garble@latest +WORKDIR /src +ENV CGO_ENABLED=0 +COPY go.* . +RUN go mod download +COPY . . + +# RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/go-ansible-vault . +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} garble -literals -tiny build -ldflags="-w -s" -o /out/go-lcg . + +FROM scratch AS bin-unix +COPY --from=build /out/go-lcg /lcg + +FROM bin-unix AS bin-linux +FROM bin-unix AS bin-darwin + +FROM scratch AS bin-windows +COPY --from=build /out/go-lcg /lcg.exe + +FROM bin-${TARGETOS} AS bin \ No newline at end of file diff --git a/README.md b/README.md index 7f8bbd9..5d5754f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,40 @@ tar -xvzf linux-command-gpt.tar.gz Do you want to (c)opy, (r)egenerate, or take (N)o action on the command? (c/r/N): ``` +```bash +> LCG_PROMPT='Provide full response' LCG_MODEL=codellama:13b lcg 'i need bash script +to execute some command by ssh on some array of hosts' +Completed in 181.16 seconds + +Here is a sample Bash script that demonstrates how to execute commands over SSH on an array of hosts: +```bash +#!/bin/bash + +hosts=(host1 host2 host3) + +for host in "${hosts[@]}"; do + ssh $host "echo 'Hello, world!' > /tmp/hello.txt" +done +``` +This script defines an array `hosts` that contains the names of the hosts to connect to. The loop iterates over each element in the array and uses the `ssh` command to execute a simple command on the remote host. In this case, the command is `echo 'Hello, world!' > /tmp/hello.txt`, which writes the string "Hello, world!" to a file called `/tmp/hello.txt`. + +You can modify the script to run any command you like by replacing the `echo` command with your desired command. For example, if you want to run a Python script on each host, you could use the following command: +```bash +ssh $host "python /path/to/script.py" +``` +This will execute the Python script located at `/path/to/script.py` on the remote host. + +You can also modify the script to run multiple commands in a single SSH session by using the `&&` operator to chain the commands together. For example: +```bash +ssh $host "echo 'Hello, world!' > /tmp/hello.txt && python /path/to/script.py" +``` +This will execute both the `echo` command and the Python script in a single SSH session. + +I hope this helps! Let me know if you have any questions or need further assistance. + +Do you want to (c)opy, (r)egenerate, or take (N)o action on the command? (c/r/N): +``` + To use the "copy to clipboard" feature, you need to install either the `xclip` or `xsel` package. ### Options diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..b18d465 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +v1.0.1 diff --git a/go.mod b/go.mod index a846ac1..70d6651 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/asrul/linux-command-gpt +module github.com/direct-dev-ru/linux-command-gpt go 1.18 diff --git a/gpt/gpt.go b/gpt/gpt.go index 6337856..2555d38 100644 --- a/gpt/gpt.go +++ b/gpt/gpt.go @@ -27,8 +27,13 @@ type Chat struct { } type Gpt3Request struct { - Model string `json:"model"` - Messages []Chat `json:"messages"` + Model string `json:"model"` + Stream bool `json:"stream"` + Messages []Chat `json:"messages"` + Options Gpt3Options `json:"options"` +} + +type Gpt3Options struct { Temperature float64 `json:"temperature"` } @@ -38,6 +43,20 @@ type Gpt3Response struct { } `json:"choices"` } +// LlamaResponse represents the response structure. +type OllamaResponse struct { + Model string `json:"model"` + CreatedAt string `json:"created_at"` + Message Chat `json:"message"` + Done bool `json:"done"` + TotalDuration int64 `json:"total_duration"` + LoadDuration int64 `json:"load_duration"` + PromptEvalCount int64 `json:"prompt_eval_count"` + PromptEvalDuration int64 `json:"prompt_eval_duration"` + EvalCount int64 `json:"eval_count"` + EvalDuration int64 `json:"eval_duration"` +} + func (gpt3 *Gpt3) deleteApiKey() { filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile if _, err := os.Stat(filePath); os.IsNotExist(err) { @@ -132,17 +151,19 @@ func (gpt3 *Gpt3) Completions(ask string) string { panic(err) } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(gpt3.ApiKey)) + // req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(gpt3.ApiKey)) messages := []Chat{ {"system", gpt3.Prompt}, - {"user", ask}, + {"user", ask + "." + gpt3.Prompt}, } payload := Gpt3Request{ - gpt3.Model, - messages, - gpt3.Temperature, + Model: gpt3.Model, + Messages: messages, + Stream: false, + Options: Gpt3Options{gpt3.Temperature}, } + payloadJson, err := json.Marshal(payload) if err != nil { panic(err) @@ -166,11 +187,13 @@ func (gpt3 *Gpt3) Completions(ask string) string { return "" } - var res Gpt3Response + // var res Gpt3Response + var res OllamaResponse err = json.Unmarshal(body, &res) if err != nil { panic(err) } - return strings.TrimSpace(res.Choices[0].Message.Content) + // return strings.TrimSpace(res.Choices[0].Message.Content) + return strings.TrimSpace(res.Message.Content) } diff --git a/main.go b/main.go index 3737565..53d3797 100644 --- a/main.go +++ b/main.go @@ -1,27 +1,42 @@ package main import ( + _ "embed" "fmt" "math" "os" "os/user" + "path" "strings" "time" - "github.com/asrul/linux-command-gpt/gpt" - "github.com/asrul/linux-command-gpt/reader" "github.com/atotto/clipboard" + "github.com/direct-dev-ru/linux-command-gpt/gpt" + "github.com/direct-dev-ru/linux-command-gpt/reader" ) -const ( - HOST = "https://api.openai.com/v1/" - COMPLETIONS = "chat/completions" - MODEL = "gpt-4o-mini" - PROMPT = "Reply with linux command and nothing else. No need explanation. No need code blocks" +//go:embed VERSION.txt +var Version string + +var cwd, _ = os.Getwd() + +var ( + HOST = getEnv("LCG_HOST", "http://192.168.87.108:11434/") + COMPLETIONS = getEnv("LCG_COMPLETIONS_PATH", "api/chat") // relative part of endpoint + MODEL = getEnv("LCG_MODEL", "codegeex4") + PROMPT = getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks. No need ` symbols.") + API_KEY_FILE = getEnv("LCG_API_KEY_FILE", ".openai_api_key") + RESULT_FOLDER = getEnv("LCG_RESULT_FOLDER", path.Join(cwd, "gpt_results")) + + // HOST = "https://api.openai.com/v1/" + // COMPLETIONS = "chat/completions" + + // MODEL = "gpt-4o-mini" + // MODEL = "codellama:13b" // This file is created in the user's home directory // Example: /home/username/.openai_api_key - API_KEY_FILE = ".openai_api_key" + // API_KEY_FILE = ".openai_api_key" HELP = ` @@ -37,7 +52,7 @@ Example Usage: lcg I want to extract linux-command-gpt.tar.gz file Example Usage: lcg --file /path/to/file.json I want to print object questions with jq ` - VERSION = "0.2.1" + VERSION = Version CMD_HELP = 100 CMD_VERSION = 101 CMD_UPDATE = 102 @@ -45,6 +60,14 @@ Example Usage: lcg --file /path/to/file.json I want to print object questions wi CMD_COMPLETION = 110 ) +// getEnv retrieves the value of the environment variable `key` or returns `defaultValue` if not set. +func getEnv(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue +} + func handleCommand(cmd string) int { if cmd == "" || cmd == "--help" || cmd == "-h" { return CMD_HELP @@ -118,6 +141,10 @@ func main() { } } + if _, err := os.Stat(RESULT_FOLDER); os.IsNotExist(err) { + os.MkdirAll(RESULT_FOLDER, 0755) + } + h := handleCommand(cmd) if h == CMD_HELP { @@ -157,10 +184,10 @@ func main() { c = "N" fmt.Printf("Completed in %v seconds\n\n", elapsed) fmt.Println(r) - fmt.Print("\nDo you want to (c)opy, (r)egenerate, or take (N)o action on the command? (c/r/N): ") + fmt.Print("\nDo you want to (c)opy, (s)ave to file, (r)egenerate, or take (N)o action on the command? (c/r/N): ") fmt.Scanln(&c) - // No action + // no action if c == "N" || c == "n" { return } @@ -176,4 +203,14 @@ func main() { fmt.Println("\033[33mCopied to clipboard") return } + + if c == "S" || c == "s" { + timestamp := time.Now().Format("2006-01-02_15-04-05") // Format: YYYY-MM-DD_HH-MM-SS + filename := fmt.Sprintf("gpt_request_%s(%s).md", timestamp, gpt3.Model) + filePath := path.Join(RESULT_FOLDER, filename) + resultString := fmt.Sprintf("## Prompt:\n\n%s\n\n------------------\n\n## Response:\n\n%s\n\n", cmd+". "+gpt3.Prompt, r) + os.WriteFile(filePath, []byte(resultString), 0644) + fmt.Println("\033[33mSaved to file") + return + } } diff --git a/shell-code/build-full.sh b/shell-code/build-full.sh new file mode 100644 index 0000000..db2ee9a --- /dev/null +++ b/shell-code/build-full.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# REPO=registry.direct-dev.ru/go-lcg +REPO=kuznetcovay/go-lcg +VERSION=$1 +if [ -z "$VERSION" ]; then + VERSION=v1.0.1 +fi +BRANCH=main + +echo ${VERSION} > VERSION.txt + +export GOCACHE="${HOME}/.cache/go-build" + +# Save the current branch +CURRENT_BRANCH=$(git branch --show-current) + +# Function to restore the original branch +function restore_branch { + echo "Restoring original branch: ${CURRENT_BRANCH}" + git checkout "${CURRENT_BRANCH}" +} + +# Check if the current branch is different from the target branch +if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then + # Set a trap to restore the branch on exit + trap restore_branch EXIT + echo "Switching to branch: ${BRANCH}" + git checkout ${BRANCH} +fi + +# Fetch all tags from the remote repository +git fetch --tags + +# Check if the specified version tag exists +if git rev-parse "refs/tags/${VERSION}" >/dev/null 2>&1; then + echo "Tag ${VERSION} already exists. Halting script." + exit 1 +fi + +# Run go tests +# if ! go test -v -run=^Test; then +# echo "Tests failed. Exiting..." +# exit 1 +# fi +mkdir binaries-for-upload +# Build for linux/amd64 +docker build -f Dockerfiles/LocalCompile/Dockerfile --target bin-linux --output bin-linux-amd64/ --platform linux/amd64 . || + { + echo "docker build for amd64 failed. Exiting with code 1." + exit 1 + } + +cp bin-linux-amd64/lcg "binaries-for-upload/lcg.amd64.${VERSION}" + +# Build for linux/arm64 +docker build -f Dockerfiles/LocalCompile/Dockerfile --target bin-linux --output bin-linux-arm64/ --platform linux/arm64 . || + { + echo "docker build for arm64 failed. Exiting with code 1." + exit 1 + } + +cp bin-linux-arm64/lcg "binaries-for-upload/lcg.arm64.${VERSION}" + +# Push multi-platform images +docker buildx build -f Dockerfiles/ImageBuild/Dockerfile --push --platform linux/amd64,linux/arm64 -t ${REPO}:"${VERSION}" . || + { + echo "docker buildx build --push failed. Exiting with code 1." + exit 1 + } + +git add -A . || + { + echo "git add failed. Exiting with code 1." + exit 1 + } +git commit -m "release $VERSION" || + { + echo "git commit failed. Exiting with code 1." + exit 1 + } +git tag -a "$VERSION" -m "release $VERSION" || + { + echo "git tag failed. Exiting with code 1." + exit 1 + } +git push -u origin main --tags || + { + echo "git push failed. Exiting with code 1." + exit 1 + } + diff --git a/shell-code/build-short.sh b/shell-code/build-short.sh new file mode 100644 index 0000000..4b9ffe6 --- /dev/null +++ b/shell-code/build-short.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +docker build -f Dockerfiles/LocalCompile/Dockerfile --target bin-linux --output bin-linux-amd64/ --platform linux/amd64 . +docker build -f Dockerfiles/LocalCompile/Dockerfile --target bin-linux --output bin-linux-arm64/ --platform linux/arm64 . + +# in linux setuid +# sudo chown root:root bin-linux/go-ansible-vault +# sudo chmod +s bin-linux/go-ansible-vault \ No newline at end of file diff --git a/shell-code/build-to-dockerhub.sh b/shell-code/build-to-dockerhub.sh new file mode 100644 index 0000000..cf6a481 --- /dev/null +++ b/shell-code/build-to-dockerhub.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +REPO=kuznetcovay/go-ansible-vault +VERSION=$1 +if [ -z "$VERSION" ]; then + VERSION=v1.0.8 +fi +BRANCH=main + +echo ${VERSION} > VERSION.txt +export GOCACHE="${HOME}/.cache/go-build" + +# Save the current branch +CURRENT_BRANCH=$(git branch --show-current) + +# Function to restore the original branch +function restore_branch { + echo "Restoring original branch: ${CURRENT_BRANCH}" + git checkout "${CURRENT_BRANCH}" +} + +# Check if the current branch is different from the target branch +if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then + # Set a trap to restore the branch on exit + trap restore_branch EXIT + echo "Switching to branch: ${BRANCH}" + git checkout ${BRANCH} +fi + +# Run go tests +if ! go test -v -run=^Test; then + echo "Tests failed. Exiting..." + exit 1 +fi + +# Push multi-platform images +docker buildx build --push --platform linux/amd64,linux/arm64 -t ${REPO}:${VERSION} . || + { + echo "docker buildx build --push failed. Exiting with code 1." + exit 1 + } diff --git a/shell-code/upload-release.sh b/shell-code/upload-release.sh new file mode 100644 index 0000000..d5ff88d --- /dev/null +++ b/shell-code/upload-release.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Variables +VERSION_FILE="VERSION.txt" + +GITHUB_TOKEN="${GITHUB_TOKEN}" # Replace with your GitHub token + +REPO="Direct-Dev-Ru/binaries.git" # Replace with your GitHub username/repo + +TAG=go-ansible-vault.$(cat "$VERSION_FILE") + +echo $TAG + +RELEASE_NAME="Binaries ${TAG}" # Replace with your release title + +RELEASE_DIR="/home/su/projects/golang/ansible-vault/binaries-for-upload" + +# Create a new release +# response=$(curl -s -X POST \ +# -H "Authorization: Bearer ${GITHUB_TOKEN}" \ +# -H "Accept: application/vnd.github+json" \ +# -H "X-GitHub-Api-Version: 2022-11-28" \ +# https://api.github.com/repos/$REPO/releases \ +# -d "{\"tag_name\": \"$TAG\", \"name\": \"$RELEASE_NAME\"}") + +body="{\"tag_name\":\"${TAG}\", \"target_commitish\":\"main\", \"name\":\"${TAG}\", \ + \"body\":\"${TAG}\", \"draft\":false, \"prerelease\":false, \"generate_release_notes\":false}" + +echo $body + +response=$(curl -L -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/direct-dev-ru/binaries/releases \ + -d $body) + + +echo $response + +# Extract the upload URL from the response +upload_url=$(echo "$response" | jq -r '.upload_url' | sed "s/{?name,label}//") + +# Check if the release was created successfully +if [[ "$response" == *"Not Found"* ]]; then + echo "Error: Repository not found or invalid token." + exit 1 +fi + +# Upload each binary file +for file in "$RELEASE_DIR"/*; do + if [[ -f "$file" ]]; then + filename=$(basename "$file") + echo "Uploading $filename..." + response=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + "$upload_url?name=$filename" \ + --data-binary @"$file") + echo $response + fi +done + +echo "All binaries uploaded successfully."