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
recursive-include config *.json
recursive-include config *.md
recursive-include aisbf *.py
recursive-include aisbf *.sh
recursive-include aisbf/payments/wallet *.py
recursive-include templates *.html
recursive-include templates *.css
......
......@@ -23,18 +23,49 @@
PIDFILE="/tmp/aisbf.pid"
# Determine the correct share directory at runtime
# Check for system installation first (/usr/share/aisbf)
if [ -d "/usr/share/aisbf" ]; then
SHARE_DIR="/usr/share/aisbf"
VENV_DIR="/usr/share/aisbf/venv"
# Running as root - use /var/log/aisbf
# Determine the correct share directory at runtime.
# Use Python's sysconfig so we always match the prefix pip actually used
# (handles venvs, user installs, system installs, and custom prefixes).
_find_share_dir() {
python3 - <<'PYEOF'
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"
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"
fi
......
This diff is collapsed.
......@@ -24,44 +24,113 @@ Why did the programmer quit his job? Because he didn't get arrays!
import os
import sys
import subprocess
import sysconfig
import shutil
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():
"""Main entry point for the aisbf CLI - calls the aisbf.sh shell script"""
# Determine the correct script path at runtime
# Check for system installation first (/usr/local/share/aisbf/aisbf.sh)
script_path = Path("/usr/local/share/aisbf/aisbf.sh")
if not script_path.exists():
# Fall back to user installation (~/.local/share/aisbf/aisbf.sh)
script_path = Path.home() / ".local" / "share" / "aisbf" / "aisbf.sh"
# Check if the script exists
if not script_path.exists():
print(f"Error: AISBF script not found at {script_path}", file=sys.stderr)
print("Please ensure AISBF is properly installed.", 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)
share_dir = _find_share_dir()
if share_dir is None:
share_dir = _bootstrap_from_package()
if share_dir:
print(
"Warning: AISBF data files were not installed by pip to the expected\n"
f"location. Bootstrapped aisbf.sh to {share_dir}.\n"
"If the server fails to start, runtime files (main.py, templates/,\n"
"static/, config/, requirements.txt) may be missing from that directory.\n"
"Re-install from source to fix this:\n"
" pip install aisbf --no-binary aisbf",
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)
# Execute the shell script with all arguments passed to this Python script
script_path = share_dir / 'aisbf.sh'
try:
# Use subprocess to run the shell script
result = subprocess.run(
[str(script_path)] + sys.argv[1:],
check=False
)
# Exit with the same exit code as the shell script
result = subprocess.run([str(script_path)] + sys.argv[1:], check=False)
sys.exit(result.returncode)
except KeyboardInterrupt:
# Handle Ctrl-C gracefully
sys.exit(130) # Standard exit code for SIGINT (128 + 2)
sys.exit(130)
except Exception as e:
print(f"Error executing AISBF script: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
\ No newline at end of file
if __name__ == '__main__':
main()
......@@ -75,7 +75,9 @@ setup(
# install_requires=requirements,
include_package_data=True,
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/**/*"],
},
data_files=[
......@@ -116,6 +118,7 @@ setup(
'aisbf/analytics.py',
'aisbf/email_utils.py',
'aisbf/geolocation.py',
'aisbf/aisbf.sh',
]),
# aisbf.providers subpackage
('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