mirror of
https://github.com/Matir/skel.git
synced 2026-06-10 11:13:40 -07:00
Compare commits
23 Commits
4e72b9b18c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e0c11c044 | ||
|
|
68daa27893 | ||
|
|
398d55b8e4 | ||
|
|
c9918cf213 | ||
|
|
d1e3c8e43e | ||
|
|
f4bb5108ab | ||
|
|
5eac8826da | ||
|
|
b1d625d3c5 | ||
|
|
5a7d9cf060 | ||
|
|
9770514d6a | ||
|
|
e1724c77f3 | ||
|
|
ecbc25e5ac | ||
|
|
ea33840ef6 | ||
|
|
1048775da3 | ||
|
|
c9af80bc5d | ||
|
|
d8b4991419 | ||
|
|
158d9f6e4e | ||
|
|
b1d1c42a02 | ||
|
|
f8ec9cc338 | ||
|
|
6e3c3dd269 | ||
|
|
77b8374871 | ||
|
|
4645682b5c | ||
|
|
75bdebb497 |
25
Brewfile
25
Brewfile
@@ -1,4 +1,5 @@
|
|||||||
tap "dart-lang/dart"
|
tap "dart-lang/dart"
|
||||||
|
tap "holtwick/tap"
|
||||||
tap "sass/sass"
|
tap "sass/sass"
|
||||||
|
|
||||||
brew "ack"
|
brew "ack"
|
||||||
@@ -8,11 +9,11 @@ brew "autoconf"
|
|||||||
brew "automake"
|
brew "automake"
|
||||||
brew "b2-tools"
|
brew "b2-tools"
|
||||||
brew "bat"
|
brew "bat"
|
||||||
brew "bazelisk"
|
|
||||||
brew "binwalk"
|
brew "binwalk"
|
||||||
brew "cask"
|
brew "cask"
|
||||||
brew "ccache"
|
brew "ccache"
|
||||||
brew "certbot"
|
brew "certbot"
|
||||||
|
brew "cloudflared"
|
||||||
brew "cmake"
|
brew "cmake"
|
||||||
brew "colima"
|
brew "colima"
|
||||||
brew "devcontainer"
|
brew "devcontainer"
|
||||||
@@ -20,8 +21,11 @@ brew "dfu-util"
|
|||||||
brew "difftastic"
|
brew "difftastic"
|
||||||
brew "direnv"
|
brew "direnv"
|
||||||
brew "duck"
|
brew "duck"
|
||||||
|
brew "dust"
|
||||||
brew "earthly"
|
brew "earthly"
|
||||||
brew "esptool"
|
brew "esptool"
|
||||||
|
brew "fish"
|
||||||
|
brew "fq"
|
||||||
brew "gh"
|
brew "gh"
|
||||||
brew "ghidra", link: false
|
brew "ghidra", link: false
|
||||||
brew "git"
|
brew "git"
|
||||||
@@ -31,14 +35,18 @@ brew "gnupg"
|
|||||||
brew "go"
|
brew "go"
|
||||||
brew "gradle"
|
brew "gradle"
|
||||||
brew "hf"
|
brew "hf"
|
||||||
|
brew "holtwick/tap/bx"
|
||||||
brew "htop"
|
brew "htop"
|
||||||
brew "httpie"
|
brew "httpie"
|
||||||
brew "huggingface-cli"
|
|
||||||
brew "hugo"
|
brew "hugo"
|
||||||
brew "imagemagick"
|
brew "imagemagick"
|
||||||
brew "john-jumbo"
|
brew "john-jumbo"
|
||||||
brew "jq"
|
brew "jq"
|
||||||
|
brew "kubeconform"
|
||||||
|
brew "kubectx"
|
||||||
|
brew "librsvg"
|
||||||
brew "lima"
|
brew "lima"
|
||||||
|
brew "minikube"
|
||||||
brew "mise"
|
brew "mise"
|
||||||
brew "mosh"
|
brew "mosh"
|
||||||
brew "neovim"
|
brew "neovim"
|
||||||
@@ -52,6 +60,7 @@ brew "pkgconf"
|
|||||||
brew "protobuf"
|
brew "protobuf"
|
||||||
brew "pwgen"
|
brew "pwgen"
|
||||||
brew "pwntools"
|
brew "pwntools"
|
||||||
|
brew "python@3.13"
|
||||||
brew "qemu"
|
brew "qemu"
|
||||||
brew "restic"
|
brew "restic"
|
||||||
brew "ripgrep"
|
brew "ripgrep"
|
||||||
@@ -93,6 +102,7 @@ cask "rectangle"
|
|||||||
cask "scroll-reverser"
|
cask "scroll-reverser"
|
||||||
cask "temurin"
|
cask "temurin"
|
||||||
cask "veracrypt"
|
cask "veracrypt"
|
||||||
|
cask "wezterm"
|
||||||
cask "zulu@17"
|
cask "zulu@17"
|
||||||
|
|
||||||
def is_corp?
|
def is_corp?
|
||||||
@@ -100,13 +110,22 @@ def is_corp?
|
|||||||
`profiles status -type enrollment 2>/dev/null`.include?("Enrolled via DEP: Yes")
|
`profiles status -type enrollment 2>/dev/null`.include?("Enrolled via DEP: Yes")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if is_corp?
|
||||||
|
brew "bazelisk", link: false
|
||||||
|
end
|
||||||
|
|
||||||
# non-corp
|
# non-corp
|
||||||
if !is_corp?
|
if !is_corp?
|
||||||
brew "bazel"
|
brew "bazelisk"
|
||||||
brew "openssh"
|
brew "openssh"
|
||||||
|
brew "virt-manager"
|
||||||
|
|
||||||
cask "claude-code"
|
cask "claude-code"
|
||||||
cask "cryptomator"
|
cask "cryptomator"
|
||||||
|
cask "keepassxc"
|
||||||
cask "gcloud-cli"
|
cask "gcloud-cli"
|
||||||
cask "google-cloud-sdk"
|
cask "google-cloud-sdk"
|
||||||
|
cask "keybase"
|
||||||
cask "orbstack"
|
cask "orbstack"
|
||||||
|
cask "jordanbaird-ice"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -251,7 +251,13 @@ def main(args):
|
|||||||
if last_type and e.pkg_type != last_type:
|
if last_type and e.pkg_type != last_type:
|
||||||
output_lines.append("")
|
output_lines.append("")
|
||||||
last_type = e.pkg_type
|
last_type = e.pkg_type
|
||||||
output_lines.extend(e.to_lines())
|
|
||||||
|
lines = e.to_lines()
|
||||||
|
# If we just added a blank line, and the new lines start with one, skip the first new line
|
||||||
|
if output_lines and output_lines[-1] == "" and lines and lines[0] == "":
|
||||||
|
output_lines.extend(lines[1:])
|
||||||
|
else:
|
||||||
|
output_lines.extend(lines)
|
||||||
|
|
||||||
new_content = "\n".join(output_lines)
|
new_content = "\n".join(output_lines)
|
||||||
if footer:
|
if footer:
|
||||||
|
|||||||
212
bin/newnote
Executable file
212
bin/newnote
Executable file
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Exit on error, undefined variables, and pipe failures
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: $(basename "$0") [options] <note_name_or_path>
|
||||||
|
|
||||||
|
Create a new note in your Obsidian vault.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-v, --vault <dir> Specify the Obsidian vault directory.
|
||||||
|
--overwrite Overwrite the file if it already exists.
|
||||||
|
-h, --help Show this help message.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
VAULT_DIR=""
|
||||||
|
OVERWRITE="false"
|
||||||
|
NOTE_PATH=""
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-v|--vault)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo "Error: --vault requires a directory path." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VAULT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--overwrite)
|
||||||
|
OVERWRITE="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Error: Unknown option: $1" >&2
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$NOTE_PATH" ]]; then
|
||||||
|
echo "Error: Multiple note paths provided: $NOTE_PATH and $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NOTE_PATH="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$NOTE_PATH" ]]; then
|
||||||
|
echo "Error: Missing note path/name." >&2
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. Find the Obsidian vault.
|
||||||
|
if [[ -n "$VAULT_DIR" ]]; then
|
||||||
|
# -v/--vault was passed. Check that the directory exists and contains a .obsidian directory.
|
||||||
|
if [[ ! -d "$VAULT_DIR" ]]; then
|
||||||
|
echo "Error: Specified vault directory does not exist: $VAULT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$VAULT_DIR/.obsidian" ]]; then
|
||||||
|
echo "Error: Specified directory is not an Obsidian vault (missing .obsidian): $VAULT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VAULT_DIR="$(cd "$VAULT_DIR" && pwd)"
|
||||||
|
else
|
||||||
|
# Search directories upward for a parent (or current) directory containing a .obsidian subdirectory.
|
||||||
|
# Stop at the user's home directory or a filesystem boundary.
|
||||||
|
CURRENT_DIR="$PWD"
|
||||||
|
FOUND_VAULT=""
|
||||||
|
|
||||||
|
get_device_id() {
|
||||||
|
local dir="$1"
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
stat -f '%d' "$dir" 2>/dev/null || echo ""
|
||||||
|
else
|
||||||
|
stat -c '%d' "$dir" 2>/dev/null || echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
CURRENT_DEV="$(get_device_id "$CURRENT_DIR")"
|
||||||
|
|
||||||
|
while [[ "$CURRENT_DIR" != "/" && "$CURRENT_DIR" != "$HOME" ]]; do
|
||||||
|
if [[ -d "$CURRENT_DIR/.obsidian" ]]; then
|
||||||
|
FOUND_VAULT="$CURRENT_DIR"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
PARENT_DIR="$(dirname "$CURRENT_DIR")"
|
||||||
|
if [[ "$PARENT_DIR" == "$CURRENT_DIR" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check filesystem boundary
|
||||||
|
PARENT_DEV="$(get_device_id "$PARENT_DIR")"
|
||||||
|
if [[ -n "$CURRENT_DEV" && -n "$PARENT_DEV" && "$CURRENT_DEV" != "$PARENT_DEV" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURRENT_DIR="$PARENT_DIR"
|
||||||
|
CURRENT_DEV="$PARENT_DEV"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check the last checked directory (could be HOME or /)
|
||||||
|
if [[ -z "$FOUND_VAULT" && -d "$CURRENT_DIR/.obsidian" ]]; then
|
||||||
|
FOUND_VAULT="$CURRENT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$FOUND_VAULT" ]]; then
|
||||||
|
VAULT_DIR="$FOUND_VAULT"
|
||||||
|
else
|
||||||
|
# Fallback paths in order: ~/Notes, ~/Obsidian/Notes, ~/Personal/Notes, ~/Projects/Notes.
|
||||||
|
FALLBACKS=(
|
||||||
|
"$HOME/Notes"
|
||||||
|
"$HOME/notes"
|
||||||
|
"$HOME/Obsidian/Notes"
|
||||||
|
"$HOME/Obsidian/notes"
|
||||||
|
"$HOME/Personal/Notes"
|
||||||
|
"$HOME/Personal/notes"
|
||||||
|
"$HOME/Projects/Notes"
|
||||||
|
"$HOME/Projects/notes"
|
||||||
|
)
|
||||||
|
for path in "${FALLBACKS[@]}"; do
|
||||||
|
if [[ -d "$path" && -d "$path/.obsidian" ]]; then
|
||||||
|
VAULT_DIR="$(cd "$path" && pwd)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$VAULT_DIR" ]]; then
|
||||||
|
echo "Error: Could not find an Obsidian vault. Please specify one with -v/--vault." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Strip leading slash from NOTE_PATH to handle relative paths properly
|
||||||
|
NOTE_PATH="${NOTE_PATH#/}"
|
||||||
|
|
||||||
|
# 2. Determine if current working directory is within the vault.
|
||||||
|
# Resolve physical paths to handle symlinks cleanly.
|
||||||
|
RESOLVED_PWD="$(pwd -P)"
|
||||||
|
RESOLVED_VAULT="$(cd "$VAULT_DIR" && pwd -P)"
|
||||||
|
|
||||||
|
if [[ "$RESOLVED_PWD" == "$RESOLVED_VAULT" || "$RESOLVED_PWD" == "$RESOLVED_VAULT"/* ]]; then
|
||||||
|
# Within the vault, treat relative to CWD
|
||||||
|
TARGET_FILE="$PWD/$NOTE_PATH"
|
||||||
|
else
|
||||||
|
# Not within the vault, treat relative to vault root
|
||||||
|
TARGET_FILE="$VAULT_DIR/$NOTE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_DIR="$(dirname "$TARGET_FILE")"
|
||||||
|
TARGET_BASE="$(basename "$TARGET_FILE")"
|
||||||
|
|
||||||
|
# If there's no file extension on the argument, add .md.
|
||||||
|
if [[ "$TARGET_BASE" != *.* ]]; then
|
||||||
|
TARGET_BASE="$TARGET_BASE.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_FILE="$TARGET_DIR/$TARGET_BASE"
|
||||||
|
|
||||||
|
# If the necessary directory components don't exist, create them.
|
||||||
|
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||||
|
mkdir -p "$TARGET_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. If the file exists and there's no --overwrite argument, throw an error.
|
||||||
|
if [[ -f "$TARGET_FILE" && "$OVERWRITE" != "true" ]]; then
|
||||||
|
echo "Error: File already exists: $TARGET_FILE (use --overwrite to replace it)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. If the path ends in .md (case-insensitive), generate appropriate YAML front matter and write it to the new file.
|
||||||
|
if [[ "${TARGET_BASE,,}" == *.md ]]; then
|
||||||
|
NOTE_TITLE="${TARGET_BASE%.*}"
|
||||||
|
CURRENT_DATE="$(date +"%Y-%m-%d %H:%M")"
|
||||||
|
|
||||||
|
cat << EOF > "$TARGET_FILE"
|
||||||
|
---
|
||||||
|
title: "$NOTE_TITLE"
|
||||||
|
date: $CURRENT_DATE
|
||||||
|
tags: []
|
||||||
|
---
|
||||||
|
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
# Just touch the file to ensure it exists
|
||||||
|
touch "$TARGET_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Open the user's $EDITOR (falling back to vim/vi if unset) pointing to the new file.
|
||||||
|
EDITOR="${EDITOR:-}"
|
||||||
|
if [[ -z "$EDITOR" ]]; then
|
||||||
|
if command -v vim >/dev/null 2>&1; then
|
||||||
|
EDITOR="vim"
|
||||||
|
else
|
||||||
|
EDITOR="vi"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$EDITOR" "$TARGET_FILE"
|
||||||
63
bin/quartz
Executable file
63
bin/quartz
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# QUARTZ_DIR search logic
|
||||||
|
if [ -z "${QUARTZ_DIR:-}" ]; then
|
||||||
|
if [ -f "quartz.config.ts" ]; then
|
||||||
|
QUARTZ_DIR="$PWD"
|
||||||
|
elif [ -d "$HOME/Personal/notes-quartz" ]; then
|
||||||
|
QUARTZ_DIR="$HOME/Personal/notes-quartz"
|
||||||
|
elif [ -d "$HOME/Projects/notes-quartz" ]; then
|
||||||
|
QUARTZ_DIR="$HOME/Projects/notes-quartz"
|
||||||
|
else
|
||||||
|
echo "Error: QUARTZ_DIR could not be found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$QUARTZ_DIR" ]; then
|
||||||
|
echo "Error: QUARTZ_DIR '$QUARTZ_DIR' is not a directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NOTES_DIR search logic
|
||||||
|
PARSE_NOTES_DIR=""
|
||||||
|
# Use a copy of args to find -d/--directory
|
||||||
|
ARGS=("$@")
|
||||||
|
for ((i=0; i<${#ARGS[@]}; i++)); do
|
||||||
|
if [[ "${ARGS[i]}" == "-d" || "${ARGS[i]}" == "--directory" ]]; then
|
||||||
|
if [[ $((i+1)) -lt ${#ARGS[@]} ]]; then
|
||||||
|
PARSE_NOTES_DIR="${ARGS[i+1]}"
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$PARSE_NOTES_DIR" ]; then
|
||||||
|
NOTES_DIR="$PARSE_NOTES_DIR"
|
||||||
|
elif [ -z "${NOTES_DIR:-}" ]; then
|
||||||
|
if [ -d "$HOME/Notes" ]; then
|
||||||
|
NOTES_DIR="$HOME/Notes"
|
||||||
|
elif [ -d "$HOME/Personal/notes" ]; then
|
||||||
|
NOTES_DIR="$HOME/Personal/notes"
|
||||||
|
elif [ -d "$HOME/Projects/notes" ]; then
|
||||||
|
NOTES_DIR="$HOME/Projects/notes"
|
||||||
|
else
|
||||||
|
echo "Error: NOTES_DIR could not be found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$NOTES_DIR" ]; then
|
||||||
|
echo "Error: NOTES_DIR '$NOTES_DIR' is not a directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$QUARTZ_DIR"
|
||||||
|
|
||||||
|
# Run npx quartz
|
||||||
|
# Following the prompt's structure but using NOTES_DIR for the flag
|
||||||
|
# npx quartz ${argv[1]} --directory ${NOTES_DIR} "$@"
|
||||||
|
# We use ${1:-} for argv[1] to handle cases with no arguments.
|
||||||
|
npx quartz "${1:-}" --directory "$NOTES_DIR" "$@"
|
||||||
4
dotfiles/bxignore
Normal file
4
dotfiles/bxignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Credentials
|
||||||
|
.ssh
|
||||||
|
Passwords.kdbx
|
||||||
|
Passwords.kdbx.age
|
||||||
63
dotfiles/commonrc.sh
Normal file
63
dotfiles/commonrc.sh
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
|
||||||
|
# common functions for use in shell scripts
|
||||||
|
|
||||||
|
find_first() {
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
if test -e "${1}" ; then
|
||||||
|
echo "${1}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
have_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function: Returns 0 if the directory is in PATH, 1 otherwise
|
||||||
|
path_contains() {
|
||||||
|
case ":${PATH}:" in
|
||||||
|
*:"$1":*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepend a directory to PATH if it's not already there
|
||||||
|
path_prepend() {
|
||||||
|
if [ -d "$1" ] && ! path_contains "$1"; then
|
||||||
|
PATH="$1:${PATH}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Append a directory to PATH if it's not already there
|
||||||
|
path_append() {
|
||||||
|
if [ -d "$1" ] && ! path_contains "$1"; then
|
||||||
|
PATH="${PATH}:$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
source_first_existing() {
|
||||||
|
_sfe_script="$(find_first "$@")"
|
||||||
|
_sfe_status=$?
|
||||||
|
|
||||||
|
if [ "$_sfe_status" -eq 0 ]; then
|
||||||
|
. "$_sfe_script"
|
||||||
|
# Clean up our temporary variables before returning success
|
||||||
|
unset -v _sfe_script _sfe_status
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up our temporary variables before returning failure
|
||||||
|
unset -v _sfe_script _sfe_status
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
source_if_existing() {
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
. "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -9,9 +9,8 @@ uv_venv_auto = true
|
|||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
age = "latest"
|
age = "latest"
|
||||||
age-plugin-yubikey = "latest"
|
|
||||||
usage = "latest"
|
usage = "latest"
|
||||||
uv = "latest"
|
uv = "0.11.8"
|
||||||
|
|
||||||
[hooks]
|
[hooks]
|
||||||
postinstall = "mise sync python --uv"
|
postinstall = "mise sync python --uv"
|
||||||
|
|||||||
@@ -39,16 +39,3 @@ disabled = true
|
|||||||
[kubernetes]
|
[kubernetes]
|
||||||
disabled = false
|
disabled = false
|
||||||
detect_folders = ["k8s"]
|
detect_folders = ["k8s"]
|
||||||
|
|
||||||
[custom.gemini_context]
|
|
||||||
description = "Displays the current Gemini CLI context"
|
|
||||||
when = "test -n \"$GEMINI_CLI_HOME\""
|
|
||||||
command = """
|
|
||||||
context_dir=\"${XDG_CONFIG_HOME:-$HOME/.config}/gemini\"
|
|
||||||
if [[ \"$GEMINI_CLI_HOME\" == $context_dir/* ]]; then
|
|
||||||
basename \"$GEMINI_CLI_HOME\"
|
|
||||||
fi
|
|
||||||
"""
|
|
||||||
style = "bold blue"
|
|
||||||
format = "♊[$output](blue) "
|
|
||||||
shell = ["/bin/sh", "-c"]
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
[difftool]
|
[difftool]
|
||||||
prompt = false
|
prompt = false
|
||||||
|
|
||||||
|
[difftool "difftastic"]
|
||||||
|
cmd = difft "$LOCAL" "$REMOTE"
|
||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
st = status
|
st = status
|
||||||
last = log -1 HEAD
|
last = log -1 HEAD
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ mise.local.toml
|
|||||||
.copilot/
|
.copilot/
|
||||||
.cursor/
|
.cursor/
|
||||||
.gemini/
|
.gemini/
|
||||||
|
.jetskicli/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export DEBEMAIL="david@systemoverlord.com"
|
|||||||
export DEBFULLNAME="David Tomaschik"
|
export DEBFULLNAME="David Tomaschik"
|
||||||
export LESS="-MR"
|
export LESS="-MR"
|
||||||
export QUOTING_STYLE="literal" # Coreutils quotes
|
export QUOTING_STYLE="literal" # Coreutils quotes
|
||||||
|
export HOMEBREW_NO_ENV_HINTS=1
|
||||||
|
|
||||||
# Fix gnome-terminal
|
# Fix gnome-terminal
|
||||||
if [ "$TERM" = "xterm" ] && [ "$COLORTERM" = "gnome-terminal" ] ; then
|
if [ "$TERM" = "xterm" ] && [ "$COLORTERM" = "gnome-terminal" ] ; then
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ DIRSTACKSIZE=16
|
|||||||
|
|
||||||
export OS="$(uname 2>/dev/null || echo "Unknown")"
|
export OS="$(uname 2>/dev/null || echo "Unknown")"
|
||||||
|
|
||||||
|
source ~/.commonrc.sh
|
||||||
|
|
||||||
# Set terminal title
|
# Set terminal title
|
||||||
case $TERM in
|
case $TERM in
|
||||||
# Only set the title for terminals that are likely to support it
|
# Only set the title for terminals that are likely to support it
|
||||||
@@ -163,28 +165,9 @@ bindkey '^r' history-incremental-search-backward
|
|||||||
# delete really deletes
|
# delete really deletes
|
||||||
bindkey "^[[3~" delete-char
|
bindkey "^[[3~" delete-char
|
||||||
|
|
||||||
source_if_existing() {
|
|
||||||
[[ -f "${1}" ]] && source "${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
source_first_existing() {
|
path_prepend "${HOME}/.npm-packages/bin"
|
||||||
while (($#)); do
|
path_prepend "${HOME}/.local/bin"
|
||||||
if test -e "${1}" ; then
|
|
||||||
source "${1}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
have_command() {
|
|
||||||
command -v "${1}" &>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
if test -d ${HOME}/.local/bin ; then
|
|
||||||
export PATH="${HOME}/.local/bin:${PATH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Source extras and aliases if interactive
|
# Source extras and aliases if interactive
|
||||||
if [[ $- == *i* ]] ; then
|
if [[ $- == *i* ]] ; then
|
||||||
@@ -270,8 +253,7 @@ if [[ $- == *i* ]] ; then
|
|||||||
if command -v direnv >/dev/null 2>&1 ; then
|
if command -v direnv >/dev/null 2>&1 ; then
|
||||||
eval "$(direnv hook zsh)"
|
eval "$(direnv hook zsh)"
|
||||||
fi
|
fi
|
||||||
test -e "${HOME}/.iterm2_shell_integration.zsh" && \
|
source_if_existing "${HOME}/.iterm2_shell_integration.zsh"
|
||||||
source "${HOME}/.iterm2_shell_integration.zsh" || true
|
|
||||||
# mise, if installed
|
# mise, if installed
|
||||||
command -v mise >/dev/null 2>&1 && eval "$(mise activate zsh)"
|
command -v mise >/dev/null 2>&1 && eval "$(mise activate zsh)"
|
||||||
|
|
||||||
@@ -286,11 +268,11 @@ if [ -x /usr/bin/ack-grep ] ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# I want these first always
|
# I want these first always
|
||||||
PATH="${HOME}/bin:${PATH}"
|
path_prepend "${HOME}/bin"
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
PATH="${HOME}/bin/macos:${PATH}"
|
path_prepend "${HOME}/bin/macos"
|
||||||
elif [[ "$(uname)" == "Linux" ]]; then
|
elif [[ "$(uname)" == "Linux" ]]; then
|
||||||
PATH="${HOME}/bin/linux:${PATH}"
|
path_prepend "${HOME}/bin/linux"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Load any local settings
|
# Load any local settings
|
||||||
|
|||||||
@@ -2,31 +2,18 @@ function dumpenv {
|
|||||||
if [ "$(uname)" = "Linux" ]; then
|
if [ "$(uname)" = "Linux" ]; then
|
||||||
tr '\0' '\n' < /proc/${1}/environ
|
tr '\0' '\n' < /proc/${1}/environ
|
||||||
elif [ "$(uname)" = "Darwin" ]; then
|
elif [ "$(uname)" = "Darwin" ]; then
|
||||||
# macOS doesn't have /proc, use ps instead.
|
# macOS doesn't have /proc, use ps instead.
|
||||||
# Note: this may truncate if environment is very large.
|
# Note: this may truncate if environment is very large.
|
||||||
ps -p ${1} -wwwe -o command= | tr ' ' '\n' | grep '='
|
ps -p ${1} -wwwe -o command= | tr ' ' '\n' | grep '='
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if test -x "/sbin/starship" ; then
|
_STARSHIP_PATH="$(find_first "$(command -v starship)" /sbin/starship "${HOME}/tools/starship/starship" "${HOME}/.local/bin/starship" /usr/local/bin/starship)"
|
||||||
_STARSHIP_PATH="/sbin/starship"
|
if test -n "$_STARSHIP_PATH" ; then
|
||||||
function starship_prompt {
|
function starship_prompt {
|
||||||
eval "$(/sbin/starship init zsh)"
|
eval "$($_STARSHIP_PATH init zsh)"
|
||||||
}
|
|
||||||
elif test -x "${HOME}/tools/starship/starship" ; then
|
|
||||||
_STARSHIP_PATH="${HOME}/tools/starship/starship"
|
|
||||||
function starship_prompt {
|
|
||||||
eval "$($HOME/tools/starship/starship init zsh)"
|
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
if test -f ${HOME}/.zprompt ; then
|
|
||||||
if test "$(cat ${HOME}/.zprompt)" = "starship" ; then
|
|
||||||
if test -n "${_STARSHIP_PATH:-}" ; then
|
|
||||||
eval "$(${_STARSHIP_PATH} init zsh)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
unset _STARSHIP_PATH
|
|
||||||
|
|
||||||
function hashall {
|
function hashall {
|
||||||
tee >(md5sum) | tee >(sha1sum) | sha256sum
|
tee >(md5sum) | tee >(sha1sum) | sha256sum
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
# Skelify -- move a file to my .skel and setup symlinks
|
# Skelify -- move a file to my .skel and setup symlinks
|
||||||
|
|
||||||
function skelify {
|
function skelify {
|
||||||
|
local -A opts
|
||||||
|
zparseopts -D -A opts -overlay:
|
||||||
|
|
||||||
|
local overlay_name="${opts[--overlay]}"
|
||||||
|
local base_skel_dir="${HOME}/.skel/dotfiles"
|
||||||
|
local extra_args=()
|
||||||
|
if [[ -n "${overlay_name}" ]]; then
|
||||||
|
base_skel_dir="${HOME}/.skel/dotfile_overlays/${overlay_name}"
|
||||||
|
extra_args=(--overlay "${overlay_name}")
|
||||||
|
fi
|
||||||
|
|
||||||
local target
|
local target
|
||||||
local whichdir
|
local whichdir
|
||||||
local relhome
|
local relhome
|
||||||
@@ -10,16 +21,19 @@ function skelify {
|
|||||||
local fulltarget
|
local fulltarget
|
||||||
for target in $~@; do
|
for target in $~@; do
|
||||||
if test -d ${target} ; then
|
if test -d ${target} ; then
|
||||||
skelify ${target}/* || return 1
|
skelify "${extra_args[@]}" ${target}/* || return 1
|
||||||
elif test -f ${target} ; then
|
elif test -f ${target} ; then
|
||||||
if ! whichdir=$(cd $(dirname $target) && pwd); then
|
if ! whichdir=$(cd $(dirname $target) && pwd); then
|
||||||
echo Could not find directory for $target >/dev/stderr
|
echo Could not find directory for $target >/dev/stderr
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fname=$(basename ${target})
|
fname=$(basename ${target})
|
||||||
relhome=${whichdir#${HOME}/}
|
|
||||||
fulltarget="${whichdir}/${fname}"
|
fulltarget="${whichdir}/${fname}"
|
||||||
if [[ ${relhome} == ${whichdir} ]] ; then
|
if [[ ${whichdir} == ${HOME} ]] ; then
|
||||||
|
relhome=""
|
||||||
|
elif [[ ${whichdir} == ${HOME}/* ]] ; then
|
||||||
|
relhome=${whichdir#${HOME}/}
|
||||||
|
else
|
||||||
echo ${whichdir} is not in home >/dev/stderr
|
echo ${whichdir} is not in home >/dev/stderr
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -32,7 +46,7 @@ function skelify {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo ${target}
|
echo ${target}
|
||||||
local skeldir="${HOME}/.skel/dotfiles/${relhome}"
|
local skeldir="${base_skel_dir}/${relhome}"
|
||||||
mkdir -p "${skeldir}"
|
mkdir -p "${skeldir}"
|
||||||
mv ${target} "${skeldir}/${fname}"
|
mv ${target} "${skeldir}/${fname}"
|
||||||
ln -s "${skeldir}/${fname}" "${fulltarget}"
|
ln -s "${skeldir}/${fname}" "${fulltarget}"
|
||||||
|
|||||||
139
install.sh
139
install.sh
@@ -7,14 +7,65 @@ set -o errexit
|
|||||||
set -o shwordsplit 2>/dev/null || true # Make zsh behave like bash
|
set -o shwordsplit 2>/dev/null || true # Make zsh behave like bash
|
||||||
|
|
||||||
HOME=${HOME:-$(cd ~ && pwd)}
|
HOME=${HOME:-$(cd ~ && pwd)}
|
||||||
|
LOCAL_BIN="${HOME}/.local/bin"
|
||||||
|
STARSHIP_INSTALL_HASH="52c64f14a558034ebeb1907ea9364e802b32474576fd3e68265f73bc33cc8fbb"
|
||||||
|
|
||||||
|
# 1. Get the raw script path (handles Bash vs Zsh)
|
||||||
|
TARGET="${BASH_SOURCE[0]}"
|
||||||
|
|
||||||
|
# 2. Loop to resolve symlinks completely
|
||||||
|
while [ -L "$TARGET" ]; do
|
||||||
|
DIR=$(cd -P "$(dirname -- "$TARGET")" &>/dev/null && pwd)
|
||||||
|
TARGET=$(readlink "$TARGET")
|
||||||
|
# If $TARGET is a relative symlink, resolve it relative to the symlink's directory
|
||||||
|
[[ $TARGET != /* ]] && TARGET="$DIR/$TARGET"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. Get the final absolute directory
|
||||||
|
SCRIPT_DIR="$(cd -P "$(dirname -- "$TARGET")" &>/dev/null && pwd)"
|
||||||
|
|
||||||
have_command() {
|
have_command() {
|
||||||
command -v "${1}" >/dev/null 2>&1
|
command -v "${1}" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
raw_sha256sum() {
|
||||||
|
local file="${1}"
|
||||||
|
if [[ -z "${file}" ]]; then
|
||||||
|
echo "Error: No file specified" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "${file}" ]]; then
|
||||||
|
echo "Error: File not found: ${file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have_command sha256sum ; then
|
||||||
|
sha256sum "${file}" | awk '{print $1}'
|
||||||
|
elif have_command shasum ; then
|
||||||
|
shasum -a 256 "${file}" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
echo "Error: Neither sha256sum nor shasum is available" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_group() {
|
||||||
|
if [[ "$(id -u)" -eq 0 ]] ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
have_command sudo && ( id -Gn | grep -q '\bsudo\b' )
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_sudo() {
|
||||||
|
if [[ "$(id -u)" -eq 0 ]] ; then
|
||||||
|
"$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! have_command sudo ; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sudo "$@"
|
||||||
|
}
|
||||||
|
|
||||||
link_directory_contents() {
|
link_directory_contents() {
|
||||||
local SRCDIR="${1}"
|
local SRCDIR="${1}"
|
||||||
@@ -43,7 +94,7 @@ ssh_key_already_installed() {
|
|||||||
fi
|
fi
|
||||||
# Extract the key data (field 2) from the key file, ignoring comments
|
# Extract the key data (field 2) from the key file, ignoring comments
|
||||||
local key_data
|
local key_data
|
||||||
key_data=$(awk '/^ssh-/ {print $2}' "$1")
|
key_data=$(awk '/^(ssh|ecdsa|sk)-/ {print $2}' "$1")
|
||||||
if [[ -z "${key_data}" ]]; then
|
if [[ -z "${key_data}" ]]; then
|
||||||
# Not a valid key file
|
# Not a valid key file
|
||||||
return 1
|
return 1
|
||||||
@@ -90,7 +141,7 @@ install_known_hosts() {
|
|||||||
verbose 'Installing known hosts...' >&2
|
verbose 'Installing known hosts...' >&2
|
||||||
local skel_hosts="${BASEDIR}/keys/known_hosts"
|
local skel_hosts="${BASEDIR}/keys/known_hosts"
|
||||||
local user_hosts="${HOME}/.ssh/known_hosts"
|
local user_hosts="${HOME}/.ssh/known_hosts"
|
||||||
local merge_script="${BASEDIR}/skeltools/merge_authorized_keys"
|
local merge_script="${BASEDIR}/skeltools/merge_known_hosts"
|
||||||
|
|
||||||
if [[ ! -f "${skel_hosts}" ]]; then
|
if [[ ! -f "${skel_hosts}" ]]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -176,6 +227,60 @@ install_dotfiles() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_starship() {
|
||||||
|
if have_command starship ; then return 0 ; fi
|
||||||
|
|
||||||
|
if have_command brew ; then
|
||||||
|
verbose "Attempting to install Starship via Homebrew..."
|
||||||
|
if brew install starship ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "brew install starship failed, trying other methods..." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have_command apt-get && sudo_group ; then
|
||||||
|
if maybe_sudo apt-get install -qy starship ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "apt-get install starship failed, installing locally" >&2
|
||||||
|
fi
|
||||||
|
local tmpd
|
||||||
|
tmpd="$(mktemp -d tmp.starship.XXXXXX)" || return 1
|
||||||
|
trap '[[ -n "${tmpd}" && -d "${tmpd}" ]] && rm -rf "${tmpd}"' EXIT
|
||||||
|
|
||||||
|
local install_path="${tmpd}/install.sh"
|
||||||
|
if have_command curl ; then
|
||||||
|
curl -sSL --show-error -o "${install_path}" https://starship.rs/install.sh
|
||||||
|
elif have_command wget ; then
|
||||||
|
wget -q -O "${install_path}" --https-only https://starship.rs/install.sh
|
||||||
|
else
|
||||||
|
echo "No curl or wget available!!" >&2
|
||||||
|
rm -rf "${tmpd}"
|
||||||
|
trap - EXIT
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local dl_hash
|
||||||
|
dl_hash="$(raw_sha256sum "${install_path}")"
|
||||||
|
if [[ "$dl_hash" != "${STARSHIP_INSTALL_HASH}" ]] ; then
|
||||||
|
echo "Hash check failed!!" >&2
|
||||||
|
echo "Expected: ${STARSHIP_INSTALL_HASH}, got ${dl_hash} on ${install_path}" >&2
|
||||||
|
rm -rf "${tmpd}"
|
||||||
|
trap - EXIT
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if sudo_group ; then
|
||||||
|
if maybe_sudo sh "${install_path}" ; then
|
||||||
|
rm -rf "${tmpd}"
|
||||||
|
trap - EXIT
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "root installation failed, falling back to user-local" >&2
|
||||||
|
fi
|
||||||
|
sh "${install_path}" -b "${LOCAL_BIN}"
|
||||||
|
rm -rf "${tmpd}"
|
||||||
|
trap - EXIT
|
||||||
|
}
|
||||||
|
|
||||||
install_main() {
|
install_main() {
|
||||||
if [[ -d "${BASEDIR}/.git" ]] && have_command git ; then
|
if [[ -d "${BASEDIR}/.git" ]] && have_command git ; then
|
||||||
if [[ -z "$(git -C "${BASEDIR}" status --porcelain)" ]]; then
|
if [[ -z "$(git -C "${BASEDIR}" status --porcelain)" ]]; then
|
||||||
@@ -185,6 +290,8 @@ install_main() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[[ "$MINIMAL" = 1 ]] || {
|
[[ "$MINIMAL" = 1 ]] || {
|
||||||
|
mkdir -p "${LOCAL_BIN}"
|
||||||
|
install_starship
|
||||||
|
|
||||||
# Install vim-plug if not already present
|
# Install vim-plug if not already present
|
||||||
local VIM_PLUG_URL="https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim"
|
local VIM_PLUG_URL="https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim"
|
||||||
@@ -199,22 +306,22 @@ install_main() {
|
|||||||
else
|
else
|
||||||
echo "Error: curl not found. Cannot install vim-plug." >&2
|
echo "Error: curl not found. Cannot install vim-plug." >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install TPM (Tmux Plugin Manager) if not already present
|
# Install TPM (Tmux Plugin Manager) if not already present
|
||||||
local TPM_DIR="${HOME}/.tmux/plugins/tpm"
|
local TPM_DIR="${HOME}/.tmux/plugins/tpm"
|
||||||
local TPM_REPO="https://github.com/tmux-plugins/tpm"
|
local TPM_REPO="https://github.com/tmux-plugins/tpm"
|
||||||
|
|
||||||
if [[ ! -d "${TPM_DIR}" ]]; then
|
if [[ ! -d "${TPM_DIR}" ]]; then
|
||||||
verbose "Installing TPM (Tmux Plugin Manager)..."
|
verbose "Installing TPM (Tmux Plugin Manager)..."
|
||||||
if have_command git; then
|
if have_command git; then
|
||||||
git clone --depth 1 "${TPM_REPO}" "${TPM_DIR}"
|
git clone --depth 1 "${TPM_REPO}" "${TPM_DIR}"
|
||||||
else
|
else
|
||||||
echo "Error: git not found. Cannot install TPM." >&2
|
echo "Error: git not found. Cannot install TPM." >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# try to update dotfile overlays
|
# try to update dotfile overlays
|
||||||
if [[ -d "${BASEDIR}/dotfile_overlays" ]] ; then
|
if [[ -d "${BASEDIR}/dotfile_overlays" ]] ; then
|
||||||
for dotfiledir in "${BASEDIR}/dotfile_overlays/"* ; do
|
for dotfiledir in "${BASEDIR}/dotfile_overlays/"* ; do
|
||||||
if [[ -d "${dotfiledir}/.git" ]] ; then
|
if [[ -d "${dotfiledir}/.git" ]] ; then
|
||||||
@@ -243,7 +350,7 @@ read_saved_prefs
|
|||||||
|
|
||||||
# Defaults if not passed in or saved.
|
# Defaults if not passed in or saved.
|
||||||
# TODO: use flags instead of environment variables.
|
# TODO: use flags instead of environment variables.
|
||||||
: ${BASEDIR:=$HOME/.skel}
|
: ${BASEDIR:=${SCRIPT_DIR}}
|
||||||
: ${MINIMAL:=0}
|
: ${MINIMAL:=0}
|
||||||
: ${INSTALL_KEYS:=1}
|
: ${INSTALL_KEYS:=1}
|
||||||
: ${TRUST_ALL_KEYS:=0}
|
: ${TRUST_ALL_KEYS:=0}
|
||||||
|
|||||||
Reference in New Issue
Block a user