diff --git a/aactivator.py b/aactivator.py new file mode 100755 index 0000000..6360042 --- /dev/null +++ b/aactivator.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""\ +Usage: eval "$(aactivator init)" + +aactivator is a script for automatically sourcing environments in an interactive shell. +The interface for using this is two files: + + - .activate.sh: when sourced, this file activates your environment + - .deactivate.sh: when sourced, this file deactivates your environment + +A typical setup in a python project: + + $ ln -vs venv/bin/activate .activate.sh + $ echo deactivate > .deactivate.sh + +If an environment is already active it will not be re-activated. +If a different project is activated, the previous project will be deactivated beforehand. + +aactivator will ask before automatically sourcing environments, and optionally +remember your answer. You can later adjust your per-project preferences in the +~/.cache/aactivator/ directory. + +see also: https://github.com/Yelp/aactivator +""" +from __future__ import absolute_import, print_function, unicode_literals + +import io +import os.path +import stat +import sys +from os.path import relpath +from shlex import quote + +ENVIRONMENT_VARIABLE = "AACTIVATOR_ACTIVE" +ACTIVATE = ".activate.sh" +DEACTIVATE = ".deactivate.sh" + +__version__ = "2.0.0" + + +def init(arg0): + arg0 = os.path.realpath(arg0) + cmd = 'if [ -x {exe} ]; then eval "`{exe}`"; fi'.format(exe=arg0) + return """\ +export AACTIVATOR_VERSION={version} +alias aactivator={arg0} +unset {varname} +if [ "$ZSH_VERSION" ]; then + precmd_aactivator() {{ {cmd}; }} + if ! [ "${{precmd_functions[(r)precmd_aactivator]}}" ]; then + precmd_functions=(precmd_aactivator $precmd_functions) + fi +else + if ! ( echo "$PROMPT_COMMAND" | grep -Fq '{cmd}' ); then + PROMPT_COMMAND='{cmd}; '"$PROMPT_COMMAND" + fi +fi""".format( + version=__version__, arg0=arg0, cmd=cmd, varname=ENVIRONMENT_VARIABLE + ) + + +def get_filesystem_id(path): + try: + return os.stat(path).st_dev + except OSError as error: + if error.errno == 2: # no such file + return None + else: + raise + + +def insecure_inode(path): + """This particular inode can be altered by someone other than the owner""" + pathstat = os.stat(path).st_mode + # Directories with a sticky bit are always acceptable. + if os.path.isdir(path) and pathstat & stat.S_ISVTX: + return False + # The path is writable by someone who is not us. + elif pathstat & (stat.S_IWGRP | stat.S_IWOTH): + return True + else: + return False + + +def first(iterable, predicate): + for x in iterable: + if predicate(x): + return x + + +def insecure(path): + """Find an insecure path, at or above this one""" + return first(search_parent_paths(path), insecure_inode) + + +def search_parent_paths(path): + path = os.path.abspath(path) + original_fs_id = fs_id = get_filesystem_id(path) + previous_path = None + while original_fs_id == fs_id and path != previous_path: + yield path + previous_path = path + path = os.path.dirname(path) + fs_id = get_filesystem_id(path) + + +def error_command(message): + return "echo %s >&2" % quote("aactivator: " + message) + + +def mkdirp(path): + try: + os.makedirs(path) + except OSError: + if os.path.isdir(path): + return + else: + raise + + +def _get_lines_if_there(path): + if os.path.exists(path): + return io.open(path).read().splitlines() + else: + return [] + + +class ConfigFile(object): + def __init__(self, directory, name): + self.path = os.path.join(directory, name) + self.lines = frozenset(_get_lines_if_there(self.path)) + + def write(self, mode, value): + mkdirp(os.path.dirname(self.path)) + with io.open(self.path, mode) as file_obj: + file_obj.write(value) + + def append(self, value): + self.write("a", value + "\n") + + +def path_is_under(path, under): + relpath = os.path.relpath(path, under).split("/") + return not relpath[:1] == [".."] + + +def user_cache_dir(env): + # stolen from pip.utils.appdirs.user_cache_dir + # expanduser doesn't take an env argument -.- + from os.path import expanduser + + orig, os.environ = os.environ, env + try: + return expanduser(env.get("XDG_CACHE_HOME", "~/.cache")) + finally: + os.environ = orig + + +class ActivateConfig(object): + def __init__(self, env, get_input): + self.env = env + self.get_input = get_input + self.path = os.path.join(user_cache_dir(self.env), "aactivator") + self.allowed = ConfigFile(self.path, "allowed") + self.not_now = ConfigFile(self.path, "not-now") + self.disallowed = ConfigFile(self.path, "disallowed") + + def refresh_not_now(self, pwd): + result = [] + for path in self.not_now.lines: + dirname = os.path.dirname(path) + if path_is_under(pwd, dirname): + result.append(path) + self.not_now.write("w", "\n".join(result)) + + def _prompt_user(self, path): + print( + "aactivator will source {0} and {1} at {2}.".format( + ACTIVATE, + DEACTIVATE, + path, + ), + file=sys.stderr, + ) + while True: + print("Acceptable? (y)es (n)o (N)ever: ", file=sys.stderr, end="") + sys.stderr.flush() + try: + response = self.get_input() + # Allow ^D to be "no" + except EOFError: + response = "n" + + if response.startswith("N"): + self.disallowed.append(path) + print( + "aactivator will remember this: " "~/.cache/aactivator/disallowed", + file=sys.stderr, + ) + return False + + response = response.lower() + if response.startswith("n"): + self.not_now.append(path) + return False + elif response.startswith("y"): + self.allowed.append(path) + print( + "aactivator will remember this: " "~/.cache/aactivator/allowed", + file=sys.stderr, + ) + return True + else: + print("I didn't understand your response.", file=sys.stderr) + print(file=sys.stderr) + + def find_allowed(self, path): + self.refresh_not_now(path) + return first(search_parent_paths(path), self.is_allowed) + + def is_allowed(self, path, _getuid=os.getuid): + activate = os.path.join(path, ACTIVATE) + if not os.path.exists(activate): + return False + elif os.stat(activate).st_uid != _getuid(): + # If we do not own this path, short circuit on activating + return False + elif path in self.disallowed.lines or path in self.not_now.lines: + return False + elif path in self.allowed.lines: + return True + else: + return self._prompt_user(path) + + +def security_check(path): + if not os.path.exists(path): + return "aactivator: File does not exist: " + path + insecure_path = insecure(path) + if insecure_path is not None: + return "aactivator: Cowardly refusing to source {0} because writeable by others: {1}".format( + relpath(path), relpath(insecure_path) + ) + + +def command_for_path(cmd, path, pwd): + if path == pwd: + return cmd + else: + return " &&\n".join( + ( + 'OLDPWD_bak="$OLDPWD"', + "cd " + quote(path), + cmd, + 'cd "$OLDPWD_bak"', + "cd " + quote(pwd), + "unset OLDPWD_bak", + ) + ) + + +def aactivate(path, pwd): + return command_for_path( + " &&\n".join( + ( + "aactivator security-check " + ACTIVATE, + "source ./" + ACTIVATE, + "export %s=%s" % (ENVIRONMENT_VARIABLE, quote(path)), + ) + ), + path, + pwd, + ) + + +def deaactivate(path, pwd): + unset = "unset " + ENVIRONMENT_VARIABLE + deactivate_path = os.path.join(path, DEACTIVATE) + + if os.path.exists(deactivate_path): + return command_for_path( + " &&\n".join( + ( + "aactivator security-check " + DEACTIVATE, + "source ./" + DEACTIVATE, + ) + ) + + "\n" + + unset, + path, + pwd, + ) + else: + return " &&\n".join( + ( + unset, + error_command( + "Cannot deactivate. File missing: {0}".format(deactivate_path) + ), + ) + ) + + +def get_output( + environ, pwd=".", get_input=sys.stdin.readline, arg0="/path/to/aactivator" +): + try: + pwd = os.path.realpath(pwd) + except OSError as error: + if error.errno == 2: # no such file + return "" + else: + raise + config = ActivateConfig(environ, get_input) + activate_path = config.find_allowed(pwd) + result = [] + + if environ.get("AACTIVATOR_VERSION") == __version__: + activated_env = environ.get(ENVIRONMENT_VARIABLE) + else: + result.append(init(arg0)) + activated_env = None + + if ( + activated_env != activate_path + ): # did we already activate the current environment? + if activated_env: # deactivate it + result.append(deaactivate(activated_env, pwd)) + if activate_path: + result.append(aactivate(activate_path, pwd)) + return " &&\n".join(result) + + +def aactivator(args, env): + if len(args) == 1: + return get_output(env, arg0=args[0]) + elif len(args) == 2 and args[1] == "init": + return init(args[0]) + elif len(args) == 3 and args[1] == "security-check": + exit(security_check(args[2])) + else: + return __doc__ + "\nVersion: " + __version__ + + +def main(): + try: + print(aactivator(tuple(sys.argv), os.environ.copy())) + except KeyboardInterrupt: # pragma: no cover + # Silence ^C + pass + + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + # Silence ^C + pass diff --git a/setup_config b/setup_config new file mode 100755 index 0000000..2649217 --- /dev/null +++ b/setup_config @@ -0,0 +1,129 @@ +#!/bin/bash + +# Function to check if a command is available +command_exists() { + command -v "$1" &> /dev/null +} + +# Function to install dependencies based on the package manager +install_dependencies() { + case $PACKAGE_MANAGER in + "brew") $PACKAGE_MANAGER install "$@" ;; + "apt-get") sudo $PACKAGE_MANAGER install -y "$@" ;; + "yum") sudo $PACKAGE_MANAGER install -y "$@" ;; + esac +} + +# Check if running on macOS or Linux +if [ "$(uname)" == "Darwin" ]; then + # Check if Homebrew is not installed + if ! command_exists brew; then + echo "Homebrew is not installed. Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + PACKAGE_MANAGER="brew" + fi +elif [ "$(uname)" == "Linux" ]; then + # Check if on Linux and use the appropriate package manager + if command_exists apt-get; then + PACKAGE_MANAGER="apt-get" + elif command_exists yum; then + PACKAGE_MANAGER="yum" + else + echo "Unsupported package manager. Exiting." + exit 1 + fi +fi + +# Clone or initialize dotfiles repository +DOTFILES_DIR="$HOME/.dotfiles" +if [ -d "$DOTFILES_DIR" ]; then + if [ -d "$DOTFILES_DIR/.git" ]; then + echo "Updating existing dotfiles repository..." + (cd "$DOTFILES_DIR" && git pull origin master) + else + git -C "$DOTFILES_DIR" init + git -C "$DOTFILES_DIR" clone https://github.com/jfraeys/.dotfiles.git "$DOTFILES_DIR" + fi +else + echo "Creating dotfiles directory..." + mkdir -p "$DOTFILES_DIR" + git -C "$DOTFILES_DIR" init + git -C "$DOTFILES_DIR" clone https://github.com/jfraeys/.dotfiles.git "$DOTFILES_DIR" +fi + +# Check and install git and other tools +if ! command_exists git; then + install_dependencies git fd ripgrep fzf tree +fi + +# Check and install zsh +if ! command_exists zsh; then + install_dependencies zsh + # Clone Oh-My-Zsh if not already present + if [ ! -d "$HOME/.oh-my-zsh" ]; then + git clone https://github.com/ohmyzsh/ohmyzsh.git "$HOME/.oh-my-zsh" + fi + # Set Zsh as the default shell + chsh -s "$(command -v zsh)" +fi + +# Check and install tmux +if ! command_exists tmux; then + install_dependencies tmux +fi + +# Install Go +case $PACKAGE_MANAGER in + "brew") + $PACKAGE_MANAGER install go + ;; + "apt-get") + sudo $PACKAGE_MANAGER install -y golang + ;; + "yum") + sudo $PACKAGE_MANAGER install -y golang + ;; +esac + +# Install Python and pip +case $PACKAGE_MANAGER in + "brew") + $PACKAGE_MANAGER install python + ;; + "apt-get") + sudo $PACKAGE_MANAGER install -y python3 python3-pip + ;; + "yum") + sudo $PACKAGE_MANAGER install -y python3 python3-pip + ;; +esac + +# Install some common linters (uncomment if needed) +# case $PACKAGE_MANAGER in +# "brew") +# $PACKAGE_MANAGER install shellcheck +# ;; +# "apt-get") +# sudo $PACKAGE_MANAGER install -y shellcheck +# ;; +# "yum") +# sudo $PACKAGE_MANAGER install -y shellcheck +# ;; +# esac + +# Create symbolic links for dotfiles +ln -s "$DOTFILES_DIR/zsh/.zshrc" ~/.zshrc +ln -s "$DOTFILES_DIR/tmux/.tmux.conf" ~/.tmux.conf +ln -s "$DOTFILES_DIR/nvim" ~/.config/nvim + +# Source configurations for the current session +source ~/.zshrc +tmux source ~/.tmux.conf + +# Inform the user and wait for input +echo "Zsh, Tmux, Neovim, Go, and development tools configurations applied. Press Enter to start Zsh and Tmux." +read -r + +# Start Zsh and Tmux +zsh + diff --git a/stow_setup b/stow_setup new file mode 100755 index 0000000..d36ec22 --- /dev/null +++ b/stow_setup @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Check for Git +if ! command -v git &> /dev/null; then + echo "Git is not installed. Aborting." >&2 + exit 1 +fi + +# make sure we have pulled in and updated any submodules +git submodule init +git submodule update + +# what directories should be installable by all users including the root user +base=( + zsh + nvim + tmux +) + +# folders that should, or only need to be installed for a local user +useronly=( + git +) + +# files to ignore during stow +ignore_files=( + ".DS_Store" + "__setup" + "bin" + "git" + "setup.sh" +) + +# run the stow command for the passed-in directory ($2) in location $1 +stowit() { + usr=$1 + app=$2 + # -v verbose + # -R recursive + # -t target + # --ignore files to ignore + stow -v -R -t "${usr}" --ignore "$(IFS="|"; echo "${ignore_files[*]}")" "${app}" +} + +echo "" +echo "Stowing apps for user: ${USER}" + +# install apps available to local users and root +for app in "${base[@]}"; do + stowit "${HOME}" "${app}" +done + +# install only user space folders +for app in "${useronly[@]}"; do + if [[ ! "$(whoami)" = "root" ]]; then + stowit "${HOME}" "${app}" + fi +done + +echo "" +echo "##### ALL DONE" + + +