resolve merge conflicts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-05-10 01:37:46 -04:00
commit 14931c541f
16 changed files with 1008 additions and 39 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Ignore the actual .bashrc_local with real passwords
dotfiles/.bashrc_local

View File

@ -29,6 +29,7 @@ After running `setup_env.sh`, activate plugins:
- **Tmux**: Press `prefix + I` (Ctrl-b + I) to install TPM plugins
- **Vim**: Run `:PluginInstall` inside vim to install Vundle plugins
### Git Operations
```bash
# Automated commit and push (defined in .bashrc)
@ -48,6 +49,7 @@ The repository uses **symlink-based configuration deployment**. When `setup_env.
All dotfiles are symlinked, not copied. Editing `~/.<file>` directly modifies files in the repository.
**Critical Files:**
- `dotfiles/.bashrc` - Main bash configuration with extensive aliases and functions
- `dotfiles/.bashrc_local` - Machine-specific environment variables (PG, MS connection strings)
@ -61,6 +63,8 @@ All dotfiles are symlinked, not copied. Editing `~/.<file>` directly modifies fi
### Custom Bash Workflow
The `.bashrc` contains a sophisticated workflow for working with:
**Database Query Management:**
PostgreSQL workflow:

View File

@ -1,9 +0,0 @@
#export IPTOKEN=
#export PG="psql -U ptrowbridge -d ubm -p 5432 -h usmidsap01"
#export MS="sqlcmd.exe -S mid-sql02 -i"
#export JAVA_HOME=/opt/jdk-19.0.1
#export PATH=$PATH:$JAVA_HOME/bin
#export PATH=$PATH:/opt/gradle/gradle-7.6/bin
#export RUNNER_PATH=/opt/runner/
#export DB2PW=
#export PGPW=

View File

@ -0,0 +1,44 @@
# .bashrc_local - Machine-specific environment variables
# Copy this file to .bashrc_local and fill in your actual values
# Token for IP services (if needed)
#export IPTOKEN=
# PostgreSQL connection string
export PG="psql -U username -d database -p 5432 -h hostname"
# SQL Server connection strings
export MS="sqlcmd -U username -C -S servername"
export MSC="sqlcmd -U username -S servername -C -s \| -W"
# Java and Gradle paths
export JAVA_HOME=/opt/jdk-20.0.1
export PATH=$PATH:$JAVA_HOME/bin
export PATH=$PATH:/opt/gradle/gradle-8.1/bin
export PATH=$PATH:/opt/mssql-tools18/bin
# Runner configuration path
export RUNNER_PATH=/opt/jrunner_conf/
# Database passwords (fill in your actual passwords)
export DB2PW=your_db2_password_here
export PGPW=your_postgres_password_here
export SQLCMDPASSWORD='your_sqlcmd_password_here'
# Windows SQL Server connection (if needed)
# export MSW="sqlcmd.exe -S servername -C "
# Alternative Java/Gradle versions (commented out)
#export JAVA_HOME=/opt/jdk-19.0.1
#export PATH=$PATH:$JAVA_HOME/bin
#export PATH=$PATH:/opt/gradle/gradle-7.6/bin
#export RUNNER_PATH=/opt/runner/
# Deno installation
export DENO_INSTALL="$HOME/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"
# NVM (Node Version Manager)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

View File

@ -1,9 +1,16 @@
-- Switch pagers with :x and :xx commands
\set x '\\setenv PAGER ''less -S'''
\set xx '\\setenv PAGER \'pspg -bX --no-mouse\''
\timing on
-- \timing on
\set QUIET 1
\pset linestyle unicode
-- \pset border 2
\pset null ∅
\unset QUIET
-- work with vd
-- \setenv PAGER 'vd -'
-- \pset format csv
-- \pset footer off
-- \setenv PSQL_PAGER_ALWAYS 1
-- \pset paget on

94
dotfiles/README.md Normal file
View File

@ -0,0 +1,94 @@
# dotfiles
Symlink-deployed config. The parent `~/setup_env/setup_env.sh` installs each file here to its destination path — edit the file in this repo, the change goes live.
| Source in this repo | Deployed to | Deployed by |
| --- | --- | --- |
| `.bashrc`, `.vimrc`, `.gitconfig`, `.pspgconf`, `.psqlrc`, `.tmux.conf`, `.bashrc_local` | `~/<filename>` | `deploy_configs` |
| `bin/*` | `~/.local/bin/<name>` | `deploy_bin` |
| `nvim/*.lua` | `~/.config/nvim/lua/<name>.lua` | `deploy_nvim` |
On a fresh machine: clone the `setup_env` repo, then from `~/setup_env/` run:
```bash
./install_neovim.sh
./install_nvchad.sh
./install_python3.sh
./setup_env.sh # creates all the symlinks + installs apt packages
```
After install you still need to add one line to the NvChad-provided `~/.config/nvim/lua/mappings.lua` (it's not tracked here because NvChad owns that file):
```lua
require("td_mappings")
```
Everything else picks up automatically.
---
## td — markdown todo time tracking
Track time on markdown todos identified by an Obsidian block-ref `^tid-*` at the end of the task line. Data lives in `time.csv` at the vault root; reports cross-join time with git history to show what was created vs. completed.
### The three surfaces (one script)
All logic is in `bin/td` — a Python 3 stdlib script. The other surfaces are thin wrappers so the script is reachable from each editing context.
| Surface | Location | Purpose |
| --- | --- | --- |
| Python script | `bin/td``~/.local/bin/td` | Single source of truth. All subcommands. |
| Shell wrappers | `.bashrc` (`tstart` / `tstop` / `treport` / `tweek`) | Bypass the `td` alias (see gotcha). |
| Nvim module | `nvim/td.lua` + `nvim/td_mappings.lua` | `:TdStart` / `:TdStop` / `:TdReport` / `:TdWeek` commands + `<leader>t{s,p,r,w}` keymaps. |
### Data model
`time.csv` at the vault root:
```
started_at, stopped_at, tid, file, description
```
- `tid` — block-ref identifier, format `tid-YYYYMMDD-HHMMSS`, appended as `^tid-...` to the task line in the markdown file
- `started_at` / `stopped_at` — ISO-8601 local timestamps; a running entry has an empty `stopped_at`
- one row per time segment; `td stop` fills in `stopped_at` on the open row
Vault root is discovered by walking up from the current buffer file looking for `time.csv` or `.obsidian/`.
### Subcommands
| Command | What it does |
| --- | --- |
| `td start <tid> [--file PATH] [--desc TEXT]` | Start a timer. Auto-stops any running entry first. If `--file` / `--desc` aren't given, greps the vault for the tid to populate them. |
| `td stop` | Close the open entry. |
| `td report [FILTER]` | Total time per tid (filter matches tid or file substring). |
| `td current` | Print the currently-running tid (or nothing). |
| `td tidgen` | Print a fresh `tid-YYYYMMDD-HHMMSS`. |
| `td week [--since DATE]` | Task create/complete events from `git log -p`, joined with `time.csv`. Default range: since Monday 00:00 of this week. |
### How `td week` works
Scans `git log -p --reverse --no-renames` from cwd, pairs `-`/`+` task-line diffs within each commit by tid, classifies each event:
- `[x]` **done**`- [ ]``- [x]` transition in one commit
- `[ ]` **new** — added `- [ ]` line with a fresh tid
- `[+]` **done+new**`- [x]` added with no prior `- [ ]` for that tid (created and completed in the same commit)
- `[o]` **reopen**`- [x]``- [ ]` transition
Event timestamp is commit time, not toggle time — batched commits share one timestamp. Fine for weekly "what did I get done" reports; not precise enough for hourly auditing.
### Nvim integration
`<leader>ts` / `:TdStart` on a `- [ ]` task line:
1. If the line has no `^tid-*` block ref, auto-generates `tid-YYYYMMDD-HHMMSS`, appends it, saves the buffer.
2. Starts the timer with the file path (relative to vault root) and the task text (minus checkbox + block ref) as description.
Other mappings: `<leader>tp` stop, `<leader>tr` report float, `<leader>tw` week float. `q` or `<Esc>` closes a float.
Known limitation: NvChad defers `mappings.lua` via `vim.schedule`, so `:TdStart` etc. aren't available inside `-c` arguments on headless invocations. Interactive use is fine.
### Gotchas
- **`td` alone is aliased to `rg`** (for the `td` / `tdp` / `tdo` todo-grep family, defined in `.bashrc`). So `td week` runs `rg "\- \[[^(x|~)]\]" week` and errors out. Use `tweek` (shell wrapper that calls `command td week`) or `command td week` directly.
- **CSV is cwd-scoped.** `$TD_LOG` defaults to `./time.csv`, so subcommands must be run from (or under) the vault root. The nvim wrapper handles this by walking up to find the vault root; the shell wrappers trust your cwd.
- **Data-model changes go in `bin/td`.** The shell and nvim surfaces should stay thin — if a new subcommand is useful in nvim too, add it to `bin/td`, then a one-line `tfoo()` wrapper in `.bashrc` and a `M.foo` + mapping in `nvim/td.lua` / `nvim/td_mappings.lua`.

228
dotfiles/bin/td Executable file
View File

@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""td — time-track markdown todos identified by ^tid block-refs.
Usage:
td start <tid> [--file PATH] [--desc TEXT]
td stop
td report [FILTER] # FILTER matches tid or file substring
td current # print the currently-running tid (or nothing)
td tidgen # print a new unique tid
td week [--since DATE] # task create/complete events from git log, joined with time.csv
Log location: $TD_LOG (default ./time.csv)
"""
import argparse, csv, os, re, subprocess, sys
from datetime import datetime, timedelta
from collections import defaultdict
LOG = os.environ.get("TD_LOG", "./time.csv")
FIELDS = ["started_at", "stopped_at", "tid", "file", "description"]
def now():
return datetime.now().isoformat(timespec="seconds")
def read_rows():
if not os.path.exists(LOG):
return []
with open(LOG, newline="") as f:
return list(csv.DictReader(f))
def write_rows(rows):
with open(LOG, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=FIELDS)
w.writeheader()
w.writerows(rows)
def lookup_task(tid):
try:
out = subprocess.check_output(
["rg", "--no-heading", "--with-filename", rf"\^{tid}\b", "."],
text=True, stderr=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, FileNotFoundError):
return "", ""
first = out.splitlines()[0] if out else ""
if not first:
return "", ""
path, _, line = first.partition(":")
file = path.removeprefix("./")
desc = re.sub(r"^\s*-\s*\[.\]\s*", "", line)
desc = re.sub(r"\s*\^\S+\s*$", "", desc).strip()
return file, desc
def cmd_start(args):
rows = read_rows()
ts = now()
stopped = [r["tid"] for r in rows if not r["stopped_at"]]
for r in rows:
if not r["stopped_at"]:
r["stopped_at"] = ts
file, desc = args.file or "", args.desc or ""
if not (file or desc):
file, desc = lookup_task(args.tid)
if not file:
print(f"warning: ^{args.tid} not found in {os.getcwd()} — logging with empty file/desc", file=sys.stderr)
rows.append({"started_at": ts, "stopped_at": "", "tid": args.tid, "file": file, "description": desc})
write_rows(rows)
if stopped:
print(f" stopped {', '.join(stopped)} first")
snippet = f" ({file}: {desc[:50]})" if desc else ""
print(f"started {args.tid}{snippet}")
def cmd_stop(args):
rows = read_rows()
if not rows:
print(f"no log at {LOG}"); sys.exit(1)
ts = now()
stopped = []
for r in rows:
if not r["stopped_at"]:
r["stopped_at"] = ts
stopped.append(r["tid"])
if not stopped:
print("nothing running"); sys.exit(1)
write_rows(rows)
print(f"stopped {', '.join(stopped)}")
def cmd_report(args):
rows = read_rows()
if not rows:
print(f"no log at {LOG}"); sys.exit(1)
totals, files, running = defaultdict(int), {}, []
for r in rows:
if args.filter and args.filter not in r["tid"] and args.filter not in r["file"]:
continue
start = datetime.fromisoformat(r["started_at"])
files[r["tid"]] = r["file"]
if r["stopped_at"]:
stop = datetime.fromisoformat(r["stopped_at"])
totals[r["tid"]] += int((stop - start).total_seconds())
else:
running.append((r["tid"], start, r["file"], r["description"]))
for tid in sorted(totals):
t = totals[tid]
h, m = t // 3600, (t % 3600) // 60
print(f"{tid:<24} {h:>3}h{m:02d}m {files.get(tid,'')}")
for tid, start, file, desc in running:
print(f"{tid:<24} RUNNING since {start.strftime('%H:%M')} {file}: {desc[:40]}")
def cmd_current(args):
for r in read_rows():
if not r["stopped_at"]:
print(r["tid"]); return
def cmd_tidgen(args):
print(f"tid-{datetime.now().strftime('%Y%m%d-%H%M%S')}")
TID_RE = re.compile(r"\^(tid-[\w-]+)")
TASK_ADD_RE = re.compile(r"^\+(?!\+\+)\s*-\s*\[([ xX])\]\s*(.*)$")
TASK_REM_RE = re.compile(r"^-(?!--)\s*-\s*\[([ xX])\]\s*(.*)$")
def _tid_totals():
totals = defaultdict(int)
for r in read_rows():
if r["stopped_at"]:
start = datetime.fromisoformat(r["started_at"])
stop = datetime.fromisoformat(r["stopped_at"])
totals[r["tid"]] += int((stop - start).total_seconds())
return totals
def _scan_git(since):
SEP = "---TDWEEK-COMMIT---"
try:
out = subprocess.check_output(
["git", "log", f"--since={since}", "--reverse",
f"--format={SEP}%n%cI", "-p", "--no-color", "--no-renames"],
text=True, stderr=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, FileNotFoundError):
return
for chunk in out.split(SEP + "\n"):
if not chunk.strip():
continue
lines = chunk.split("\n")
ts = lines[0]
current_file = None
adds, removes = [], []
for ln in lines[1:]:
if ln.startswith("+++ b/"):
current_file = ln[6:].split("\t")[0].rstrip(); continue
if ln.startswith("--- ") or ln.startswith("diff --git") or ln.startswith("index ") or ln.startswith("@@"):
continue
if not current_file or current_file == "/dev/null":
continue
m = TASK_ADD_RE.match(ln)
if m:
status = m.group(1).lower().strip() or " "
tm = TID_RE.search(m.group(2))
if tm:
desc = TID_RE.sub("", m.group(2)).strip()
adds.append((current_file, status, tm.group(1), desc))
continue
m = TASK_REM_RE.match(ln)
if m:
status = m.group(1).lower().strip() or " "
tm = TID_RE.search(m.group(2))
if tm:
removes.append((current_file, status, tm.group(1)))
rem_by_tid = {t[2]: t for t in removes}
for file, status, tid, desc in adds:
prior = rem_by_tid.get(tid)
if prior:
if prior[1] == " " and status == "x":
yield (ts, "done", tid, file, desc)
elif prior[1] == "x" and status == " ":
yield (ts, "reopen", tid, file, desc)
else:
yield (ts, "done+new" if status == "x" else "new", tid, file, desc)
def _default_since():
today = datetime.now()
monday = today - timedelta(days=today.weekday())
return monday.strftime("%Y-%m-%d 00:00")
def _fmt_dur(secs):
return f"{secs // 3600}h{(secs % 3600) // 60:02d}m"
def cmd_week(args):
since = args.since or _default_since()
events = list(_scan_git(since))
totals = _tid_totals()
if not events:
print(f"no task events since {since}"); return
by_day = defaultdict(list)
for ts, kind, tid, file, desc in events:
day = ts[:10]
by_day[day].append((ts, kind, tid, file, desc))
marker = {"done": "[x]", "new": "[ ]", "done+new": "[+]", "reopen": "[o]"}
n_done = n_new = total_secs = 0
counted_tids = set()
print(f"Week since {since}\n")
for day in sorted(by_day):
dt = datetime.fromisoformat(day)
print(f"{dt.strftime('%a %Y-%m-%d')}")
for ts, kind, tid, file, desc in by_day[day]:
t = totals.get(tid, 0)
dur = _fmt_dur(t) if t else " "
print(f" {marker.get(kind,'? ')} {tid:<24} {dur:>6} {file} — {desc[:60]}")
if kind in ("done", "done+new"): n_done += 1
if kind in ("new", "done+new"): n_new += 1
if tid not in counted_tids:
counted_tids.add(tid); total_secs += t
print()
print(f"Totals: completed {n_done} created {n_new} time {_fmt_dur(total_secs)}")
def main():
p = argparse.ArgumentParser(prog="td", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
sp = p.add_subparsers(dest="cmd", required=True)
s = sp.add_parser("start"); s.add_argument("tid"); s.add_argument("--file", default=""); s.add_argument("--desc", default=""); s.set_defaults(func=cmd_start)
sp.add_parser("stop").set_defaults(func=cmd_stop)
s = sp.add_parser("report"); s.add_argument("filter", nargs="?"); s.set_defaults(func=cmd_report)
sp.add_parser("current").set_defaults(func=cmd_current)
sp.add_parser("tidgen").set_defaults(func=cmd_tidgen)
s = sp.add_parser("week"); s.add_argument("--since", default=None); s.set_defaults(func=cmd_week)
args = p.parse_args()
args.func(args)
if __name__ == "__main__":
main()

119
dotfiles/nvim/td.lua Normal file
View File

@ -0,0 +1,119 @@
local M = {}
local function vault_root()
local file = vim.api.nvim_buf_get_name(0)
local dir = file ~= "" and vim.fn.fnamemodify(file, ":p:h") or vim.fn.getcwd()
local probe = dir
while probe ~= "/" and probe ~= "" do
if vim.loop.fs_stat(probe .. "/time.csv") or vim.loop.fs_stat(probe .. "/.obsidian") then
return probe
end
local parent = vim.fn.fnamemodify(probe, ":h")
if parent == probe then break end
probe = parent
end
return vim.fn.getcwd()
end
local function relpath(abs, base)
local a = vim.fn.fnamemodify(abs, ":p")
local b = vim.fn.fnamemodify(base, ":p"):gsub("/$", "")
if a:sub(1, #b + 1) == b .. "/" then return a:sub(#b + 2) end
return a
end
local function run(cmd, cwd, on_done)
local stdout, stderr = {}, {}
vim.fn.jobstart(cmd, {
cwd = cwd,
stdout_buffered = true,
stderr_buffered = true,
on_stdout = function(_, d) for _, l in ipairs(d) do if l ~= "" then stdout[#stdout + 1] = l end end end,
on_stderr = function(_, d) for _, l in ipairs(d) do if l ~= "" then stderr[#stderr + 1] = l end end end,
on_exit = function(_, code)
vim.schedule(function() if on_done then on_done(stdout, stderr, code) end end)
end,
})
end
local function notify(lines, level)
if #lines == 0 then return end
vim.notify(table.concat(lines, "\n"), level or vim.log.levels.INFO)
end
local function extract_desc(line)
return (line:gsub("^%s*%-%s*%[.%]%s*", ""):gsub("%s*%^%S+%s*$", ""):gsub("%s+$", ""))
end
local function ensure_tid_on_line()
local line = vim.api.nvim_get_current_line()
local tid = line:match("%^(tid%-%S+)")
if tid then return tid end
if not line:match("^%s*%-%s*%[.%]") then
vim.notify("td: not on a todo line (- [ ] ...)", vim.log.levels.WARN)
return nil
end
tid = os.date("tid-%Y%m%d-%H%M%S")
local new = line:gsub("%s+$", "") .. " ^" .. tid
vim.api.nvim_set_current_line(new)
vim.cmd("silent! write")
return tid
end
function M.start()
local tid = ensure_tid_on_line()
if not tid then return end
local cwd = vault_root()
local file = relpath(vim.api.nvim_buf_get_name(0), cwd)
local desc = extract_desc(vim.api.nvim_get_current_line())
run({ "td", "start", tid, "--file", file, "--desc", desc }, cwd, function(out, err)
notify(out); notify(err, vim.log.levels.WARN)
end)
end
function M.stop()
run({ "td", "stop" }, vault_root(), function(out, err)
notify(out); notify(err, vim.log.levels.WARN)
end)
end
local function float(title, lines)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buf, "modifiable", false)
local width = math.min(120, vim.o.columns - 4)
local height = math.min(30, #lines + 2)
vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
row = math.floor((vim.o.lines - height) / 2),
col = math.floor((vim.o.columns - width) / 2),
style = "minimal",
border = "rounded",
title = " " .. title .. " ",
title_pos = "center",
})
vim.api.nvim_buf_set_keymap(buf, "n", "q", "<cmd>close<cr>", { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(buf, "n", "<Esc>", "<cmd>close<cr>", { noremap = true, silent = true })
end
function M.report()
run({ "td", "report" }, vault_root(), function(out, err)
if #out == 0 then notify(err, vim.log.levels.WARN); return end
float("td report", out)
end)
end
function M.week(opts)
local cmd = { "td", "week" }
if opts and opts.args and opts.args ~= "" then
for w in opts.args:gmatch("%S+") do cmd[#cmd + 1] = w end
end
run(cmd, vault_root(), function(out, err)
if #out == 0 then notify(err, vim.log.levels.WARN); return end
float("td week", out)
end)
end
return M

View File

@ -0,0 +1,11 @@
-- td: time-track markdown todos (logic lives in ~/.local/bin/td and td.lua)
-- Loaded from ~/.config/nvim/lua/mappings.lua via require("td_mappings").
local td = require("td")
vim.api.nvim_create_user_command("TdStart", td.start, {})
vim.api.nvim_create_user_command("TdStop", td.stop, {})
vim.api.nvim_create_user_command("TdReport", td.report, {})
vim.api.nvim_create_user_command("TdWeek", td.week, { nargs = "*" })
vim.keymap.set("n", "<leader>ts", td.start, { desc = "td: start timer on current task" })
vim.keymap.set("n", "<leader>tp", td.stop, { desc = "td: stop (pause) timer" })
vim.keymap.set("n", "<leader>tr", td.report, { desc = "td: report floating window" })
vim.keymap.set("n", "<leader>tw", td.week, { desc = "td: week (tasks from git log)" })

View File

@ -1,3 +1,81 @@
sudo apt install zip
#!/bin/bash
set -euo pipefail
curl -s "https://get.sdkman.io" | bash
echo "============================================"
echo "SDKMAN (Java Development) Installation Script"
echo "============================================"
echo ""
echo "This script will run the following commands with sudo:"
echo " - apt-get update"
echo " - apt-get install -y zip unzip curl"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
echo ""
echo "Starting installation (commands will be shown as they run)..."
echo ""
# Enable command tracing
set -x
# Install prerequisites
sudo apt-get update
sudo apt-get install -y zip unzip curl
set +x
# Check if SDKMAN is already installed
if [ -d "$HOME/.sdkman" ]; then
echo ""
echo "============================================"
echo "SDKMAN is already installed at ~/.sdkman"
echo "To update SDKMAN, run: sdk selfupdate"
echo "============================================"
exit 0
fi
# Install SDKMAN
echo "Installing SDKMAN..."
set -x
if ! curl -s "https://get.sdkman.io" | bash; then
set +x
echo "Error: SDKMAN installation failed" >&2
exit 1
fi
set +x
# Source SDKMAN to make it available in current session
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
echo ""
echo "============================================"
# Verify installation
if [ -d "$HOME/.sdkman" ] && [ -f "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
echo "SDKMAN installed successfully!"
echo ""
echo "To start using SDKMAN, either:"
echo " 1. Restart your shell, or"
echo " 2. Run: source ~/.bashrc"
echo ""
echo "Then you can install Java with:"
echo " sdk list java # List available Java versions"
echo " sdk install java # Install latest Java"
echo " sdk install java 21.0.1-tem # Install specific version"
echo ""
echo "Other useful SDKMAN commands:"
echo " sdk install gradle # Install Gradle"
echo " sdk install maven # Install Maven"
echo " sdk list # List all available SDKs"
else
echo "Error: SDKMAN installation verification failed" >&2
exit 1
fi
echo "============================================"

View File

@ -1,5 +1,79 @@
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
sudo rm -rf /opt/nvim
#!/bin/bash
set -euo pipefail
echo "============================================"
echo "Neovim Latest Installation Script"
echo "============================================"
echo ""
echo "This script will run the following commands with sudo:"
echo " - apt-get update (if curl not installed)"
echo " - apt-get install -y curl (if needed)"
echo " - rm -rf /opt/nvim-linux64 (if old installation exists)"
echo " - tar -C /opt -xzf nvim-linux64.tar.gz"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
echo ""
echo "Starting installation (commands will be shown as they run)..."
echo ""
# Enable command tracing
set -x
# Check for required tools
if ! command -v curl &> /dev/null; then
sudo apt-get update
sudo apt-get install -y curl
fi
# Disable tracing temporarily for cleaner download messages
set +x
# Download latest Neovim
echo "Downloading Neovim..."
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
if ! curl -fsSL -o nvim-linux64.tar.gz https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz; then
echo "Error: Failed to download Neovim" >&2
rm -rf "$TEMP_DIR"
exit 1
fi
echo "Download complete."
set -x
# Remove old installation if it exists
if [ -d /opt/nvim-linux64 ]; then
sudo rm -rf /opt/nvim-linux64
fi
# Extract to /opt
sudo tar -C /opt -xzf nvim-linux64.tar.gz
export PATH="$PATH:/opt/nvim-linux64/bin"
set +x
# Clean up
cd - > /dev/null
rm -rf "$TEMP_DIR"
echo ""
echo "============================================"
# Verify installation
if [ -x /opt/nvim-linux64/bin/nvim ]; then
echo "Neovim installed successfully!"
/opt/nvim-linux64/bin/nvim --version | head -n1
echo ""
echo "Neovim is installed at: /opt/nvim-linux64/bin/nvim"
echo "Add to PATH by adding this to your ~/.bashrc:"
echo ' export PATH="$PATH:/opt/nvim-linux64/bin"'
else
echo "Error: Neovim installation failed" >&2
exit 1
fi
echo "============================================"

113
install_nvchad.sh Executable file
View File

@ -0,0 +1,113 @@
#!/bin/bash
set -euo pipefail
echo "============================================"
echo "NvChad Configuration Installation Script"
echo "============================================"
echo ""
echo "This script will:"
echo " - Backup existing ~/.config/nvim to ~/.config/nvim.backup (if exists)"
echo " - Clone your NvChad config from git@gitea.hptrow.me:pt/nvchad.git (customize branch)"
echo " - Launch nvim to auto-install lazy.nvim and all plugins"
echo ""
echo "Prerequisites:"
echo " - Neovim 0.9.5+ must be installed (run ./install_neovim.sh if needed)"
echo " - Git must be installed"
echo " - SSH key must be set up for gitea.hptrow.me"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
echo ""
echo "Starting installation..."
echo ""
# Check prerequisites
if ! command -v nvim &> /dev/null; then
echo "Error: Neovim is not installed or not in PATH" >&2
echo "Please run ./install_neovim.sh first" >&2
exit 1
fi
if ! command -v git &> /dev/null; then
echo "Error: Git is not installed" >&2
exit 1
fi
# Verify Neovim version
NVIM_VERSION=$(nvim --version | head -n1 | grep -oP 'v\K[0-9]+\.[0-9]+' || echo "0.0")
REQUIRED_VERSION="0.9"
if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$NVIM_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then
echo "Error: Neovim version $NVIM_VERSION is too old (need 0.9.5+)" >&2
exit 1
fi
echo "Neovim version: $(nvim --version | head -n1)"
echo ""
# Backup existing config if it exists
if [ -d ~/.config/nvim ]; then
echo "Backing up existing ~/.config/nvim to ~/.config/nvim.backup"
if [ -d ~/.config/nvim.backup ]; then
rm -rf ~/.config/nvim.backup
fi
mv ~/.config/nvim ~/.config/nvim.backup
fi
# Clone the config
echo "Cloning NvChad config from gitea (customize branch)..."
set -x
git clone -b customize git@gitea.hptrow.me:pt/nvchad.git ~/.config/nvim
set +x
if [ ! -d ~/.config/nvim ]; then
echo "Error: Failed to clone config" >&2
exit 1
fi
echo ""
echo "Config cloned successfully!"
echo ""
echo "============================================"
echo "First Launch Setup"
echo "============================================"
echo ""
echo "Neovim will now launch and automatically:"
echo " 1. Bootstrap lazy.nvim plugin manager"
echo " 2. Install NvChad (as a plugin)"
echo " 3. Install all configured plugins"
echo ""
echo "This may take a few minutes on first run."
echo "After installation completes, close nvim with :q"
echo ""
read -p "Press Enter to launch nvim and complete setup..." -r
echo ""
# Launch nvim to trigger plugin installation
nvim +q
echo ""
echo "============================================"
echo "Installation Complete!"
echo "============================================"
echo ""
echo "Your NvChad configuration is installed at: ~/.config/nvim"
echo ""
echo "Key customizations in this config:"
echo " - Theme: vscode_dark"
echo " - Tab width: 4 spaces"
echo " - Obsidian.nvim integration"
echo " - SQL development tools (pg_format, sqlfluff)"
echo " - Mason for installing formatters"
echo " - Custom keybindings: g+w (format), <leader>gb (blame), more in lua/mappings.lua"
echo ""
echo "Next time you launch nvim, everything will be ready!"
echo ""
if [ -d ~/.config/nvim.backup ]; then
echo "Note: Your old config was backed up to ~/.config/nvim.backup"
fi
echo "============================================"

View File

@ -1,12 +1,61 @@
# Create the file repository configuration:
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
#!/bin/bash
set -euo pipefail
# Import the repository signing key:
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
echo "============================================"
echo "PostgreSQL Installation Script"
echo "============================================"
echo ""
echo "This script will run the following commands with sudo:"
echo " - mkdir -p /etc/apt/keyrings"
echo " - curl ... | gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg"
echo " - tee /etc/apt/sources.list.d/pgdg.list"
echo " - apt-get update"
echo " - apt-get install -y postgresql"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
# Update the package lists:
echo ""
echo "Starting installation (commands will be shown as they run)..."
echo ""
# Enable command tracing
set -x
# Create directory for keyrings if it doesn't exist
sudo mkdir -p /etc/apt/keyrings
# Download and install the PostgreSQL GPG key
if [ ! -f /etc/apt/keyrings/postgresql.gpg ]; then
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
sudo gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg
fi
# Add PostgreSQL repository
echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | \
sudo tee /etc/apt/sources.list.d/pgdg.list > /dev/null
# Update package lists
sudo apt-get update
# Install the latest version of PostgreSQL.
# If you want a specific version, use 'postgresql-12' or similar instead of 'postgresql':
sudo apt-get -y install postgresql
# Install latest PostgreSQL
sudo apt-get install -y postgresql
# Disable command tracing for cleaner output
set +x
echo ""
echo "============================================"
# Verify installation
if command -v psql &> /dev/null; then
echo "PostgreSQL installed successfully!"
psql --version
else
echo "Error: PostgreSQL installation failed" >&2
exit 1
fi
echo "============================================"

View File

@ -1,24 +1,83 @@
#!/bin/bash
set -euo pipefail
# Update the package list
echo "============================================"
echo "Python 3 Latest Installation Script"
echo "============================================"
echo ""
echo "This script will run the following commands with sudo:"
echo " - apt-get update"
echo " - apt-get install -y software-properties-common"
echo " - add-apt-repository -y ppa:deadsnakes/ppa"
echo " - apt-get update"
echo " - apt-get install -y python3.X python3.X-venv python3.X-dev python3.X-distutils"
echo " - <latest-python> (to install pip via get-pip.py)"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
echo ""
echo "Starting installation (commands will be shown as they run)..."
echo ""
# Enable command tracing
set -x
# Install prerequisites
sudo apt-get update
sudo apt-get install -y software-properties-common
# Add deadsnakes PPA
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update
# Install the software-properties-common package
sudo apt-get install software-properties-common
# Disable tracing temporarily to find latest version cleanly
set +x
# Add the deadsnakes PPA to the sources list
sudo add-apt-repository ppa:deadsnakes/ppa
# Find the latest Python 3 version available
echo "Finding latest Python 3 version..."
LATEST_PYTHON=$(apt-cache search --names-only '^python3\.[0-9]+$' | \
grep -oP 'python3\.\d+' | \
sort -V | \
tail -1)
# Update the package list again
sudo apt-get update
if [ -z "$LATEST_PYTHON" ]; then
echo "Error: Could not determine latest Python 3 version" >&2
exit 1
fi
# Check the latest version of Python 3 available
latest_version=$(apt-cache madison python3 | awk '{print $3}' | grep "^3\." | sort -V | tail -1)
echo "Installing $LATEST_PYTHON..."
set -x
# Install the latest version of Python 3
sudo apt-get install -y python3=$latest_version
sudo apt-get install -y \
"$LATEST_PYTHON" \
"$LATEST_PYTHON-venv" \
"$LATEST_PYTHON-dev" \
"$LATEST_PYTHON-distutils"
# Verify the installation
python3 --version
which python3
# Install pip for the new Python version
curl -sS https://bootstrap.pypa.io/get-pip.py | sudo "$LATEST_PYTHON"
# Disable command tracing for cleaner output
set +x
echo ""
echo "============================================"
# Verify installation
if command -v "$LATEST_PYTHON" &> /dev/null; then
echo "$LATEST_PYTHON installed successfully!"
"$LATEST_PYTHON" --version
"$LATEST_PYTHON" -m pip --version
else
echo "Error: $LATEST_PYTHON installation failed" >&2
exit 1
fi
echo ""
echo "Note: Use '$LATEST_PYTHON' to run this version"
echo "To make it the default python3, run:"
echo " sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/$LATEST_PYTHON 1"
echo "============================================"

View File

@ -1,3 +1,68 @@
pip3 install visidata
#!/bin/bash
set -euo pipefail
# https://www.visidata.org/install/
echo "============================================"
echo "VisiData Installation Script"
echo "============================================"
echo ""
echo "This script will run the following commands with sudo:"
echo " - apt-get update (if pipx not installed)"
echo " - apt-get install -y pipx (if needed)"
echo ""
read -p "Continue with installation? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Installation cancelled."
exit 0
fi
echo ""
echo "Starting installation (commands will be shown as they run)..."
echo ""
# Enable command tracing
set -x
# Check if python3 is available
if ! command -v python3 &> /dev/null; then
set +x
echo "Error: python3 is not installed. Please install Python 3 first." >&2
exit 1
fi
# Install pipx if not available (recommended way to install VisiData)
if ! command -v pipx &> /dev/null; then
sudo apt-get update
sudo apt-get install -y pipx
set +x
pipx ensurepath
set -x
fi
set +x
# Install VisiData using pipx
echo "Installing VisiData via pipx..."
set -x
pipx install visidata
# Disable command tracing for cleaner output
set +x
echo ""
echo "============================================"
# Verify installation
if command -v vd &> /dev/null; then
echo "VisiData installed successfully!"
vd --version
echo ""
echo "Note: If 'vd' command is not found, you may need to:"
echo " 1. Restart your shell, or"
echo " 2. Run: source ~/.bashrc"
else
echo "Warning: VisiData installed but 'vd' command not in PATH" >&2
echo "You may need to restart your shell or run: source ~/.bashrc" >&2
fi
echo ""
echo "Documentation: https://www.visidata.org/"
echo "============================================"

View File

@ -65,6 +65,35 @@ deploy_configs() {
source ~/.bashrc
}
# Deploy executable scripts from dotfiles/bin/ into ~/.local/bin/
deploy_bin() {
echo "Deploying scripts to ~/.local/bin/ ..."
local src_dir="$(pwd)/dotfiles/bin"
[[ -d "$src_dir" ]] || return 0
mkdir -p ~/.local/bin
for script in "$src_dir"/*; do
[[ -f "$script" ]] || continue
create_symlink "$script" ~/.local/bin/"$(basename "$script")"
done
}
# Deploy nvim lua modules from dotfiles/nvim/ into ~/.config/nvim/lua/
# NOTE: requires ~/.config/nvim/ to already exist (see install_neovim.sh + install_nvchad.sh).
# Modules are required by name from ~/.config/nvim/lua/mappings.lua — e.g. require("td_mappings").
deploy_nvim() {
echo "Deploying nvim lua modules to ~/.config/nvim/lua/ ..."
local src_dir="$(pwd)/dotfiles/nvim"
[[ -d "$src_dir" ]] || return 0
if [[ ! -d ~/.config/nvim/lua ]]; then
echo " ~/.config/nvim/lua missing — run install_neovim.sh + install_nvchad.sh first. Skipping."
return 0
fi
for mod in "$src_dir"/*.lua; do
[[ -f "$mod" ]] || continue
create_symlink "$mod" ~/.config/nvim/lua/"$(basename "$mod")"
done
}
# Main script
main() {
install_packages
@ -72,6 +101,8 @@ main() {
install_vundle
install_git_bash_prompt
deploy_configs
deploy_bin
deploy_nvim
echo "Setup complete! Please restart your shell or run 'source ~/.bashrc' for changes to take effect."
}