fix: reliable share-dir detection in cli.py and aisbf.sh; bundle aisbf.sh in package

cli.py:
- Replace hardcoded paths with sysconfig.get_path('data', scheme) covering all
  pip install modes (venv, posix_user, posix_prefix, posix_home, system)
- Fall back to legacy hardcoded paths for extra safety
- Last-resort bootstrap: copy bundled aisbf/aisbf.sh to ~/.local/share/aisbf/
  when data_files were not installed by pip (known pip/wheel limitation)
- Improved error message with all checked paths and reinstall instructions

aisbf.sh:
- Replace hardcoded /usr/share/aisbf check (wrong: setup.py installs to
  /usr/local/share/aisbf) with a Python sysconfig lookup that checks the
  actual pip data prefix across all known schemes
- Derive LOG_DIR from SHARE_DIR instead of duplicating the detection logic

Packaging:
- Add aisbf/aisbf.sh as package_data so it is always present in the installed
  aisbf package regardless of data_files extraction success
- Add recursive-include aisbf *.sh to MANIFEST.in for sdist
- Add aisbf/aisbf.sh to setup.py data_files share/aisbf/aisbf listing
parent 8c57873a
...@@ -8,6 +8,7 @@ include cli.py ...@@ -8,6 +8,7 @@ include cli.py
recursive-include config *.json recursive-include config *.json
recursive-include config *.md recursive-include config *.md
recursive-include aisbf *.py recursive-include aisbf *.py
recursive-include aisbf *.sh
recursive-include aisbf/payments/wallet *.py recursive-include aisbf/payments/wallet *.py
recursive-include templates *.html recursive-include templates *.html
recursive-include templates *.css recursive-include templates *.css
......
...@@ -23,18 +23,49 @@ ...@@ -23,18 +23,49 @@
PIDFILE="/tmp/aisbf.pid" PIDFILE="/tmp/aisbf.pid"
# Determine the correct share directory at runtime # Determine the correct share directory at runtime.
# Check for system installation first (/usr/share/aisbf) # Use Python's sysconfig so we always match the prefix pip actually used
if [ -d "/usr/share/aisbf" ]; then # (handles venvs, user installs, system installs, and custom prefixes).
SHARE_DIR="/usr/share/aisbf" _find_share_dir() {
VENV_DIR="/usr/share/aisbf/venv" python3 - <<'PYEOF'
# Running as root - use /var/log/aisbf import sysconfig, os, sys
def check(p):
return os.path.isfile(os.path.join(p, 'main.py'))
candidates = []
try:
candidates.append(os.path.join(sysconfig.get_path('data'), 'share', 'aisbf'))
except Exception:
pass
for scheme in ('posix_user', 'posix_prefix', 'posix_home'):
try:
candidates.append(os.path.join(sysconfig.get_path('data', scheme), 'share', 'aisbf'))
except Exception:
pass
candidates += ['/usr/local/share/aisbf', '/usr/share/aisbf']
seen = set()
for p in candidates:
if p not in seen:
seen.add(p)
if check(p):
print(p)
sys.exit(0)
# None found — default to user path so downstream errors are clear
import pathlib
print(pathlib.Path.home() / '.local' / 'share' / 'aisbf')
PYEOF
}
SHARE_DIR="$(_find_share_dir)"
VENV_DIR="$SHARE_DIR/venv"
# Choose log directory based on whether this is a system or user install
if [[ "$SHARE_DIR" == /usr/* ]]; then
LOG_DIR="/var/log/aisbf" LOG_DIR="/var/log/aisbf"
else else
# Fall back to user installation (~/.local/share/aisbf)
SHARE_DIR="$HOME/.local/share/aisbf"
VENV_DIR="$HOME/.local/share/aisbf/venv"
# Running as user - use ~/.local/var/log/aisbf
LOG_DIR="$HOME/.local/var/log/aisbf" LOG_DIR="$HOME/.local/var/log/aisbf"
fi fi
......
This diff is collapsed.
...@@ -24,44 +24,113 @@ Why did the programmer quit his job? Because he didn't get arrays! ...@@ -24,44 +24,113 @@ Why did the programmer quit his job? Because he didn't get arrays!
import os import os
import sys import sys
import subprocess import subprocess
import sysconfig
import shutil
from pathlib import Path from pathlib import Path
def _share_dir_candidates():
"""Return candidate share directories in priority order, deduplicated."""
seen = set()
result = []
def add(p):
key = str(p)
if key not in seen:
seen.add(key)
result.append(Path(p))
# sysconfig paths for the running Python interpreter — covers venvs,
# system installs, and user installs regardless of scheme.
add(Path(sysconfig.get_path('data')) / 'share' / 'aisbf')
for scheme in ('posix_user', 'posix_prefix', 'posix_home'):
try:
add(Path(sysconfig.get_path('data', scheme)) / 'share' / 'aisbf')
except Exception:
pass
# Legacy hardcoded fallbacks (setup.py installs here for system-wide)
add(Path('/usr/local/share/aisbf'))
add(Path('/usr/share/aisbf'))
add(Path.home() / '.local' / 'share' / 'aisbf')
return result
def _find_share_dir():
"""Return the first candidate that contains aisbf.sh, or None."""
for candidate in _share_dir_candidates():
if (candidate / 'aisbf.sh').exists():
return candidate
return None
def _bootstrap_from_package():
"""
Last-resort: copy aisbf.sh from the bundled package data to
~/.local/share/aisbf/ so the user can at least run the script.
The other runtime files (main.py, templates, …) still need to be
present — this only fixes the 'aisbf.sh not found' error.
"""
try:
import aisbf as _pkg
bundled = Path(_pkg.__file__).parent / 'aisbf.sh'
if not bundled.exists():
return None
dest_dir = Path.home() / '.local' / 'share' / 'aisbf'
dest_dir.mkdir(parents=True, exist_ok=True)
dest = dest_dir / 'aisbf.sh'
shutil.copy2(str(bundled), str(dest))
dest.chmod(dest.stat().st_mode | 0o755)
return dest_dir
except Exception:
return None
def main(): def main():
"""Main entry point for the aisbf CLI - calls the aisbf.sh shell script""" share_dir = _find_share_dir()
# Determine the correct script path at runtime if share_dir is None:
# Check for system installation first (/usr/local/share/aisbf/aisbf.sh) share_dir = _bootstrap_from_package()
script_path = Path("/usr/local/share/aisbf/aisbf.sh") if share_dir:
if not script_path.exists(): print(
# Fall back to user installation (~/.local/share/aisbf/aisbf.sh) "Warning: AISBF data files were not installed by pip to the expected\n"
script_path = Path.home() / ".local" / "share" / "aisbf" / "aisbf.sh" f"location. Bootstrapped aisbf.sh to {share_dir}.\n"
"If the server fails to start, runtime files (main.py, templates/,\n"
# Check if the script exists "static/, config/, requirements.txt) may be missing from that directory.\n"
if not script_path.exists(): "Re-install from source to fix this:\n"
print(f"Error: AISBF script not found at {script_path}", file=sys.stderr) " pip install aisbf --no-binary aisbf",
print("Please ensure AISBF is properly installed.", file=sys.stderr) file=sys.stderr,
print(f"Expected locations:", file=sys.stderr) )
print(f" - /usr/local/share/aisbf/aisbf.sh (system-wide)", file=sys.stderr)
print(f" - ~/.local/share/aisbf/aisbf.sh (user installation)", file=sys.stderr) if share_dir is None or not (share_dir / 'aisbf.sh').exists():
checked = '\n'.join(f' - {p}' for p in _share_dir_candidates())
print(
"Error: AISBF share directory not found.\n"
"The data files may not have been installed correctly from the wheel.\n\n"
"Checked locations:\n"
f"{checked}\n\n"
"To fix, reinstall from source so pip can place files correctly:\n"
" pip install aisbf --no-binary aisbf\n"
"Or install system-wide as root:\n"
" sudo pip install aisbf",
file=sys.stderr,
)
sys.exit(1) sys.exit(1)
# Execute the shell script with all arguments passed to this Python script script_path = share_dir / 'aisbf.sh'
try: try:
# Use subprocess to run the shell script result = subprocess.run([str(script_path)] + sys.argv[1:], check=False)
result = subprocess.run(
[str(script_path)] + sys.argv[1:],
check=False
)
# Exit with the same exit code as the shell script
sys.exit(result.returncode) sys.exit(result.returncode)
except KeyboardInterrupt: except KeyboardInterrupt:
# Handle Ctrl-C gracefully sys.exit(130)
sys.exit(130) # Standard exit code for SIGINT (128 + 2)
except Exception as e: except Exception as e:
print(f"Error executing AISBF script: {e}", file=sys.stderr) print(f"Error executing AISBF script: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == '__main__':
main() main()
...@@ -75,7 +75,9 @@ setup( ...@@ -75,7 +75,9 @@ setup(
# install_requires=requirements, # install_requires=requirements,
include_package_data=True, include_package_data=True,
package_data={ package_data={
"aisbf": ["*.json"], # aisbf.sh bundled inside the package so cli.py can bootstrap the share
# directory even when pip fails to install data_files from the wheel.
"aisbf": ["*.json", "aisbf.sh"],
"": ["templates/**/*.html", "templates/**/*.css", "templates/**/*.js", "static/**/*"], "": ["templates/**/*.html", "templates/**/*.css", "templates/**/*.js", "static/**/*"],
}, },
data_files=[ data_files=[
...@@ -116,6 +118,7 @@ setup( ...@@ -116,6 +118,7 @@ setup(
'aisbf/analytics.py', 'aisbf/analytics.py',
'aisbf/email_utils.py', 'aisbf/email_utils.py',
'aisbf/geolocation.py', 'aisbf/geolocation.py',
'aisbf/aisbf.sh',
]), ]),
# aisbf.providers subpackage # aisbf.providers subpackage
('share/aisbf/aisbf/providers', [ ('share/aisbf/aisbf/providers', [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment