Skip to content

Commit

Permalink
linux
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanleung committed Sep 9, 2024
1 parent 7b4e1f4 commit 7d271eb
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 6 deletions.
140 changes: 140 additions & 0 deletions .github/workflows/mor-agents-build-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: MOR Agents Build Linux

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: Build with PyInstaller
run: |
pyinstaller --name="MORagents" --add-data "images/moragents.png:images" main.py
- name: Create Debian package
run: |
mkdir -p debian/DEBIAN
mkdir -p debian/usr/bin
mkdir -p debian/usr/share/applications
mkdir -p debian/usr/share/icons/hicolor/256x256/apps
cp -r dist/MORagents/* debian/usr/bin/
cp images/moragents.png debian/usr/share/icons/hicolor/256x256/apps/moragents.png
echo "[Desktop Entry]
Name=MORagents
Exec=/usr/bin/MORagents
Icon=moragents
Type=Application
Categories=Utility;" > debian/usr/share/applications/moragents.desktop
echo "Package: moragents
Version: 1.0
Section: utils
Priority: optional
Architecture: amd64
Maintainer: LachsBagel
Description: MORagents application
MORagents is a desktop application for AI agents." > debian/DEBIAN/control
dpkg-deb --build debian moragents.deb
- name: Create setup script
run: |
cat << EOF > moragents-setup.sh
#!/bin/bash
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to add current user to docker group
add_user_to_docker_group() {
local current_user=\$(whoami)
if [ "\$current_user" != "root" ]; then
echo -e "\${YELLOW}Adding current user to docker group...${NC}"
sudo usermod -aG docker "\$current_user"
echo -e "\${GREEN}User added to docker group. Please log out and log back in for changes to take effect.${NC}"
else
echo -e "\${YELLOW}Running as root. Skipping user addition to docker group.${NC}"
fi
}
# Install curl if not present
if ! command_exists curl; then
echo -e "${YELLOW}Installing curl...${NC}"
sudo apt-get update
sudo apt-get install -y curl
fi
# Install Docker if not present
if ! command_exists docker; then
echo -e "${YELLOW}Installing Docker...${NC}"
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
add_user_to_docker_group
sudo systemctl enable docker
sudo systemctl start docker
else
echo -e "${GREEN}Docker is already installed.${NC}"
fi
# Install Ollama
echo -e "${YELLOW}Installing Ollama...${NC}"
curl -fsSL https://ollama.com/install.sh | sh
# Start Ollama service
echo -e "${YELLOW}Starting Ollama service...${NC}"
nohup ollama serve > /dev/null 2>&1 &
# Pull Ollama models
echo -e "${YELLOW}Pulling Ollama models...${NC}"
ollama pull llama3
ollama pull nomic-embed-text
# Pull necessary Docker images
echo -e "${YELLOW}Pulling Docker images...${NC}"
sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.0.9
sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.0.9
# Start Docker containers
echo -e "${YELLOW}Starting Docker containers...${NC}"
sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.0.9
sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.0.9
echo -e "${GREEN}Setup complete!${NC}"
EOF
chmod +x moragents-setup.sh
- name: Upload Debian Package and Setup Script
uses: actions/upload-artifact@v4
with:
name: MORagentsSetup-Linux
path: |
moragents.deb
moragents-setup.sh
28 changes: 28 additions & 0 deletions build_assets/linux/install_moragents.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color

# Check if script is run as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run as root${NC}"
exit 1
fi

echo -e "${GREEN}Welcome to the MORagents installer for Linux${NC}"

# Install the .deb package
echo -e "${YELLOW}Installing MORagents...${NC}"
dpkg -i moragents.deb
apt-get install -f -y

# Run the setup script
echo -e "${YELLOW}Running MORagents setup...${NC}"
./moragents-setup.sh

echo -e "${GREEN}Installation complete!${NC}"
echo "You can now start MORagents from your application menu or by running 'MORagents' in the terminal."
echo -e "${YELLOW}NOTE: Please log out and log back in for Docker group changes to take effect.${NC}"
12 changes: 9 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
# Run as bundled executable if condition is met, else run as regular Python script
repo_root = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
elif os_name == "Linux":
raise RuntimeError(
f"MORagents needs Linux support! Would you like to help?\n"
f"https://github.com/MorpheusAIs/moragents/issues/27")
repo_root = os.path.dirname(__file__)
else:
raise RuntimeError(f"Unsupported OS: {os_name}")

class AgentDockerConfig:
MACOS_IMAGE_NAMES = [
Expand All @@ -23,13 +23,19 @@ class AgentDockerConfig:
"lachsbagel/moragents_dockers-nginx:amd64-0.0.9",
"lachsbagel/moragents_dockers-agents:amd64-0.0.9"
]
LINUX_IMAGE_NAMES = [
"lachsbagel/moragents_dockers-nginx:amd64-0.0.9",
"lachsbagel/moragents_dockers-agents:amd64-0.0.9"
]

@staticmethod
def get_current_image_names():
if os_name == "macOS":
return AgentDockerConfig.MACOS_IMAGE_NAMES
elif os_name == "Windows":
return AgentDockerConfig.WINDOWS_IMAGE_NAMES
elif os_name == "Linux":
return AgentDockerConfig.LINUX_IMAGE_NAMES
else:
raise RuntimeError(f"Unsupported OS: {os_name}")

Expand Down
Binary file added images/moragents.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from runtime_setup_macos import main as macos_setup
from runtime_setup_windows import main as windows_setup
from runtime_setup_linux import main as linux_setup
from utils.logger_config import setup_logger
from utils.host_utils import get_os_and_arch

Expand All @@ -21,9 +22,7 @@
elif os_name == "Windows":
windows_setup()
elif os_name == "Linux":
raise RuntimeError(
f"MORagents needs Linux support! Would you like to help?\n"
f"https://github.com/MorpheusAIs/moragents/issues/27")
linux_setup()

except Exception as e:
logging.critical(f"Error during Docker setup: {str(e)}")
Expand Down
167 changes: 167 additions & 0 deletions runtime_setup_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import os
import shutil
import subprocess

from utils.logger_config import setup_logger
from config import AgentDockerConfig, AgentDockerConfigDeprecate

logger = setup_logger(__name__)

def get_docker_path():
docker_paths = [
'/usr/bin/docker', # Common Linux path
'/usr/local/bin/docker', # Alternative Linux path
shutil.which('docker')
]
for docker_path in docker_paths:
if docker_path and os.path.exists(docker_path):
return docker_path

logger.error("Docker executable not found in PATH.")
return None

def check_docker_installed(docker_path):
try:
subprocess.run([docker_path, "--version"],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
logger.info(f"Docker was found at {docker_path}")
return True
except (subprocess.CalledProcessError, TypeError) as e:
logger.error(f"Error checking Docker installation: {str(e)}")
return False

def delete_docker_image(docker_path, image_name):
try:
# List all images
list_command = [docker_path, "images", "--format", "{{.Repository}}:{{.Tag}}"]
output = subprocess.check_output(list_command, universal_newlines=True)
images = output.strip().split("\n")

# Find the image with the specified name
if image_name in images:
# Remove the image
remove_command = [docker_path, "rmi", "-f", image_name]
subprocess.run(remove_command, check=True)
logger.info(f"Image '{image_name}' deleted successfully.")
else:
pass

except subprocess.CalledProcessError as e:
logger.warning(f"Error deleting image: {e}")

def list_containers_for_image(docker_path, image_name):
try:
output = subprocess.check_output(
[docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"])
containers = output.decode().strip().split("\n")
return [container for container in containers if container]
except subprocess.CalledProcessError as e:
logger.error(f"Failed to list containers for image '{image_name}': {e}")
return []

def remove_container(docker_path, container):
try:
subprocess.run([docker_path, "rm", "-f", container], check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as e:
logger.error(f"Failed to remove container '{container}': {e}")

def docker_image_present_on_host(docker_path, image_name):
try:
subprocess.run([docker_path, "inspect", image_name], check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
return True
except (subprocess.CalledProcessError, TypeError) as e:
return False

def remove_containers_for_image(docker_path, image_name):
containers = list_containers_for_image(docker_path, image_name)
for container in containers:
remove_container(docker_path, container)
logger.info(f"Removed container '{container}' for image '{image_name}'")

def remove_containers_by_name(docker_path, container_name):
try:
list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"]
output = subprocess.check_output(list_command, universal_newlines=True)
containers = output.strip().split("\n")

if container_name in containers:
remove_command = [docker_path, "rm", "-f", container_name]
subprocess.run(remove_command, check=True)
logger.info(f"Removed container '{container_name}'")
else:
logger.info(f"Container '{container_name}' not found")
except subprocess.CalledProcessError as e:
logger.error(f"Error removing container '{container_name}': {str(e)}")

def migration_remove_old_images(docker_path):
for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES:
if docker_image_present_on_host(docker_path, image_name):
delete_docker_image(docker_path, image_name)
logger.info(f"Deleted image '{image_name} from previous release")

def pull_docker_images(docker_path):
for image in AgentDockerConfig.get_current_image_names():
try:
subprocess.run([docker_path, "pull", image], check=True)
logger.info(f"Successfully pulled image: {image}")
except subprocess.CalledProcessError as e:
logger.error(f"Failed to pull image {image}: {e}")
raise

def start_ollama_server():
ollama_path = '/usr/local/bin/ollama' # This path might need to be adjusted for Linux

try:
# Start Ollama server
logger.info("Starting Ollama server...")
subprocess.Popen([ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
logger.info("Ollama server started successfully.")
except Exception as e:
logger.info("Failed to start Ollama server.")
logger.error(f"Failed to start Ollama server: {e}")

def docker_setup():
docker_path = get_docker_path()
logger.info(f"Docker path: {docker_path}")

if not check_docker_installed(docker_path):
logger.critical("Docker is not installed.")
raise RuntimeError("Docker is not installed.")

# Remove old images and containers
logger.info("Checking whether old images need removal.")
migration_remove_old_images(docker_path)

for image_name in AgentDockerConfig.get_current_image_names():
remove_containers_for_image(docker_path, image_name)

remove_containers_by_name(docker_path, "agents")
remove_containers_by_name(docker_path, "nginx")

# Pull the latest images
pull_docker_images(docker_path)

# Spin up Agent container
subprocess.run([
docker_path, "run", "-d", "--name", "agents",
"-p", "8080:5000", "--restart", "always",
"-v", "/var/lib/agents:/var/lib/agents", "-v", "/app/src:/app/src", # Adjusted volume paths for Linux
AgentDockerConfig.get_current_image_names()[1] # agents image
], check=True)

# Spin up Nginx container
subprocess.run([
docker_path, "run", "-d", "--name", "nginx", "-p", "3333:80",
AgentDockerConfig.get_current_image_names()[0] # nginx image
], check=True)

def main():
# main() called every time the app is opened (from main.py). Put all app open code here.
logger.info("Starting app...")
start_ollama_server()
docker_setup()

if __name__ == "__main__":
main()

0 comments on commit 7d271eb

Please sign in to comment.