Last active
July 31, 2024 13:34
-
-
Save srathi-monarch/c61c24510fe8c199e14680774947568c to your computer and use it in GitHub Desktop.
Zsh config to be used on top of GRML zsh config. Includes ros, fzf.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
xmodmap -e "keysym Print = Menu" 2> /dev/null | |
# Setup Paths | |
export PATH="${PATH}:${HOME}/.local/bin:${HOME}/.cargo/bin" | |
export PATH=/usr/local/cuda-11.7/bin${PATH:+:${PATH}} | |
export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} | |
# export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}:${HOME}/.local/opt/glibc/glibc232/lib | |
export PATH=$PATH:/home/srathi/.spicetify | |
# Setup History | |
# HISTFILE="$HOME/.zsh_history" | |
export HISTSIZE=10000000 | |
export SAVEHIST=10000000 | |
# setopt BANG_HIST # Treat the '!' character specially during expansion. | |
setopt EXTENDED_HISTORY # Write the history file in the ":start:elapsed;command" format. | |
setopt INC_APPEND_HISTORY # Write to the history file immediately, not when the shell exits. | |
setopt SHARE_HISTORY # Share history between all sessions. | |
setopt HIST_EXPIRE_DUPS_FIRST # Expire duplicate entries first when trimming history. | |
setopt HIST_IGNORE_DUPS # Don't record an entry that was just recorded again. | |
setopt HIST_IGNORE_ALL_DUPS # Delete old recorded entry if new entry is a duplicate. | |
setopt HIST_FIND_NO_DUPS # Do not display a line previously found. | |
setopt HIST_IGNORE_SPACE # Don't record an entry starting with a space. | |
setopt HIST_SAVE_NO_DUPS # Don't write duplicate entries in the history file. | |
setopt HIST_REDUCE_BLANKS # Remove superfluous blanks before recording entry. | |
setopt HIST_VERIFY # Don't execute immediately upon history expansion. | |
# Alias and options for some common tools | |
alias fgrep='grep -F' | |
alias dff='df -hT -x tmpfs -x devtmpfs -x squashfs' | |
alias tmux='tmux -f $HOME/.config/tmux.conf' | |
export LESS="-r --mouse --quit-if-one-screen" | |
alias bat=batcat | |
alias gtree="tree -I \"$(grep -hvE '^$|^#' {,$(git rev-parse --show-toplevel 2>/dev/null)/}.gitignore $(git rev-parse --show-toplevel 2>/dev/null)/.git/info/exclude 2>/dev/null |sed 's:/$::'|tr \\n '\|')\"" | |
alias inhibit='systemd-inhibit --what=shutdown:sleep:idle:handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch --mode=block' | |
alias ..='cd ../' | |
alias ...='cd ../../' | |
alias ....='cd ../../../' | |
alias als='aws s3 ls --human-readable --summarize' | |
# Useful functions | |
fk () { "$@" 2>/dev/null >/dev/null &! } # Silently Fork off the process | |
compdef _command_names fk | |
mcd() { ( [[ -d "$@" ]] || mkdir "$@") && cd "$1" || exit } | |
compdef _mkdir mcd69:14 | |
alias tclip='tmux show-buffer | xclip -selection clipboard' | |
# Run this after kernel upgrades or if opencv cuda is not working | |
alias fix_nvidia='sudo rmmod nvidia_uvm && sudo modprobe nvidia_uvm' | |
# See also: ix.io, curl.io, chunk.io or https://news.ycombinator.com/item?id=11322007 | |
# transfer(){ if [ $# -eq 0 ];then echo "No arguments specified.\nUsage:\n transfer <file|directory>\n ... | transfer <file_name>">&2;return 1;fi;if tty -s;then file="$1";file_name=$(basename "$file");if [ ! -e "$file" ];then echo "$file: No such file or directory">&2;return 1;fi;if [ -d "$file" ];then file_name="$file_name.zip" ,;(cd "$file"&&zip -r -q - .)|curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null,;else cat "$file"|curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null;fi;else file_name=$1;curl --progress-bar --upload-file "-" "https://transfer.sh/$file_name"|tee /dev/null;fi;} | |
# VPN / Workstation / Tractor Related aliases | |
# alias is_vpn='networkctl 2>/dev/null | grep tun0' # This seems to cause problems with the new cloudflare stuff. DO NOT RUN. | |
# alias vpn_ip="ip addr show tun0 | grep -Po '(?<=inet )[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(?=/)'" # TODO: update for new cloudflare stuff | |
# alias workstation='sshpass -f ~/.ssh/pass_lol ssh $(is_vpn > /dev/null && echo -n "vpn_ws" || echo -n "ws")' | |
alias workstation='sshpass -f ~/.ssh/pass_lol ssh ws' | |
# Workstation and Tractor IP and Ports are moved to ~/.ssh/config | |
tssh() { | |
nmcli con up 'suraj_rathi_san_jose_(ca)' > /dev/null 2>&1 | |
# xargs trims whitespace | |
export LC_AWS_ACCESS_KEY_ID=$(grep -i aws_access_key_id ~/.aws/credentials | cut -d'=' -f 2 | xargs) | |
export LC_AWS_SECRET_ACCESS_KEY=$(grep -i aws_secret_access_key ~/.aws/credentials | cut -d'=' -f 2 | xargs) | |
export LC_SSH_KEY_PASSPHRASE=$(cat ~/.ssh/pass_tractor_key) | |
# LC_AWS_DEFAULT_REGION= | |
ssh -o "SendEnv LC_*" -t $@ '~/suraj_ws/.zdotdir/start_session.sh' | |
} | |
copy_keys_tractor() { | |
set +m | |
trap 'set -m' INT | |
for target in $(grep -Po '^Host[[:space:]]+\Kn(1|2)[A-Za-z0-9\.]*$' ~/.ssh/config); | |
do | |
echo -n "${target} " | |
{ | |
timeout -k 30 20 sshpass -f ~/.ssh/monarch_pass -- ssh-copy-id "${target}" &> /dev/null | |
echo "${target}: Done $?" | |
} & | |
done | |
echo | |
wait | |
set -m | |
} | |
gen_ssh_conf() { | |
if [[ $# -lt 1 || $# -gt 2 ]]; then | |
echo "Usage: ${0} <target> [--old]" | |
echo "Target should be of the form x[1-4]<tractor_name>" | |
echo "n1<tractor_name> should be in ~/.ssh/config" | |
echo "You should run `copy_keys_tractor` once to copy your publickey to the n1s and n2s." | |
return 1 | |
fi | |
target=$1 | |
if [[ -a ~/.ssh/tractor_confs/"${target}" ]]; then | |
echo "Config already exists" | |
return 0 | |
fi | |
# If the first two characters is not x{1-4} then error | |
if [[ ! "${target:0:2}" =~ ^x[1-4]$ ]]; then | |
echo "Invalid target" | |
return 1 | |
fi | |
dev="${target:0:2}" | |
if [[ $2 == "--old" || $2 == "-o" ]]; then | |
dev="${dev}old" | |
fi | |
tractor="${target:2:${#target}}" | |
for n1 in $(grep -Po '^Host[[:space:]]+\Kn1[A-Za-z0-9\.]*$' ~/.ssh/config); | |
do | |
if [[ "${n1:2:${#n1}}" == "${tractor}" ]]; then | |
if [[ ! -a ~/.ssh/tractor_keys/${tractor}/id_rsa || ! -a ~/.ssh/tractor_keys/${tractor}/id_rsa.pub ]]; then | |
echo "Fetching keys" | |
mkdir -p ~/.ssh/tractor_keys/"${tractor}"/ || { echo "Couldn't create keys directory for ${tractor}"; return 1 } | |
chmod go-rwx ~/.ssh/tractor_keys/"${tractor}"/ | |
scp "${n1}":.ssh/{id_rsa,id_rsa.pub} ~/.ssh/tractor_keys/"${tractor}" || { echo "Couldn't copy keys for ${tractor}"; return 1 } | |
echo "Copied keys for ${tractor}" | |
fi | |
echo "Trying to log into ${dev} via ${n1}" | |
ssh-keygen -f ~/.ssh/known_hosts -R "${dev}" | |
ssh -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" -- true || { echo "Could not ssh into ${dev}"; return 1 } | |
echo "Login successful, generating conf file for ${target}" | |
ssh -G -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" | | |
grep -E '^hostname |^port |^user |^identityfile |^proxyjump |^stricthostkeychecking ' | | |
awk "BEGIN {print \"Host ${target}\"} {print \" \"\$0}" > ~/.ssh/tractor_confs/"${target}" | |
# -o PreferredAuthentications=publickey -o StrictHostKeychecking=no | |
# ssh -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" | |
echo "Successfully created ~/.ssh/tractor_confs/${target}" | |
echo "You may now run \`ssh ${target}\`" | |
return 0 | |
fi | |
done | |
echo "Could not find an n1 for ${tractor}" | |
echo -n "Available tractors are: " | |
grep -Po '^Host[[:space:]]+\Kn1[A-Za-z0-9\.]*$' ~/.ssh/config | tr '\n' ' ' | |
echo | |
} | |
atssh() { | |
# ssh's into any device on a tractor who's n1 is in ssh config | |
# This loads the basic bash shell | |
# The n1's IdentityFile should be set in ~/.ssh/config | |
# We use the n1's IdentityFile to ssh into the the target device | |
if [[ $# -lt 1 || $# -gt 2 ]]; then | |
echo "Usage: ${0} <target> [--old]" | |
echo "Target should be of the form (n[1-2]|x[1-4])<tractor_name>" | |
echo "Omitting the tractor_name implies that you are on a tractor network." | |
echo "Both n1<tractor_name> and optionally n2<tractor_name> should be in ~/.ssh/config" | |
echo "You should run `copy_keys_tractor` once to copy your publickey to the n1s and n2s." | |
return 1 | |
fi | |
target=$1 | |
if [[ ${#target} -lt 2 ]]; then | |
echo "Invalid target" | |
return 1 | |
fi | |
if [[ -a ~/.ssh/tractor_confs/"${target}" ]]; then | |
ssh ${target} | |
return $? | |
fi | |
if [[ "${target:0:2}" =~ ^n[1-2]$ ]]; then | |
# When do we do tssh? | |
# Can we automate installing our tssh over here? | |
ssh -o StrictHostKeychecking=no ${target} | |
return 1 | |
fi | |
# If the first two characters is not x{1-4} then error | |
if [[ ! "${target:0:2}" =~ ^x[1-4]$ ]]; then | |
echo "Invalid target" | |
return 1 | |
fi | |
dev="${target:0:2}" | |
if [[ $2 == "--old" || $2 == "-o" ]]; then | |
dev="${dev}old" | |
fi | |
tractor="${target:2:${#target}}" | |
for n1 in $(grep -Po '^Host[[:space:]]+\Kn1[A-Za-z0-9\.]*$' ~/.ssh/config); | |
do | |
if [[ "${n1:2:${#n1}}" == "${tractor}" ]]; then | |
if [[ ! -a ~/.ssh/tractor_keys/${tractor}/id_rsa || ! -a ~/.ssh/tractor_keys/${tractor}/id_rsa.pub ]]; then | |
echo "Fetching keys" | |
mkdir -p ~/.ssh/tractor_keys/"${tractor}"/ || { echo "Couldn't create keys directory for ${tractor}"; return 1 } | |
chmod go-rwx ~/.ssh/tractor_keys/"${tractor}"/ | |
scp "${n1}":.ssh/{id_rsa,id_rsa.pub} ~/.ssh/tractor_keys/"${tractor}" || { echo "Couldn't copy keys for ${tractor}"; return 1 } | |
echo "Copied keys for ${tractor}" | |
fi | |
ssh -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" -- true || { echo "Could not ssh into ${dev}"; return 1 } | |
ssh -G -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" | | |
grep -E '^hostname |^port |^user |^identityfile |^proxyjump |^stricthostkeychecking ' | | |
awk "BEGIN {print \"Host ${target}\"} {print \" \"\$0}" > ~/.ssh/tractor_confs/"${target}" | |
# -o PreferredAuthentications=publickey -o StrictHostKeychecking=no | |
atssh ${target} | |
# ssh -o StrictHostKeychecking=no -J "${n1}" -i "~/.ssh/tractor_keys/${tractor}/id_rsa" "${dev}" | |
return $? | |
fi | |
done | |
echo "Could not find an n1 for ${tractor}" | |
echo -n "Available tractors are: " | |
grep -Po '^Host[[:space:]]+\Kn1[A-Za-z0-9\.]*$' ~/.ssh/config | tr '\n' ' ' | |
echo | |
} | |
# Alias and functions to eaily run clion and pycharm on directories and ROS packages | |
ide_wrapper() { | |
[[ $# -lt 1 ]] && return 1 | |
ide_cmd=$1 | |
shift | |
FORCE=0 | |
DIRNAME="./" | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
-f|--force) | |
FORCE=1 | |
shift | |
;; | |
*) | |
DIRNAME=$1 | |
shift | |
;; | |
esac | |
done | |
roscd $DIRNAME 2>&1 > /dev/null || cd $DIRNAME || return 1 | |
( [ -d ./.idea ] || [ $FORCE -gt 0 ] ) && eval "$ide_cmd" ./ || echo "No existing ide directory here, run as \"${funcstack[0]} -f\" to force creation." | |
cd - > /dev/null | |
} | |
# alias pycharm="fk ${HOME}/.local/share/JetBrains/Toolbox/apps/pycharm-community/bin/pycharm.sh" | |
alias pycharm="fk ${HOME}/.local/share/JetBrains/Toolbox/apps/pycharm-professional/bin/pycharm.sh" | |
alias clion="fk ${HOME}/.local/share/JetBrains/Toolbox/apps/clion/bin/clion.sh" | |
alias nova="fk ${HOME}/.local/share/JetBrains/Toolbox/apps/clion-nova/bin/clion.sh" | |
cc() { ide_wrapper clion $@ } | |
pp() { ide_wrapper pycharm $@ } | |
nn() { ide_wrapper nova $@ } | |
apt-history () { | |
case "$1" in | |
install) | |
cat /var/log/dpkg.log | grep 'install ' | |
;; | |
upgrade|remove) | |
cat /var/log/dpkg.log | grep $1 | |
;; | |
rollback) | |
cat /var/log/dpkg.log | grep upgrade | \ | |
grep "$2" -A10000000 | \ | |
grep "$3" -B10000000 | \ | |
awk '{print $4"="$5}' | |
;; | |
*) | |
cat /var/log/dpkg.log | |
;; | |
esac | |
} | |
# Setup ROS | |
export ROSCONSOLE_FORMAT='[${severity} ${time:%H:%M.%S}] ${node}: ${message}' # Second precision: | |
# export ROSCONSOLE_FORMAT='[${severity} ${time}] ${node}: ${message}' # Subsecond precision: | |
export ROSCONSOLE_CONFIG_FILE="${HOME}/.config/rosconsole.config" | |
alias catkin_build="catkin build" | |
export ROS_MASTER_URI="http://localhost:11311" | |
export ROS_HOSTNAME="localhost" | |
# Workspace Tools | |
compile_cmds_catkin_build() { # I think this exists in cc_setup.zsh, why is this here? | |
catkin build "$@" | |
cat $ROS_WS_DIR/.catkin_tools/CATKIN_BUILD_DUMMY.json $ROS_WS_DIR/build/*/compile_commands.json(N) $ROS_WS_DIR/.catkin_tools/CATKIN_BUILD_DUMMY.json | sed 's:\]\[:,:' > "${ROS_WS_DIR}/src/${CATKIN_PROFILE}/compile_commands.json" | |
} | |
source_ws() { # Isn't this all part of cc_setup.zsh now? Or should we replace that with the source_ws command. That will centralize that. | |
catkin locate | read -r WS_HOME && export ROS_WS_DIR="${WS_HOME}" | |
echo -ne "[\n]" > $ROS_WS_DIR/.catkin_tools/CATKIN_BUILD_DUMMY.json | |
alias catkin_build=compile_cmds_catkin_build | |
[ -f "${WS_HOME}/cc_setup.zsh" ] && source "${ROS_WS_DIR}/cc_setup.zsh" || source "${ROS_WS_DIR}/devel/setup.zsh" | |
} | |
DEFAULT_WS="$HOME/ws" | |
ws() {cd "${1:-$DEFAULT_WS}" && source_ws} | |
roswd() {roscd "$@" && pwd} | |
# Source Required WSs | |
source /opt/ros/noetic/setup.zsh | |
source ~/.local/opt/zed_noetic/setup.zsh | |
# FZF Tabcomplete, ZSH Autosuggestions, and ZSH syntax highlighting | |
# Installation Instructions: | |
# sudo apt install zsh fzf zsh-autosuggestions zsh-syntax-highlighting | |
# wget -O ~/.zshrc https://git.grml.org/f/grml-etc-core/etc/zsh/zshrc | |
# wget 'https://raw.githubusercontent.com/lincheney/fzf-tab-completion/master/zsh/fzf-zsh-completion.sh' -P ~/.local/share | |
source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh | |
export FZF_DEFAULT_OPTS="--reverse" | |
source /usr/share/doc/fzf/examples/key-bindings.zsh | |
source /usr/share/doc/fzf/examples/completion.zsh | |
source ~/.local/share/fzf-zsh-completion.sh # Breaks ROS Completion, run togcomp to turn it off for ROS | |
# toggle fzf complete to allow ROS autocompletion | |
togcomp() { | |
if [ "${fzf_default_completion}" = "fzf_completion" ]; then | |
export fzf_default_completion=expand-or-complete | |
echo "fzf tab disabled" | |
else | |
export fzf_default_completion=fzf_completion | |
echo "fzf tab renabled" | |
fi | |
} | |
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Filename: /etc/tmux | |
# Purpose: configuration file for tmux | |
# Authors: grml-team (grml.org), (c) Michael Prokop <mika at grml.org>, | |
# Sebastian Boehm <sebastian at sometimesfood.org> | |
# Bernhard Tittelbach <xro at realraum.at> | |
# Bug-Reports: see http://grml.org/bugs/ | |
# License: This file is licensed under the GPL v2. | |
################################################################################ | |
### screen-like keybindings | |
unbind C-b | |
set -g prefix C-a | |
bind-key a send-prefix | |
bind-key C-a last-window | |
unbind space | |
bind-key space next-window | |
bind-key C-space next-window | |
bind-key K confirm-before kill-pane | |
bind-key '\' confirm-before kill-session | |
#bind-key C-h previous-window | |
### join the last active pane to the currently active window | |
bind-key j join-pane -s ! | |
### join the marked pane to the currently active window | |
### ('bind-key m select-pane -m' is default to mark the current pane) | |
bind-key J join-pane | |
### Move current window to session named "bg" (and create session if it does not exist) | |
bind-key B if-shell "! tmux has-session -t bg" "new-session -d -s bg" \; move-window -t bg | |
### Reload Config | |
if-shell "! (env | grep -q TMUX=/tmp/tmate)" \ | |
"bind-key R source-file ~/.tmux.conf \\; source-file -q ~/.tmux.conf.local \\; display-message '~/.tmux.conf[.local] reloaded'" | |
###rebind keys | |
bind-key h next-layout | |
bind-key BSpace previous-window | |
bind-key tab select-pane -t :.+ | |
### useful custom keybindings | |
bind-key | command-prompt -p "exec:" "split-window -h '%%'" | |
bind-key - command-prompt -p "exec:" "split-window -v '%%'" | |
### misc options | |
set -s escape-time 0 | |
set -g default-terminal "screen-256color" | |
set -g display-panes-time 3000 | |
set -g visual-activity on | |
set -g mode-keys vi | |
### set status line appearance | |
set -g status-style fg=white,bg=black | |
set -g status-left-length 28 | |
### status-left: @hostname:sessionname | |
set -g status-left "#[fg=white]@#h#[fg=red]:#S#[fg=white] |" | |
### status-left: username@hostname:sessionname | |
#set -g status-left "#[fg=blue]#(echo ${USER})#[fg=white]@#h#[fg=red]:#S#[fg=white] |" | |
### status-right: Date and Time | |
set -g status-right-length 16 | |
set -g status-right '#[fg=yellow]%Y-%m-%d %H:%M' | |
### status-right: Time | |
#set -g status-right-length 6 | |
#set -g status-right "#[fg=yellow]%H:%M" | |
set-window-option -g window-status-style fg=blue,bg=black | |
set-window-option -g window-status-current-style bold | |
### source user-specific local configuration file | |
if-shell "! (env | grep -q TMUX=/tmp/tmate)" \ | |
"source-file -q ~/.config/tmux.conf.local" | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Enable true color | |
set -sa terminal-overrides ",xterm-256color:Tc" | |
unbind C-a | |
set-option -g prefix C-x | |
bind-key C-b send-keys C-b | |
bind-key C-x send-prefix | |
bind-key -n C-b send-prefix | |
set -g mouse | |
bind-key V split-window -h # h | |
bind-key v split-window -v | |
bind-key N new-window | |
bind PageUp copy-mode -eu | |
bind C-S run "tmux save-buffer - | xclip -selection clipboard" | |
bind Q confirm-before -p "kill-session #S? (y/n)" kill-session | |
set -g base-index 1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user] | |
name = Suraj | |
email = [email protected] | |
signingkey = D2C996AB7423059E | |
[core] | |
editor = vim | |
autocrlf = input | |
[init] | |
defaultbranch = master | |
[alias] | |
lg = lg1 | |
lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)' --all | |
lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(auto)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' | |
[commit] | |
gpgsign = true | |
[rerere] | |
enabled = true | |
[branch] | |
sort = -committerdate | |
[diff] | |
submodule = log | |
mnemonicprefix = true | |
[url "ssh://[email protected]/"] | |
insteadof = https://github.com/ | |
[url "ssh://[email protected]"] | |
insteadof = gh: | |
[url "https://aur.archlinux.org/"] | |
insteadof = aur: | |
[pull] | |
ff = only | |
[merge] | |
conflictstyle = diff3 | |
tool = meld | |
[push] | |
default = current | |
[status] | |
submodulesummary = true | |
[submodule] | |
recurse = true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment