From 83c3d1917797bab085d91260ad3a262005b769fb Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Thu, 9 May 2024 15:57:28 +0200 Subject: [PATCH 01/15] rename sh to rc and add comment how to source it in .bashrc/.zshrc --- mytotp.rc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mytotp.rc b/mytotp.rc index 88c4454..e2ea360 100644 --- a/mytotp.rc +++ b/mytotp.rc @@ -1,7 +1,10 @@ # this code is a fork of https://github.com/ThingCrimson/mytotp -# put this file in path and source it in .bashrc or .zshrc -# add next line to .bashrc or .zshrc -# source mytotp.sh +# put this file in ~ path and source it in .bashrc or .zshrc +# if [ -f $HOME/.mytotp.rc ]; then +# . $HOME/.mytotp.rc +# fi +# or add next line to .bashrc or .zshrc +# source /path/to/somewhere/mytotp.rc # create a directory for the keys ~/.config/mytotp # usage, mannually creating SERVID.gpg file: # Put TOTP key for service SERVID to GPG file crypted for '\''My TOTP'\'' From 57e0d4a3eef53c4984bfcbb7dccaa7d5e1fbe3fd Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Thu, 9 May 2024 17:26:18 +0200 Subject: [PATCH 02/15] chore: Update mytotp script to use .rc file extension and provide instructions for sourcing in .bashrc/.zshrc, remove duplicated declarations, and add xclip for initial key pasting to keyfile.asc (unsafe) --- mytotp.rc | 96 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/mytotp.rc b/mytotp.rc index e2ea360..3db0fc3 100644 --- a/mytotp.rc +++ b/mytotp.rc @@ -1,7 +1,7 @@ # this code is a fork of https://github.com/ThingCrimson/mytotp -# put this file in ~ path and source it in .bashrc or .zshrc -# if [ -f $HOME/.mytotp.rc ]; then -# . $HOME/.mytotp.rc +# put this file in ~ path and source it in .bashrc or .zshrc +# if [ -f $HOME/mytotp.rc ]; then +# . $HOME/mytotp.rc # fi # or add next line to .bashrc or .zshrc # source /path/to/somewhere/mytotp.rc @@ -15,31 +15,48 @@ # mytotpadd SERVID # usage, listing all SERVIDs: # mytotplist -function mytotp() { - if ! command -v oathtool &> /dev/null - then + +KEYDIR=~/.config/mytotp +KEYEXT=.gpg + +function mytotp() { + SERVID=$1 + if ! command -v oathtool &>/dev/null; then echo "oathtool could not be found" echo "Please install it with: brew install oath-toolkit" + echo "or check further https://launchpad.net/oath-toolkit/+packages && https://www.nongnu.org/oath-toolkit/" return 1 fi - KEYDIR=~/.config/mytotp - KEYEXT=.gpg - SERVID=$1 + if [ "$(uname)" == "Darwin" ]; then + PASTECOMMAND="pbpaste" + fi - if [ -z "${SERVID}" ] ; then + if [ "$(uname)" == "Linux" ]; then + if ! command -v xclip &>/dev/null; then + echo "xclip could not be found. It is an optional tool for reading the initial key from the clipboard." + echo "If you want to use this optional feature, please install it with: sudo apt-get install xclip" + read -p "Do you want to continue without xclip? (y/n) " yn + case $yn in + [Yy]*) ;; + [Nn]*) return 1 ;; + *) echo "Please answer yes or no." ;; + esac + fi + PASTECOMMAND="xclip -o" + fi + + if [ -z "${SERVID}" ]; then echo -e "Usage: $0 SERVID\n\tSERVID is a service ID, abbreviated, w/o ext:" find ${KEYDIR}/*${KEYEXT} | sed -e 's/\/home.*\// /; s/\.gpg//' return 2 fi - if [ ! -f "${KEYDIR}/${SERVID}${KEYEXT}" ] ; then + if [ ! -f "${KEYDIR}/${SERVID}${KEYEXT}" ]; then echo "No key for ${KEYDIR}/${SERVID}${KEYEXT}" return 1 fi - - SKEY=$(gpg -d --quiet "${KEYDIR}/${SERVID}${KEYEXT}") NOWS=$(date +'%S') @@ -50,7 +67,7 @@ function mytotp() { echo -n "Seconds :${NOWS} (we need to wait ${WAIT}) ... " sleep ${WAIT} - TOTP=$(echo "${SKEY}" | oathtool -b --totp - ) + TOTP=$(echo "${SKEY}" | oathtool -b --totp -) echo "${TOTP}" SKEY="none" @@ -60,22 +77,59 @@ function mytotp() { # add new SERVID to GPG file in ~/.config/mytotp/SERVID.gpg # paste the key in the prompt and press enter, then $SERVID.gpg will be created function mytotpadd() { + SERVID=$1 + # Check if "My TOTP" GPG key exists + if ! gpg --list-keys "My TOTP" >/dev/null 2>&1; then + echo "GPG key 'My TOTP' does not exist. Please create it first." + # ask user if they want to create the key + read -p "Do you want to create the key 'My TOTP' now ? (y/n) " yn + case $yn in + [Yy]*) + echo "Write and remember the password for 'My TOTP' gpg key in the next line:" + gpg --yes --batch --passphrase-fd 0 --quick-generate-key 'My TOTP' + ;; + [Nn]*) return 1 ;; + *) echo "Please answer yes or no." ;; + esac + echo "get back with further usage: mytotpadd " + return + fi + # if no $1 supplied, exit - if [ -z "$1" ] ; then + if [ -z "$1" ]; then echo -e "Usage: $0 SERVID\n\tSERVID is a service ID, abbreviated, w/o ext:" return 1 fi - KEYDIR=~/.config/mytotp - KEYEXT=.gpg - SERVID=$1 + # print user instruction about press control-D to stop gpg" echo "Paste the key in the prompt, press enter, and then press control-D to stop gpg" gpg -e -r "My TOTP" >~/.config/mytotp/$SERVID.gpg + # if you want paste again the key, this way it would be stored in ~/.config/mytotp/SERVIDS.keys.lst file + read -p "Do you want to store the initial service key in ~/.config/mytotp/${SERVID}.key.asc? warn: it is unsafe (y/n)" yn + case $yn in + # read from clipboard with xclip + [Yy]*) $PASTECOMMAND >>~/.config/mytotp/${SERVID}.key.asc ;; + [Nn]*) return ;; + *) echo "Please answer yes or no." ;; + esac } # function to list all SERVIDs in ~/.config/mytotp function mytotplist() { - KEYDIR=~/.config/mytotp - KEYEXT=.gpg - find ${KEYDIR}/*${KEYEXT} | sed -e 's/\/home.*\// /; s/\.gpg//' + if [ ! -d "${KEYDIR}" ]; then + read -p "Directory ${KEYDIR} does not exist. Do you want to create it? (y/n) " yn + case $yn in + [Yy]*) mkdir -p "${KEYDIR}" ;; + [Nn]*) return ;; + *) echo "Please answer yes or no." ;; + esac + fi + + ENTRIES=$(find ${KEYDIR}/*${KEYEXT} 2>/dev/null | sed -e 's/\/home.*\// /; s/\.gpg//') + + if [ -z "$ENTRIES" ]; then + echo "Warning: No SERVID entries found." + else + echo "$ENTRIES" + fi } From 33cf200d2cbe7023952263af8eae40c4e45db2ca Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Fri, 10 May 2024 19:16:11 +0200 Subject: [PATCH 03/15] chore: Update mytotp script to provide help, format readme file for mytotp.rc --- mytotp.rc | 66 ++++++++++++++++++++++++++++----------------- mytotp.rc.README.md | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 mytotp.rc.README.md diff --git a/mytotp.rc b/mytotp.rc index 3db0fc3..d133c78 100644 --- a/mytotp.rc +++ b/mytotp.rc @@ -1,4 +1,11 @@ +#!/bin/bash # this code is a fork of https://github.com/ThingCrimson/mytotp +# there are prerequisites to use this code: +# 1. gpg +# 2. oathtool +# 3. xclip (optional, for Linux only) or pbpaste (it is included in MacOS) +# 4. that for MacOS only bash 5.2.26, if you have older version that is default case for MacOS, please upgrade it with brew or use previous version of script, +# see this repo releases for MacOS standard bash # put this file in ~ path and source it in .bashrc or .zshrc # if [ -f $HOME/mytotp.rc ]; then # . $HOME/mytotp.rc @@ -15,12 +22,37 @@ # mytotpadd SERVID # usage, listing all SERVIDs: # mytotplist - KEYDIR=~/.config/mytotp KEYEXT=.gpg +if [ "$(uname)" == "Darwin" ]; then + PASTECOMMAND="pbpaste" + # make check of bash version, if it is even to 3.2.57 then show warning and instructions how to upgrade shell to bash 5.2.26 with brew, then exit with code 1 + if [ "$(echo $BASH_VERSION | awk -F. '{print $1}')" -eq 3 ] && [ "$(echo $BASH_VERSION | awk -F. '{print $2}')" -eq 2 ] && [ "$(echo $BASH_VERSION | awk -F. '{print $3}')" -eq 57 ]; then + echo "Your MacOS bash version is too old, please upgrade it to 5.2.26 with brew, following these steps" + echo "brew install bash" + echo "sudo ln -s /opt/homebrew/bin/bash /usr/local/bin/bash " + echo "sudo bash -c 'echo /usr/local/bin/bash >> /etc/shells'" + echo "optional step to change default zsh to bash, not really needed: chsh -s /usr/local/bin/bash" + return 1 + fi +fi +if [ "$(uname)" == "Linux" ]; then + if ! command -v xclip &>/dev/null; then + echo "xclip could not be found. It is an optional tool for reading the initial key from the clipboard." + echo "If you want to use this optional feature, please install it with: sudo apt-get install xclip" + read -p "Do you want to continue without xclip? (y/n) " yn + case $yn in + [Yy]*) ;; + [Nn]*) return 1 ;; + *) echo "Please answer yes or no." ;; + esac + fi + PASTECOMMAND="xclip -o" +fi function mytotp() { SERVID=$1 + if ! command -v oathtool &>/dev/null; then echo "oathtool could not be found" echo "Please install it with: brew install oath-toolkit" @@ -28,28 +60,11 @@ function mytotp() { return 1 fi - if [ "$(uname)" == "Darwin" ]; then - PASTECOMMAND="pbpaste" - fi - - if [ "$(uname)" == "Linux" ]; then - if ! command -v xclip &>/dev/null; then - echo "xclip could not be found. It is an optional tool for reading the initial key from the clipboard." - echo "If you want to use this optional feature, please install it with: sudo apt-get install xclip" - read -p "Do you want to continue without xclip? (y/n) " yn - case $yn in - [Yy]*) ;; - [Nn]*) return 1 ;; - *) echo "Please answer yes or no." ;; - esac - fi - PASTECOMMAND="xclip -o" - fi - - if [ -z "${SERVID}" ]; then - echo -e "Usage: $0 SERVID\n\tSERVID is a service ID, abbreviated, w/o ext:" - find ${KEYDIR}/*${KEYEXT} | sed -e 's/\/home.*\// /; s/\.gpg//' - return 2 + if [ -z "$1" ]; then + echo "mytotp version 1.0.0.rc" + echo "Usage: mytotp SERVID" + echo "SERVID is a service ID, abbreviated, that you provided for mytotpadd before, check all with mytotplist command" + return 1 fi if [ ! -f "${KEYDIR}/${SERVID}${KEYEXT}" ]; then @@ -105,9 +120,10 @@ function mytotpadd() { echo "Paste the key in the prompt, press enter, and then press control-D to stop gpg" gpg -e -r "My TOTP" >~/.config/mytotp/$SERVID.gpg # if you want paste again the key, this way it would be stored in ~/.config/mytotp/SERVIDS.keys.lst file - read -p "Do you want to store the initial service key in ~/.config/mytotp/${SERVID}.key.asc? warn: it is unsafe (y/n)" yn - case $yn in # read from clipboard with xclip + echo "Do you want to store the initial service key in .key.asc? Warn: it is unsafe (y/n) " + read -p "y/n " byn + case $byn in [Yy]*) $PASTECOMMAND >>~/.config/mytotp/${SERVID}.key.asc ;; [Nn]*) return ;; *) echo "Please answer yes or no." ;; diff --git a/mytotp.rc.README.md b/mytotp.rc.README.md new file mode 100644 index 0000000..f7c5d68 --- /dev/null +++ b/mytotp.rc.README.md @@ -0,0 +1,50 @@ +# mytotp.rc + +TOTP 2FA without smartphone + +Simple replacement of TOTP Authenticator mobile app for POSIX console (using `oathtool`). + +Now in two versions: plain BASH script and BASH functions for sourcing. + +This readme file is about `mytotp.rc` version, if you want `mytotp.sh` then skip to [main README.md](./README.md) + +## Prerequisits + +POSIX-compliant console, with BASH, GNU utils, GNU Privacy Guard and `oathtool` available. The latest script version require MacOS bash be upgraded to modern version. see bellow for details. If you dont want to upgrade standard MacOS bash, then use previos ugly script version from [releases here](https://github.com/uyriq/mytotp_as_bashfuncs/releases/tag/0.0.1.rc) +for latest scipt then we need xclip (optional, for Linux only) or pbpaste (it is included in MacOS by default) +Note, that installed `brew` is required for MacOS, if you want to install modern bash. after `brew install bash` make symlink `sudo ln -s /opt/homebrew/bin/bash /usr/local/bin/bash` then you do not need to `chsh` your shell from zsh to bash, it is more convinient to use subshell with call of `/usr/local/bin/bash` and keep zsh as default + +## Instalation + +Put this file in ~ path and source it in .bashrc or .zshrc +like this + +```bash + if [ -f $HOME/mytotp.rc ]; then + . $HOME/mytotp.rc + fi +``` + +or just add next line to .bashrc or .zshrc +`source /path/to/somewhere/isplaced/mytotp.rc` + +MacOS users with default zsh should start modern bash calling `/usr/local/bin/bash` then do `source /path/to/somewhere/isplaced/mytotp.rc` + +Create a direcrory for TOTP keys `mkdir -p ~/.config/mytotp` it can be done with `mytotplist` command as well + +If you have no suitable GPG key for encrypting of TOTP keys, create it +`gpg --yes --batch --passphrase-fd 0 --quick-generate-key 'My TOTP'` +enter your passphrase, (rememeber it) hit Enter, check keys with +`gpg --list-secret-keys | grep "My TOTP" ` you should view `[ultimate] My TOTP` + +## Usage + +To use these commands, follow these steps: + +1. **List Services with `mytotplist`**: This command lists all the services that have been added to the TOTP generator. It reads the GPG files in the `~/.config/mytotp/` directory and prints the service IDs. If the `~/.config/mytotp` directory does not exist, the function will prompt the user to create it. + +2. **Add a Service with `mytotpadd SERVID`**: When you activate 2FA on a service (let's call it SERVID), you will receive a TOTP key string. Copy this string, then run `mytotpadd SERVID`. This command adds a new service to the TOTP generator. You will be prompted to enter the service's initial secret key. This key is usually displayed as a QR code, but we need the alphanumeric form of this initial secret code. The service and its secret key will be stored in a GPG file in the `~/.config/mytotp/` directory. Optionally, the initial secret can be stored in a plain text file in the same directory. However, this is a development option and is not recommended for daily use due to security concerns. After running `mytotpadd SERVID`, paste the initial code string, then press Enter and Ctrl+D. + +3. **Generate a TOTP with `mytotp SERVID`**: This command generates a Time-based One-Time Password (TOTP) for the specified service ID (SERVID). The output is a standard 6-digit TOTP code, which may not be compatible with other TOTP formats (e.g., Yandex TOTP). If no service ID is provided, the command will print usage help and version information. The `mytotp` function is identical to the standalone `mytotp.sh` script. For more information, see the [main README.md](./README.md). + +To get a TOTP code for a service, run `mytotp.sh SERVID`, unlock your GPG key with the passphrase, then wait for the 6-digit code. The script will wait for the next 30-second interval before generating the code, giving you a maximum of 30 seconds to use it. From ee687fde839ba5e149da0454472736d2b6eb1337 Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:20:21 +0100 Subject: [PATCH 04/15] - Added a new function `mytotpdel` to delete SERVID. - Implemented autocompletion for both Bash and PowerShell functions `mytotp` and `mytotpdel`. - Refactored the script to include a PowerShell version. - Added the release workflow to PUBLISH RELEASE - fancyfied readme with badges --- .github/workflows/release.yml | 99 ++++++++++++++++++ mytotp.rc | 37 ++++++- mytotp.rc.README.md | 37 ++++++- mytotp.rc.ps1 | 186 ++++++++++++++++++++++++++++++++++ 4 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 mytotp.rc.ps1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7de6b90 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,99 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + +jobs: + release: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get previous tag + id: get_previous_tag + run: | + # Get all tags sorted by creation date in reverse order + $tags = git tag --sort=-creatordate + # Convert the tags to an array + $tagsArray = $tags -split "`n" + # Get the second latest tag + $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "" } + # Write previous tag to environment file + Write-Host "Previous tag: $prevTag" + echo "PREV_TAG=$prevTag" >> $env:GITHUB_ENV + shell: pwsh + + - name: Create release notes + id: create_release_notes + run: | + echo "## Release Notes" > release_notes.md + echo "" >> release_notes.md + echo "### Changes in this release:" >> release_notes.md + echo "" >> release_notes.md + $PREV_TAG = $env:PREV_TAG + Write-Host "Previous tag: $PREV_TAG" + if ([string]::IsNullOrEmpty($PREV_TAG)) { + Write-Host "No previous tag found. Exiting." + exit 1 + } + git log --oneline $PREV_TAG..HEAD >> release_notes.md + echo "" >> release_notes.md + + shell: pwsh + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body_path: ./release_notes.md + draft: false + prerelease: false + + - name: Upload PowerShell script + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./mytotp.rc.ps1 + asset_name: mytotp.rc.ps1 + asset_content_type: text/plain + + - name: Upload .rc Bash script + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./mytotp.rc + asset_name: mytotp.rc + asset_content_type: text/plain + + - name: Upload standalone Bash script + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./mytotp.sh + asset_name: mytotp.sh + asset_content_type: text/plain + + - name: Upload README.md + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./mytotp.rc.README.md + asset_name: README.md + asset_content_type: text/markdown diff --git a/mytotp.rc b/mytotp.rc index d133c78..ff52e52 100644 --- a/mytotp.rc +++ b/mytotp.rc @@ -61,7 +61,7 @@ function mytotp() { fi if [ -z "$1" ]; then - echo "mytotp version 1.0.0.rc" + echo "mytotp version 1.0.1.rc" echo "Usage: mytotp SERVID" echo "SERVID is a service ID, abbreviated, that you provided for mytotpadd before, check all with mytotplist command" return 1 @@ -130,6 +130,20 @@ function mytotpadd() { esac } +function mytotpdel() { + SERVID=$1 + if [ -z "$1" ]; then + echo -e "Usage: $0 SERVID\n\tSERVID is a service ID, abbreviated, w/o ext:" + return 1 + fi + if [ -f "${KEYDIR}/${SERVID}${KEYEXT}" ]; then + rm -f "${KEYDIR}/${SERVID}${KEYEXT}" + echo "Key for ${SERVID} deleted." + else + echo "No key for ${SERVID}" + fi +} + # function to list all SERVIDs in ~/.config/mytotp function mytotplist() { if [ ! -d "${KEYDIR}" ]; then @@ -149,3 +163,24 @@ function mytotplist() { echo "$ENTRIES" fi } + +# Function to get the list of .gpg files in $KEYDIR +get_gpg_files() { + find "$KEYDIR" -name "*$KEYEXT" -type f 2>/dev/null | sed -e "s|$KEYDIR/||" -e "s|$KEYEXT||" +} + +# Register argument completer for mytotpdel function +_mytotpdel_completions() { + local cur_word="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=( $(compgen -W "$(get_gpg_files)" -- "$cur_word") ) +} + +# Register argument completer for mytotp function +_mytotp_completions() { + local cur_word="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=( $(compgen -W "$(get_gpg_files)" -- "$cur_word") ) +} + +# Register the completion functions +complete -F _mytotpdel_completions mytotpdel +complete -F _mytotp_completions mytotp \ No newline at end of file diff --git a/mytotp.rc.README.md b/mytotp.rc.README.md index f7c5d68..aafbdfd 100644 --- a/mytotp.rc.README.md +++ b/mytotp.rc.README.md @@ -1,18 +1,47 @@ # mytotp.rc +[![Release](https://github.com/uyriq/mytotp_as_bashfuncs/actions/workflows/release.yml/badge.svg)](https://github.com/uyriq/mytotp_as_bashfuncs/actions/workflows/release.yml) +![Bash](https://img.shields.io/badge/shell-bash-blue.svg) +![PowerShell](https://img.shields.io/badge/shell-powershell-blue.svg) +![macOS](https://img.shields.io/badge/os-macOS-green.svg) +![Linux](https://img.shields.io/badge/os-linux-green.svg) +![Windows](https://img.shields.io/badge/os-windows-green.svg) + TOTP 2FA without smartphone Simple replacement of TOTP Authenticator mobile app for POSIX console (using `oathtool`). -Now in two versions: plain BASH script and BASH functions for sourcing. +Now in different versions: plain BASH script and BASH functions for sourcing. +Also the powershell core (pwsh) version for sourcing and use as cmdlets the same way This readme file is about `mytotp.rc` version, if you want `mytotp.sh` then skip to [main README.md](./README.md) ## Prerequisits -POSIX-compliant console, with BASH, GNU utils, GNU Privacy Guard and `oathtool` available. The latest script version require MacOS bash be upgraded to modern version. see bellow for details. If you dont want to upgrade standard MacOS bash, then use previos ugly script version from [releases here](https://github.com/uyriq/mytotp_as_bashfuncs/releases/tag/0.0.1.rc) +POSIX-compliant console, with BASH or PWSH, GNU utils, GNU Privacy Guard and `oathtool` available. The latest script version require MacOS bash be upgraded to modern version. see bellow for details. If you dont want to upgrade standard MacOS bash, then use previos ugly script version from [releases here](https://github.com/uyriq/mytotp_as_bashfuncs/releases/tag/0.0.1.rc) for latest scipt then we need xclip (optional, for Linux only) or pbpaste (it is included in MacOS by default) Note, that installed `brew` is required for MacOS, if you want to install modern bash. after `brew install bash` make symlink `sudo ln -s /opt/homebrew/bin/bash /usr/local/bin/bash` then you do not need to `chsh` your shell from zsh to bash, it is more convinient to use subshell with call of `/usr/local/bin/bash` and keep zsh as default +Another way is to use with pwsh (MS Powershell core) + +Once Homebrew is installed, open your terminal and run the following command to install PowerShell Core: + +`brew install --cask powershell` + +Create a symbolic link: After installing PowerShell Core, create a symbolic link to make it easier to run pwsh from the terminal: + +`sudo ln -s /usr/local/bin/pwsh /usr/local/bin/pwsh` + +Add PowerShell Core to the list of valid login shells: To allow PowerShell Core to be used as a login shell, add it to the list of valid shells: + +`sudo bash -c 'echo /usr/local/bin/pwsh >> /etc/shells'` + +Source mytotp.rc in PowerShell Core: To use the mytotp.rc functions in PowerShell Core, source the mytotp.rc file in your PowerShell profile. Open your PowerShell profile file (e.g., ~/.config/powershell/Microsoft.PowerShell_profile.ps1) and add the following line: + +`. $HOME/mytotp.rc.ps1` + +This will load the mytotp.rc functions whenever you start a new PowerShell Core session. + +By following these steps, you will have PowerShell Core installed and configured on your macOS system, and you will be able to use the mytotp.rc functions in PowerShell Core. ## Instalation @@ -48,3 +77,7 @@ To use these commands, follow these steps: 3. **Generate a TOTP with `mytotp SERVID`**: This command generates a Time-based One-Time Password (TOTP) for the specified service ID (SERVID). The output is a standard 6-digit TOTP code, which may not be compatible with other TOTP formats (e.g., Yandex TOTP). If no service ID is provided, the command will print usage help and version information. The `mytotp` function is identical to the standalone `mytotp.sh` script. For more information, see the [main README.md](./README.md). To get a TOTP code for a service, run `mytotp.sh SERVID`, unlock your GPG key with the passphrase, then wait for the 6-digit code. The script will wait for the next 30-second interval before generating the code, giving you a maximum of 30 seconds to use it. + +4. **Delete an existing TOTP with `mytotpdel SERVID`**: This command delete gpg file, so remove SERVID. + +after typing `mytotp ` or `mytotpdel ` press space and twice tab key to see autocompletion that list your existing SERVIDs (gpg filese in key folder) diff --git a/mytotp.rc.ps1 b/mytotp.rc.ps1 new file mode 100644 index 0000000..4b684a0 --- /dev/null +++ b/mytotp.rc.ps1 @@ -0,0 +1,186 @@ +#!/usr/bin/env pwsh + +# mytotp.rc.ps1 + +# before all we check if there is installed pwsh core, if not we exit with message about + +$isPwshInstalled = Get-Command pwsh -ErrorAction SilentlyContinue +if (-not $isPwshInstalled) { + Write-Host "PowerShell Core is not installed. Please install it first." + Write-Host "You can install it with: brew install --cask powershell, see more on site https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos" + Write-Host "Then you can run this script again." + exit 1 +} +# Define constants +$KEYDIR = "$HOME/.config/mytotp" +$KEYEXT = ".gpg" + +# Check the operating system and set the paste command +if ($IsMacOS) { + $PASTECOMMAND = "pbpaste" + $pwshVersion = $PSVersionTable.PSVersion + if ($pwshVersion.Major -eq 6 -and $pwshVersion.Minor -eq 0 -and $pwshVersion.Build -lt 2) { + Write-Host "Your MacOS PowerShell Core version is too old, please upgrade it to 7.0.3 with brew, following these steps" + Write-Host "brew install --cask powershell" + Write-Host "sudo ln -s /usr/local/bin/pwsh /usr/local/bin/pwsh" + Write-Host "sudo bash -c 'echo /usr/local/bin/pwsh >> /etc/shells'" + Write-Host "optional step to change default shell to pwsh, not really needed: chsh -s /usr/local/bin/pwsh" + exit 1 + } +} elseif ($IsLinux) { + if (-not (Get-Command xclip -ErrorAction SilentlyContinue)) { + Write-Host "xclip could not be found. It is an optional tool for reading the initial key from the clipboard." + Write-Host "If you want to use this optional feature, please install it with: sudo apt-get install xclip" + $yn = Read-Host "Do you want to continue without xclip? (y/n)" + if ($yn -ne 'y') { exit 1 } + } + $PASTECOMMAND = "xclip -o" +} + +function mytotp { + param ( + [string]$SERVID + ) + + if (-not (Get-Command oathtool -ErrorAction SilentlyContinue)) { + Write-Host "oathtool could not be found" + Write-Host "Please install it with: brew install oath-toolkit" + Write-Host "or check further https://launchpad.net/oath-toolkit/+packages && https://www.nongnu.org/oath-toolkit/" + return 1 + } + + if (-not $SERVID) { + Write-Host "mytotp version 1.0.1.rc" + Write-Host "Usage: mytotp SERVID" + Write-Host "SERVID is a service ID, abbreviated, that you provided for mytotpadd before, check all with mytotplist command" + return 1 + } + + $keyFile = "$KEYDIR/$SERVID$KEYEXT" + if (-not (Test-Path $keyFile)) { + Write-Host "No key for $keyFile" + return 1 + } + + $SKEY = gpg -d --quiet $keyFile + + $NOWS = (Get-Date).Second + $WAIT = 60 - $NOWS + if ($WAIT -gt 30) { + $WAIT -= 30 + } + Write-Host "Seconds :$NOWS (we need to wait $WAIT) ... " + Start-Sleep -Seconds $WAIT + + $TOTP = $SKEY | oathtool -b --totp - + Write-Host $TOTP + $SKEY = "none" + return 0 +} + +function mytotpdel { + param ( + [string]$SERVID + ) + + if (-not $SERVID) { + Write-Host "Usage: mytotpdel SERVID" + Write-Host "SERVID is a service ID, abbreviated, w/o ext:" + return 1 + } + + $keyFile = "$KEYDIR/$SERVID$KEYEXT" + if (-not (Test-Path $keyFile)) { + Write-Host "No key for $keyFile" + return 1 + } + + Remove-Item $keyFile + Write-Host "Key for $SERVID deleted." +} + +function mytotpadd { + param ( + [string]$SERVID + ) + + if (-not (gpg --list-keys "My TOTP" -ErrorAction SilentlyContinue)) { + Write-Host "GPG key 'My TOTP' does not exist. Please create it first." + $yn = Read-Host "Do you want to create the key 'My TOTP' now ? (y/n)" + if ($yn -eq 'y') { + Write-Host "Write and remember the password for 'My TOTP' gpg key in the next line:" + gpg --yes --batch --passphrase-fd 0 --quick-generate-key 'My TOTP' + } else { + return 1 + } + Write-Host "get back with further usage: mytotpadd " + return + } + + if (-not $SERVID) { + Write-Host "Usage: mytotpadd SERVID" + Write-Host "SERVID is a service ID, abbreviated, w/o ext:" + return 1 + } + + Write-Host "Paste the key in the prompt, press enter, and then press control-D to stop gpg" + gpg -e -r "My TOTP" > "$KEYDIR/$SERVID$KEYEXT" + + $byn = Read-Host "Do you want to store the initial service key in .key.asc? Warn: it is unsafe (y/n)" + if ($byn -eq 'y') { + & $PASTECOMMAND >> "$KEYDIR/$SERVID.key.asc" + } +} + +function mytotplist { + if (-not (Test-Path $KEYDIR)) { + $yn = Read-Host "Directory $KEYDIR does not exist. Do you want to create it? (y/n)" + if ($yn -eq 'y') { + New-Item -ItemType Directory -Path $KEYDIR + } else { + return + } + } + + $ENTRIES = Get-ChildItem "$KEYDIR/*$KEYEXT" -ErrorAction SilentlyContinue | ForEach-Object { + $_.Name -replace '\.gpg$', '' + } + + if (-not $ENTRIES) { + Write-Host "Warning: No SERVID entries found." + } else { + Write-Host $ENTRIES + } +} + +# Function to get the list of .gpg files in $KEYDIR +function Get-GpgFiles { + $files = Get-ChildItem -Path $KEYDIR -Filter "*$KEYEXT" -ErrorAction SilentlyContinue + if ($files) { + $files | ForEach-Object { $_.BaseName } + } else { + @() # Return an empty array if no files are found + } +} + +# Register argument completer for mytotpdel function +Register-ArgumentCompleter -Native -CommandName mytotpdel -ParameterName SERVID -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + $gpgFiles = Get-GpgFiles + if ($gpgFiles) { + $gpgFiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } +} + +# Register argument completer for mytotp function (for SERVID parameter) just the same as totpdel +Register-ArgumentCompleter -Native -CommandName mytotp -ParameterName SERVID -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + $gpgFiles = Get-GpgFiles + if ($gpgFiles) { + $gpgFiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } +} \ No newline at end of file From 7630a3239897868ca5edce1da7a5f9a846cc56f9 Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:20:23 +0100 Subject: [PATCH 05/15] - Added a new function `mytotpdel` to delete SERVID. - Implemented autocompletion for both Bash and PowerShell functions `mytotp` and `mytotpdel`. - Refactored the script to include a PowerShell version. - Added the release workflow to PUBLISH RELEASE - fancyfied readme with badges From 1aba4646ab830f48fc744e0e93928a0022fb32bf Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:36:10 +0100 Subject: [PATCH 06/15] fix: release.yml --- .github/workflows/release.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7de6b90..dc670d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,12 @@ jobs: $tagsArray = $tags -split "`n" # Get the second latest tag $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "" } + + if (-not $prevTag) { + # No previous tag found, fallback to the last commit hash + $prevTag = git log -1 --pretty=format:"%H" + } + # Write previous tag to environment file Write-Host "Previous tag: $prevTag" echo "PREV_TAG=$prevTag" >> $env:GITHUB_ENV @@ -68,7 +74,7 @@ jobs: asset_name: mytotp.rc.ps1 asset_content_type: text/plain - - name: Upload .rc Bash script + - name: Upload Bash script uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} @@ -78,16 +84,6 @@ jobs: asset_name: mytotp.rc asset_content_type: text/plain - - name: Upload standalone Bash script - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./mytotp.sh - asset_name: mytotp.sh - asset_content_type: text/plain - - name: Upload README.md uses: actions/upload-release-asset@v1 env: From a848270e04b52730c6fc1e7cf7388ef92372da84 Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:16:53 +0100 Subject: [PATCH 07/15] chore: some in yml --- .github/workflows/release.yml | 19 +++++++++++-------- release_notes.md | 0 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 release_notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc670d2..1cd93d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: jobs: release: runs-on: windows-latest + permissions: + contents: write steps: - name: Checkout code @@ -16,6 +18,7 @@ jobs: - name: Get previous tag id: get_previous_tag + shell: pwsh run: | # Get all tags sorted by creation date in reverse order $tags = git tag --sort=-creatordate @@ -25,17 +28,17 @@ jobs: $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "" } if (-not $prevTag) { - # No previous tag found, fallback to the last commit hash - $prevTag = git log -1 --pretty=format:"%H" + # will do stub + $prevTag = "0.0.1.rc" } # Write previous tag to environment file Write-Host "Previous tag: $prevTag" echo "PREV_TAG=$prevTag" >> $env:GITHUB_ENV - shell: pwsh - name: Create release notes id: create_release_notes + shell: pwsh run: | echo "## Release Notes" > release_notes.md echo "" >> release_notes.md @@ -44,13 +47,13 @@ jobs: $PREV_TAG = $env:PREV_TAG Write-Host "Previous tag: $PREV_TAG" if ([string]::IsNullOrEmpty($PREV_TAG)) { - Write-Host "No previous tag found. Exiting." - exit 1 + Write-Host "No previous tag found. Using fallback." + git log --pretty=format:%s ${GITHUB_REF}~2..${GITHUB_REF} >> release_notes.md + } else { + git log --pretty=format:%s $PREV_TAG..${GITHUB_REF} >> release_notes.md } - git log --oneline $PREV_TAG..HEAD >> release_notes.md - echo "" >> release_notes.md - shell: pwsh + echo "" >> release_notes.md - name: Create GitHub Release id: create_release diff --git a/release_notes.md b/release_notes.md new file mode 100644 index 0000000..e69de29 From 5d04c1ff25dd0c8f57e984c724966d879baa9ace Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:52:36 +0100 Subject: [PATCH 08/15] Update previous tag in release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cd93d2..23a005e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: if (-not $prevTag) { # will do stub - $prevTag = "0.0.1.rc" + $prevTag = "v1.2.0" } # Write previous tag to environment file From 657671f8d34ac418f5e8557f64293558f8cf5131 Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:00:33 +0100 Subject: [PATCH 09/15] Update release.yml to fetch all history including tags during code checkout --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23a005e..c94520c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history including tags - name: Get previous tag id: get_previous_tag From b0c0dcc68ff740070749754c4444226a89640175 Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:08:14 +0100 Subject: [PATCH 10/15] Fix release.yml to correctly fetch all history including tags during code checkout --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c94520c..5feb228 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: if (-not $prevTag) { # will do stub - $prevTag = "v1.2.0" + $prevTag = "'v1.2.0'" } # Write previous tag to environment file @@ -50,9 +50,9 @@ jobs: Write-Host "Previous tag: $PREV_TAG" if ([string]::IsNullOrEmpty($PREV_TAG)) { Write-Host "No previous tag found. Using fallback." - git log --pretty=format:%s ${GITHUB_REF}~2..${GITHUB_REF} >> release_notes.md + git log --pretty=format:%s "${GITHUB_REF}~2..${GITHUB_REF}" >> release_notes.md } else { - git log --pretty=format:%s $PREV_TAG..${GITHUB_REF} >> release_notes.md + git log --pretty=format:%s "$PREV_TAG..${GITHUB_REF}" >> release_notes.md } echo "" >> release_notes.md From 5a08b15ae4d282a3df2b684855e54e34324d2407 Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:22:00 +0100 Subject: [PATCH 11/15] Fix release.yml to correctly fetch all history including tags during code checkout --- .github/workflows/release.yml | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5feb228..2262bb2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,19 +22,9 @@ jobs: id: get_previous_tag shell: pwsh run: | - # Get all tags sorted by creation date in reverse order $tags = git tag --sort=-creatordate - # Convert the tags to an array $tagsArray = $tags -split "`n" - # Get the second latest tag - $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "" } - - if (-not $prevTag) { - # will do stub - $prevTag = "'v1.2.0'" - } - - # Write previous tag to environment file + $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "'v1.2.0'" } Write-Host "Previous tag: $prevTag" echo "PREV_TAG=$prevTag" >> $env:GITHUB_ENV @@ -50,24 +40,30 @@ jobs: Write-Host "Previous tag: $PREV_TAG" if ([string]::IsNullOrEmpty($PREV_TAG)) { Write-Host "No previous tag found. Using fallback." - git log --pretty=format:%s "${GITHUB_REF}~2..${GITHUB_REF}" >> release_notes.md + git log --pretty=format:%s "${GITHUB_REF}~2..${GITHUB_REF}" >> release_notes.md } else { git log --pretty=format:%s "$PREV_TAG..${GITHUB_REF}" >> release_notes.md } - echo "" >> release_notes.md + - name: Extract tag name + id: extract_tag_name + shell: pwsh + run: | + $tagName = "${GITHUB_REF#refs/tags/}" + Write-Host "Tag name: $tagName" + echo "TAG_NAME=$tagName" >> $env:GITHUB_ENV + - name: Create GitHub Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} + tag_name: ${{ env.TAG_NAME }} + release_name: Release ${{ env.TAG_NAME }} body_path: ./release_notes.md draft: false - prerelease: false - name: Upload PowerShell script uses: actions/upload-release-asset@v1 From 1ede871e00cd8e983fa964f3792252ac51ec3b07 Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:28:48 +0100 Subject: [PATCH 12/15] Fix release.yml to correctly fetch all history including tags during code checkout --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2262bb2..0bd07b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: run: | $tags = git tag --sort=-creatordate $tagsArray = $tags -split "`n" - $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "'v1.2.0'" } + $prevTag = if ($tagsArray.Length -ge 2) { $tagsArray[1].Trim() } else { "" } Write-Host "Previous tag: $prevTag" echo "PREV_TAG=$prevTag" >> $env:GITHUB_ENV @@ -50,7 +50,7 @@ jobs: id: extract_tag_name shell: pwsh run: | - $tagName = "${GITHUB_REF#refs/tags/}" + $tagName = $env:GITHUB_REF -replace 'refs/tags/', '' Write-Host "Tag name: $tagName" echo "TAG_NAME=$tagName" >> $env:GITHUB_ENV From 177e4c82b8958c73ae869f795cbb63a01b260deb Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:46:53 +0100 Subject: [PATCH 13/15] Fix release.yml to correctly fetch tag name during code checkout --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0bd07b1..4e0f5cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,11 @@ jobs: id: extract_tag_name shell: pwsh run: | - $tagName = $env:GITHUB_REF -replace 'refs/tags/', '' + if ($env:GITHUB_REF -match 'refs/tags/(.*)') { + $tagName = $matches[1] + } else { + $tagName = git describe --tags $(git rev-list --tags --max-count=1) + } Write-Host "Tag name: $tagName" echo "TAG_NAME=$tagName" >> $env:GITHUB_ENV From 7f82b6993b1e50e968d623bbe343d4b193eca8fa Mon Sep 17 00:00:00 2001 From: Yuri <105006434+uyriq@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:23:09 +0100 Subject: [PATCH 14/15] Update release.yml to include latest release notes in the generated release file --- .github/workflows/release.yml | 2 ++ release_notes.md => latest_release_notes.md | 0 2 files changed, 2 insertions(+) rename release_notes.md => latest_release_notes.md (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e0f5cf..d03521b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,8 @@ jobs: run: | echo "## Release Notes" > release_notes.md echo "" >> release_notes.md + Get-Content "./latest_release_notes.md" >> release_notes.md + echo "" >> release_notes.md echo "### Changes in this release:" >> release_notes.md echo "" >> release_notes.md $PREV_TAG = $env:PREV_TAG diff --git a/release_notes.md b/latest_release_notes.md similarity index 100% rename from release_notes.md rename to latest_release_notes.md From 361b4531faa9827e0be4f18bcfcfa9447356e3d9 Mon Sep 17 00:00:00 2001 From: uyriq <105006434+uyriq@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:55:14 +0100 Subject: [PATCH 15/15] Create dependabot.yml --- .github/dependabot.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..70fd8d8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# Basic set up for three package managers + +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.) + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + time: "17:00"