Skip to content

Commit

Permalink
feat: Add more skeletons
Browse files Browse the repository at this point in the history
  • Loading branch information
achimnol committed Nov 6, 2023
1 parent 96bae6b commit a002d74
Show file tree
Hide file tree
Showing 9 changed files with 449 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/ai/backend/install/app.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ ModeMenu Label {
.log {
height: 30;
width: 1fr;
padding: 1 1;
padding: 1 2;
background: $panel-darken-3;
}
53 changes: 40 additions & 13 deletions src/ai/backend/install/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
Static,
)

from ai.backend.install import __version__
from ai.backend.plugin.entrypoint import find_build_root

from . import __version__
from .common import detect_os
from .context import Context, current_app, current_log
from .dev import bootstrap_pants, install_editable_webui, install_git_hooks, install_git_lfs
from .docker import check_docker, get_preferred_pants_local_exec_root
from .types import InstallModes

top_tasks: WeakSet[asyncio.Task] = WeakSet()
Expand All @@ -42,15 +46,31 @@ async def begin_install(self) -> None:
top_tasks.add(asyncio.create_task(self.install()))

async def install(self) -> None:
log: RichLog = cast(RichLog, self.query_one(".log"))
_log: RichLog = cast(RichLog, self.query_one(".log"))
_log_token = current_log.set(_log)
try:
for tick in range(3):
await asyncio.sleep(1)
log.write(Text.from_markup(f"[gold1](dev)[/] something is going: {tick}"))
# prerequisites
await detect_os()
await install_git_lfs()
await install_git_hooks()
await check_docker()
local_execution_root_dir = await get_preferred_pants_local_exec_root()
await bootstrap_pants(local_execution_root_dir)
# install
await install_editable_webui()
# TODO: install agent-watcher
# TODO: install storage-agent
# TODO: install storage-watcher
ctx = Context()
await ctx.install_halfstack(ha_setup=False)
# configure
# TODO: ...
except asyncio.CancelledError:
log.write(Text.from_markup("[red]Interrupted!"))
_log.write(Text.from_markup("[red]Interrupted!"))
await asyncio.sleep(1)
raise
finally:
current_log.reset(_log_token)


class PackageSetup(Static):
Expand All @@ -68,9 +88,15 @@ async def begin_install(self) -> None:
async def install(self) -> None:
log: RichLog = cast(RichLog, self.query_one(".log"))
try:
for tick in range(3):
await asyncio.sleep(1)
log.write(Text.from_markup(f"[gold1](pkg)[/] something is going: {tick}"))
# prerequisites
await detect_os()
await check_docker()
# install
# TODO: download packages
ctx = Context()
await ctx.install_halfstack(ha_setup=False)
# configure
# TODO: ...
except asyncio.CancelledError:
log.write(Text.from_markup("[red]Interrupted!"))
await asyncio.sleep(1)
Expand Down Expand Up @@ -148,8 +174,8 @@ def start_package_mode(self) -> None:

class InstallerApp(App):
BINDINGS = [
Binding("q", "quit", "Quit the installer"),
Binding("ctrl+c", "quit", "Quit the installer", show=False, priority=True),
Binding("q", "shutdown", "Quit the installer"),
Binding("ctrl+c", "shutdown", "Quit the installer", show=False, priority=True),
]
CSS_PATH = "app.tcss"

Expand All @@ -170,7 +196,7 @@ def on_mount(self) -> None:
header.tall = True
self.title = "Backend.AI Installer"

async def action_quit(self):
async def action_shutdown(self, message: str | None = None, exit_code: int = 0) -> None:
for t in {*top_tasks}:
if t.done():
continue
Expand All @@ -179,7 +205,7 @@ async def action_quit(self):
await t
except asyncio.CancelledError:
pass
self.exit()
self.exit(return_code=exit_code, message=message)


@click.command(
Expand All @@ -202,4 +228,5 @@ def main(
) -> None:
"""The installer"""
app = InstallerApp(mode)
current_app.set(app)
app.run()
76 changes: 58 additions & 18 deletions src/ai/backend/install/common.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
from textual.widgets import RichLog


async def install_halfstack(log: RichLog, ha_setup: bool) -> None:
pass


async def load_fixtures(log: RichLog) -> None:
pass


async def detect_cuda(log: RichLog) -> None:
pass


async def populate_bundled_images(log: RichLog) -> None:
from .context import current_os
from .types import OSInfo


async def detect_os():
"""
# Detect distribution
KNOWN_DISTRO="(Debian|Ubuntu|RedHat|CentOS|openSUSE|Amazon|Arista|SUSE)"
DISTRO=$(lsb_release -d 2>/dev/null | grep -Eo $KNOWN_DISTRO || grep -Eo $KNOWN_DISTRO /etc/issue 2>/dev/null || uname -s)
if [ $DISTRO = "Darwin" ]; then
DISTRO="Darwin"
STANDALONE_PYTHON_PLATFORM="apple-darwin"
elif [ -f /etc/debian_version -o "$DISTRO" == "Debian" -o "$DISTRO" == "Ubuntu" ]; then
DISTRO="Debian"
STANDALONE_PYTHON_PLATFORM="unknown-linux-gnu"
elif [ -f /etc/redhat-release -o "$DISTRO" == "RedHat" -o "$DISTRO" == "CentOS" -o "$DISTRO" == "Amazon" ]; then
DISTRO="RedHat"
STANDALONE_PYTHON_PLATFORM="unknown-linux-gnu"
elif [ -f /etc/system-release -o "$DISTRO" == "Amazon" ]; then
DISTRO="RedHat"
STANDALONE_PYTHON_PLATFORM="unknown-linux-gnu"
elif [ -f /usr/lib/os-release -o "$DISTRO" == "SUSE" ]; then
DISTRO="SUSE"
STANDALONE_PYTHON_PLATFORM="unknown-linux-gnu"
else
show_error "Sorry, your host OS distribution is not supported by this script."
show_info "Please send us a pull request or file an issue to support your environment!"
exit 1
fi
"""
current_os.set(
OSInfo(
platform="",
distro="",
)
)


async def detect_cuda() -> None:
pass


async def pull_image(log: RichLog) -> None:
pass
async def check_docker_desktop_mount() -> None:
"""
echo "validating Docker Desktop mount permissions..."
docker pull alpine:3.8 > /dev/null
docker run --rm -v "$HOME/.pyenv:/root/vol" alpine:3.8 ls /root/vol > /dev/null 2>&1
if [ $? -ne 0 ]; then
# backend.ai-krunner-DISTRO pkgs are installed in pyenv's virtualenv,
# so ~/.pyenv must be mountable.
show_error "You must allow mount of '$HOME/.pyenv' in the File Sharing preference of the Docker Desktop app."
exit 1
fi
docker run --rm -v "$ROOT_PATH:/root/vol" alpine:3.8 ls /root/vol > /dev/null 2>&1
if [ $? -ne 0 ]; then
show_error "You must allow mount of '$ROOT_PATH' in the File Sharing preference of the Docker Desktop app."
exit 1
fi
echo "${REWRITELN}validating Docker Desktop mount permissions: ok"
"""
27 changes: 27 additions & 0 deletions src/ai/backend/install/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
from __future__ import annotations

import enum
from contextvars import ContextVar
from typing import TYPE_CHECKING

from textual.widgets import RichLog

from .types import OSInfo

if TYPE_CHECKING:
from .cli import InstallerApp

current_log: ContextVar[RichLog] = ContextVar("current_log")
current_app: ContextVar[InstallerApp] = ContextVar("current_app")
current_os: ContextVar[OSInfo] = ContextVar("current_os")


class PostGuide(enum.Enum):
Expand All @@ -17,6 +32,12 @@ def add_post_guide(self, guide: PostGuide) -> None:
def show_post_guide(self) -> None:
pass

async def install_halfstack(self, ha_setup: bool) -> None:
pass

async def load_fixtures(self) -> None:
pass

async def configure_services(self) -> None:
pass

Expand All @@ -37,3 +58,9 @@ async def configure_webui(self) -> None:

async def dump_etcd_config(self) -> None:
pass

async def populate_bundled_images(self) -> None:
pass

async def pull_image(self) -> None:
pass
150 changes: 135 additions & 15 deletions src/ai/backend/install/dev.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,145 @@
from textual.widgets import RichLog
from rich.text import Text

from .context import current_log

async def install_git_lfs(log: RichLog) -> None:
pass

async def install_git_lfs() -> None:
log = current_log.get()
log.write(Text.from_markup("[dim green]Installing Git LFS"))
"""
case $DISTRO in
Debian)
$sudo apt-get install -y git-lfs
;;
RedHat)
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.rpm.sh | $sudo bash
$sudo yum install -y git-lfs
;;
SUSE)
$sudo zypper install -y git-lfs
;;
Darwin)
brew install git-lfs
;;
esac
git lfs install
"""

async def install_git_hooks(log: RichLog) -> None:
pass

async def install_git_hooks() -> None:
log = current_log.get()
log.write(Text.from_markup("[dim green]Installing Git hooks"))
"""
local magic_str="monorepo standard pre-commit hook"
if [ -f .git/hooks/pre-commit ]; then
grep -Fq "$magic_str" .git/hooks/pre-commit
if [ $? -eq 0 ]; then
:
else
echo "" >> .git/hooks/pre-commit
cat scripts/pre-commit >> .git/hooks/pre-commit
fi
else
cp scripts/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
fi
local magic_str="monorepo standard pre-push hook"
if [ -f .git/hooks/pre-push ]; then
grep -Fq "$magic_str" .git/hooks/pre-push
if [ $? -eq 0 ]; then
:
else
echo "" >> .git/hooks/pre-push
cat scripts/pre-push >> .git/hooks/pre-push
fi
else
cp scripts/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push
fi
"""

async def bootstrap_pants(log: RichLog) -> None:
pass

async def bootstrap_pants(local_execution_root_dir: str) -> None:
log = current_log.get()
log.write(Text.from_markup("[dim green]Bootstrapping Pantsbuild"))
log.write(f"local_execution_root_dir = {local_execution_root_dir}")
"""
pants_local_exec_root=$($docker_sudo $bpython scripts/check-docker.py --get-preferred-pants-local-exec-root)
mkdir -p "$pants_local_exec_root"
$bpython scripts/tomltool.py -f .pants.rc set 'GLOBAL.local_execution_root_dir' "$pants_local_exec_root"
set +e
if command -v pants &> /dev/null ; then
echo "Pants system command is already installed."
else
case $DISTRO in
Darwin)
brew install pantsbuild/tap/pants
;;
*)
curl --proto '=https' --tlsv1.2 -fsSL https://static.pantsbuild.org/setup/get-pants.sh > /tmp/get-pants.sh
bash /tmp/get-pants.sh
if ! command -v pants &> /dev/null ; then
$sudo ln -s $HOME/bin/pants /usr/local/bin/pants
show_note "Symlinked $HOME/bin/pants from /usr/local/bin/pants as we could not find it from PATH..."
fi
;;
esac
fi
pants version
if [ $? -eq 1 ]; then
# If we can't find the prebuilt Pants package, then try the source installation.
show_error "Cannot proceed the installation because Pants is not available for your platform!"
exit 1
fi
set -e
"""

async def install_editable_webui(log: RichLog) -> None:
pass
"""
pants export \
--resolve=python-default \
--resolve=python-kernel \
--resolve=pants-plugins \
--resolve=towncrier \
--resolve=ruff \
--resolve=mypy \
--resolve=black
"""


async def check_docker(log: RichLog) -> None:
pass


async def check_docker_compose(log: RichLog) -> None:
pass
async def install_editable_webui() -> None:
log = current_log.get()
log.write(Text.from_markup("[dim green]Installing the editable version of webui"))
"""
if ! command -v node &> /dev/null; then
install_node
fi
show_info "Installing editable version of Web UI..."
if [ -d "./src/ai/backend/webui" ]; then
echo "src/ai/backend/webui already exists, so running 'make clean' on it..."
cd src/ai/backend/webui
make clean
else
git clone https://github.com/lablup/backend.ai-webui ./src/ai/backend/webui
cd src/ai/backend/webui
cp configs/default.toml config.toml
local site_name=$(basename $(pwd))
# The debug mode here is only for 'hard-core' debugging scenarios -- it changes lots of behaviors.
# (e.g., separate debugging of Electron's renderer and main threads)
sed_inplace "s@debug = true@debug = false@" config.toml
# The webserver endpoint to use in the session mode.
sed_inplace "s@#[[:space:]]*apiEndpoint =.*@apiEndpoint = "'"'"http://127.0.0.1:${WEBSERVER_PORT}"'"@' config.toml
sed_inplace "s@#[[:space:]]*apiEndpointText =.*@apiEndpointText = "'"'"${site_name}"'"@' config.toml
# webServerURL lets the electron app use the web UI contents from the server.
# The server may be either a `npm run server:d` instance or a `./py -m ai.backend.web.server` instance.
# In the former case, you may live-edit the webui sources while running them in the electron app.
sed_inplace "s@webServerURL =.*@webServerURL = "'"'"http://127.0.0.1:${WEBSERVER_PORT}"'"@' config.toml
sed_inplace "s@proxyURL =.*@proxyURL = "'"'"http://127.0.0.1:${WSPROXY_PORT}"'"@' config.toml
echo "PROXYLISTENIP=0.0.0.0" >> .env
echo "PROXYBASEHOST=localhost" >> .env
echo "PROXYBASEPORT=${WSPROXY_PORT}" >> .env
fi
npm i
make compile
make compile_wsproxy
cd ../../../..
"""
Loading

0 comments on commit a002d74

Please sign in to comment.