-
Notifications
You must be signed in to change notification settings - Fork 10
Add bare metal Mac installation and run process to remove admin requirements #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
fbe8859
a241a5a
29768d3
e588ba6
3b9ee45
42a5c9e
71cbf53
1cce403
d5565e0
af2ce5b
7357d6f
6ef0557
0a4e57a
c423f08
0b93f90
d43e76a
870c8f3
4d2238c
82a30e0
973d62a
f2de8d1
d43ee19
c34627c
db58e82
b28028b
7db6514
aea09ee
0a1cc41
7e48340
e84c596
1f16474
2267461
44045c0
ad697c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.11.15 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if compareNgrams should just be compiled by default. Adding Go as a dependency is not a huge ask anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember, i have to get back in and check.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's because I made the edit. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,10 @@ | ||
| ## DATABASE SETTINGS ## | ||
| [WEB_APP] | ||
| ## Directory where generated web apps are written; irrelevant if you always pass --skip_web_app | ||
| web_app_path = /var/www/html/text-pair | ||
| ## Base URL where the TextPAIR API is served | ||
| api_server = http://localhost/text-pair-api | ||
|
|
||
| [DATABASE] | ||
| database_name = textpair | ||
| database_user = textpair | ||
| database_password = |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,317 @@ | ||
| #!/bin/bash | ||
| # TextPAIR Mac Bare-Metal Install Script | ||
| # For use without Docker, without web app | ||
| # Forked from ARTFL-Project/text-pair | ||
| # | ||
| # Usage: ./install-mac.sh | ||
|
|
||
| set -e | ||
|
|
||
| RED='\033[0;31m' | ||
| GREEN='\033[0;32m' | ||
| YELLOW='\033[1;33m' | ||
| NC='\033[0m' # No Color | ||
|
|
||
| echo -e "${GREEN}TextPAIR Mac Bare-Metal Installer${NC}" | ||
| echo "==================================" | ||
| echo "" | ||
|
|
||
| # ============================================================================= | ||
| # PATCH PHILOLOGIC WC -L (macOS compatibility) | ||
| # ============================================================================= | ||
| patch_philologic_wc() { | ||
| echo "Patching PhiloLogic line_count.py for macOS..." | ||
|
|
||
| # Find the philologic installation | ||
| local philologic_path=$("$PYTHON_BIN" -c "import philologic; print(philologic.__path__[0])" 2>/dev/null) | ||
|
|
||
| if [ -z "$philologic_path" ]; then | ||
| echo -e "${YELLOW} PhiloLogic not yet installed, will patch after pip install${NC}" | ||
| return 0 | ||
| fi | ||
|
|
||
| local line_count_file="${philologic_path}/utils/line_count.py" | ||
|
|
||
| if [ -f "$line_count_file" ]; then | ||
| # Check if already patched | ||
| if grep -q 'wc -l < {file_path}' "$line_count_file"; then | ||
| echo " Already patched" | ||
| else | ||
| # Rewrite the entire file - the upstream non-lz4 branch is broken | ||
| # (runs cut on empty stdin instead of wc -l on the file) | ||
| cat > /tmp/_line_count_patch.py << 'PATCH' | ||
| #!/usr/bin/env python3 | ||
| """Count number of lines in a file using subprocess module.""" | ||
| import subprocess | ||
| def count_lines(file_path, lz4=False): | ||
| """Count number of lines in a file.""" | ||
| if lz4: | ||
| cmd = f"lz4 -dc {file_path} | wc -l" | ||
| else: | ||
| cmd = f"wc -l < {file_path}" | ||
| process = subprocess.run(cmd, shell=True, text=True, capture_output=True) | ||
| count = int(process.stdout.strip()) | ||
| return count | ||
| PATCH | ||
| sudo cp /tmp/_line_count_patch.py "$line_count_file" | ||
| rm /tmp/_line_count_patch.py | ||
| echo " Patched: rewrote line_count.py (upstream non-lz4 branch was broken)" | ||
| fi | ||
| else | ||
| echo -e "${YELLOW} line_count.py not found at expected path${NC}" | ||
| fi | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # ARCHITECTURE CHECK | ||
| # ============================================================================= | ||
| check_architecture() { | ||
| local arch=$(uname -m) | ||
| echo "Checking architecture..." | ||
| echo " Detected: $arch" | ||
|
|
||
| if [ "$arch" == "arm64" ]; then | ||
| BINARY_ARCH="aarch64" | ||
| echo " Binary: aarch64 (Apple Silicon)" | ||
| elif [ "$arch" == "x86_64" ]; then | ||
| BINARY_ARCH="x86_64" | ||
| echo " Binary: x86_64 (Intel)" | ||
| else | ||
| echo -e "${RED}Unsupported architecture: $arch${NC}" | ||
| exit 1 | ||
| fi | ||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # DEPENDENCY CHECK | ||
| # ============================================================================= | ||
| check_dependencies() { | ||
| echo "Checking dependencies..." | ||
|
|
||
| # Homebrew (required to auto-install pyenv/Go below) | ||
| if command -v brew &> /dev/null; then | ||
| echo " Homebrew: found" | ||
| else | ||
| echo -e "${RED} ERROR: Homebrew not found. TextPAIR needs it to install pyenv and the Go toolchain.${NC}" | ||
| echo -e "${YELLOW} Install Homebrew first, then re-run this script:${NC}" | ||
| echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' | ||
| exit 1 | ||
| fi | ||
|
|
||
| # pyenv (guarantees a consistent, correct Python 3.11 regardless of whatever | ||
| # python3 happens to be on the ambient PATH - relying on system/Homebrew python3 | ||
| # directly is what let a Python 3.9 slip past the old version check below) | ||
| if command -v pyenv &> /dev/null; then | ||
| echo " pyenv: found" | ||
| else | ||
| echo " pyenv: not found, installing with Homebrew..." | ||
| brew install pyenv | ||
| echo " pyenv installed" | ||
| fi | ||
|
|
||
| # Resolve the latest available Python 3.11.x via pyenv, install it if missing, and | ||
| # pin this directory to it (writes .python-version) so python3/pip here always | ||
| # resolve to 3.11, regardless of the system's default python3. | ||
| TARGET_PY_VERSION=$(pyenv install --list | grep -E '^\s*3\.11\.[0-9]+$' | tail -1 | xargs) | ||
| if [ -z "$TARGET_PY_VERSION" ]; then | ||
| echo -e "${RED} ERROR: could not find a Python 3.11.x version via pyenv${NC}" | ||
| exit 1 | ||
| fi | ||
| if ! pyenv versions --bare | grep -qx "$TARGET_PY_VERSION"; then | ||
| echo " Installing Python $TARGET_PY_VERSION via pyenv (this can take a few minutes)..." | ||
| pyenv install "$TARGET_PY_VERSION" | ||
| fi | ||
| pyenv local "$TARGET_PY_VERSION" | ||
| PYTHON_BIN="$(pyenv root)/versions/$TARGET_PY_VERSION/bin/python3" | ||
| echo " Python: $("$PYTHON_BIN" --version) (pyenv, pinned to this directory via .python-version)" | ||
|
|
||
| # Confirm pyenv's shims are wired into the user's shell so `textpair`/`python3` keep | ||
| # resolving to this pinned version in future terminal sessions, not just this script run. | ||
| local shell_rc="" | ||
| case "$SHELL" in | ||
| */zsh) shell_rc="$HOME/.zshrc" ;; | ||
| */bash) shell_rc="$HOME/.bash_profile" ;; | ||
| *) shell_rc="$HOME/.profile" ;; | ||
| esac | ||
| if [ -f "$shell_rc" ] && grep -q 'pyenv init' "$shell_rc"; then | ||
| echo " pyenv shell integration: found in $shell_rc" | ||
| else | ||
| echo -e "${YELLOW} pyenv shell integration not found in $shell_rc${NC}" | ||
| echo -e "${YELLOW} Add this line to $shell_rc, then restart your terminal:${NC}" | ||
| echo ' eval "$(pyenv init -)"' | ||
| fi | ||
|
|
||
| # ripgrep (optional but recommended) | ||
| if command -v rg &> /dev/null; then | ||
| echo " ripgrep: $(rg --version | head -1)" | ||
| else | ||
| echo -e "${YELLOW} ripgrep: not found (optional, install with: brew install ripgrep)${NC}" | ||
| fi | ||
|
|
||
| # Homebrew (required to auto-install Go below) | ||
| if command -v brew &> /dev/null; then | ||
| echo " Homebrew: found" | ||
| else | ||
| echo -e "${RED} ERROR: Homebrew not found. TextPAIR needs it to install the Go toolchain (used to build compareNgrams).${NC}" | ||
| echo -e "${YELLOW} Install Homebrew first, then re-run this script:${NC}" | ||
| echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Go (required to build compareNgrams; the bundled binaries are Linux-only) | ||
| if command -v go &> /dev/null; then | ||
| echo " Go: $(go version)" | ||
| else | ||
| echo " Go: not found, installing with Homebrew..." | ||
| brew install go | ||
| echo " Go installed" | ||
| fi | ||
|
|
||
| # lz4 CLI (used to merge alignment result batches) | ||
| if command -v lz4 &> /dev/null; then | ||
| echo " lz4: found" | ||
| else | ||
| echo " lz4: not found, installing with Homebrew..." | ||
| brew install lz4 | ||
| fi | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # INSTALL | ||
| # ============================================================================= | ||
| install_textpair() { | ||
| echo "Installing TextPAIR..." | ||
|
|
||
| # Install textpair_llm first (local dependency) | ||
| if [ -d "lib/textpair_llm" ]; then | ||
| echo " Installing textpair_llm..." | ||
| "$PYTHON_BIN" -m pip install -e lib/textpair_llm/. --break-system-packages --quiet | ||
| fi | ||
|
|
||
| # Install main package | ||
| echo " Installing textpair..." | ||
| "$PYTHON_BIN" -m pip install -e lib/. --break-system-packages | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # SEED GLOBAL SETTINGS (user-level path, no sudo/root needed on macOS) | ||
| # ============================================================================= | ||
| setup_global_settings() { | ||
| local target="$HOME/.text-pair/global_settings.ini" | ||
| echo "Setting up $target..." | ||
|
|
||
| if [ -f "$target" ] || [ -f /etc/text-pair/global_settings.ini ]; then | ||
| echo " Already exists, leaving as-is" | ||
| else | ||
| mkdir -p "$HOME/.text-pair" | ||
| cp config/global_settings.ini "$target" | ||
| echo " Seeded from config/global_settings.ini" | ||
| echo -e "${YELLOW} Edit $target with your actual PostgreSQL credentials (only needed if you drop --skip_web_app)${NC}" | ||
| fi | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # SCAFFOLD STARTER CORPUS CONFIG | ||
| # ============================================================================= | ||
| scaffold_config() { | ||
| local target="my_config.ini" | ||
| echo "Setting up $target..." | ||
|
|
||
| if [ -f "$target" ]; then | ||
| echo " Already exists, leaving as-is ($target)" | ||
| else | ||
| cp config/sa_config.ini "$target" | ||
| echo " Seeded from config/sa_config.ini" | ||
| echo -e "${YELLOW} Edit $target and set source_file_path to your corpus directory before running textpair${NC}" | ||
| fi | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # INSTALL BINARY | ||
| # ============================================================================= | ||
| install_binary() { | ||
| echo "Installing compareNgrams binary..." | ||
|
|
||
| # The prebuilt binaries under lib/core/binary are Linux ELF executables (upstream only | ||
| # targets Linux) and cannot run on macOS at all, even when the CPU architecture matches. | ||
| # Build a native Mach-O binary from source instead. Go is guaranteed present at this point | ||
| # (installed by check_dependencies if it was missing). | ||
| echo " Building compareNgrams from source with Go..." | ||
| (cd lib/core/src/compareNgrams && go build -o /tmp/compareNgrams_build .) | ||
| sudo cp /tmp/compareNgrams_build /usr/local/bin/compareNgrams | ||
| rm -f /tmp/compareNgrams_build | ||
| sudo chmod +x /usr/local/bin/compareNgrams | ||
| echo " Built and installed to /usr/local/bin/compareNgrams" | ||
|
|
||
| echo "" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # VERIFY | ||
| # ============================================================================= | ||
| verify_install() { | ||
| echo "Verifying installation..." | ||
|
|
||
| # Check the pyenv-pinned interpreter directly, since `command -v textpair` only | ||
| # works if pyenv's shims are already wired into this shell's PATH (see the | ||
| # shell-integration note printed by check_dependencies). | ||
| local textpair_bin="$(pyenv root)/versions/$TARGET_PY_VERSION/bin/textpair" | ||
| if [ -x "$textpair_bin" ]; then | ||
| echo -e "${GREEN} textpair command found ($textpair_bin)${NC}" | ||
| else | ||
| echo -e "${RED} ERROR: textpair command not found at $textpair_bin${NC}" | ||
| exit 1 | ||
| fi | ||
| if ! command -v textpair &> /dev/null; then | ||
| echo -e "${YELLOW} Note: textpair isn't on PATH in this shell yet - see the pyenv shell integration note above${NC}" | ||
| fi | ||
|
|
||
| if command -v compareNgrams &> /dev/null; then | ||
| echo -e "${GREEN} compareNgrams binary found${NC}" | ||
| else | ||
| echo -e "${RED} ERROR: compareNgrams binary not found${NC}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "" | ||
| echo -e "${GREEN}Installation complete!${NC}" | ||
| echo "" | ||
| echo "Usage:" | ||
| echo " textpair --config=my_config.ini --skip_web_app --output_path=/tmp/textpair-out --workers=4 alignment_name" | ||
| echo "" | ||
| echo "Notes:" | ||
| echo " - Edit my_config.ini first and set source_file_path to your corpus directory" | ||
| echo " - ulimit is automatically increased on macOS (no manual fix needed)" | ||
| echo " - Use absolute paths in my_config.ini for source_file_path" | ||
| echo " - Avoid paths with spaces (copy corpus to /tmp if on iCloud)" | ||
| echo " - Input files should be TEI XML format" | ||
| echo " - Downloading a Spacy model (python -m spacy download <model>) is only needed if" | ||
| echo " you enable POS/entity filtering or spacy-based lemmatization in my_config.ini" | ||
| } | ||
|
|
||
| # ============================================================================= | ||
| # MAIN | ||
| # ============================================================================= | ||
| main() { | ||
| check_architecture | ||
| check_dependencies | ||
| install_textpair | ||
| patch_philologic_wc | ||
| setup_global_settings | ||
| scaffold_config | ||
| install_binary | ||
| verify_install | ||
| } | ||
|
|
||
| main "$@" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why hardcode to 3.11? The original install.sh lets you choose your Python executable which gives you more flexibility. I'm also planning on actually having uv install python directly (Python 3.12 most likely) to avoid any issues with Python versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its an issue with the way python is upgrading on different systems, Im game to try some alts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, the hardcode will break stuff