Skip to content

Commit

Permalink
Merge pull request #204 from SelfhostedPro/develop
Browse files Browse the repository at this point in the history
Alpha 5 (v0.0.5-alpha)
  • Loading branch information
SelfhostedPro authored Oct 31, 2020
2 parents a0ab6f7 + b4a8921 commit 58ad2bc
Show file tree
Hide file tree
Showing 61 changed files with 2,400 additions and 933 deletions.
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
![logo](https://raw.githubusercontent.com/SelfhostedPro/Yacht/master/readme_media/Yacht_logo_1_dark.png "templates")

[![Docker Hub Pulls](https://img.shields.io/docker/pulls/selfhostedpro/yacht?color=%2341B883&label=Docker%20Pulls&logo=docker&logoColor=%23403d3d&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Docker Image Size](https://img.shields.io/docker/image-size/selfhostedpro/yacht/vue?color=%2341B883&label=Image%20Size&logo=docker&logoColor=%23403d3d&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Layers](https://img.shields.io/microbadger/layers/selfhostedpro/yacht?color=%2341B883&label=Layers&logo=docker&logoColor=%23403d3d&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Docker Hub Pulls](https://img.shields.io/docker/pulls/selfhostedpro/yacht?color=%2341B883&label=Docker%20Pulls&logo=docker&logoColor=%2341B883&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Docker Image Size](https://img.shields.io/docker/image-size/selfhostedpro/yacht/vue?color=%2341B883&label=Image%20Size&logo=docker&logoColor=%2341B883&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Layers](https://img.shields.io/microbadger/layers/selfhostedpro/yacht?color=%2341B883&label=Layers&logo=docker&logoColor=%2341B883&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht)
[![Open Collective](https://img.shields.io/opencollective/all/selfhostedpro.svg?color=%2341B883&logoColor=%2341B883&style=for-the-badge&label=Supporters&logo=open%20collective)](https://opencollective.com/selfhostedpro "please consider helping me by either donating or contributing")

## Yacht
Yacht is a container management UI with a focus on templates and 1-click deployments.
Expand All @@ -19,6 +20,10 @@ Installation documentation can be found [here](https://yacht.sh/Installation/yac

Check out the getting started guide if this is the first time you've used Yacht: https://yacht.sh/Installation/gettingstarted/

**Yacht is also available via the DigitalOcean marketplace:**

[![DigitalOcean](https://raw.githubusercontent.com/SelfhostedPro/Yacht/develop/readme_media/do-btn-blue.svg)](https://marketplace.digitalocean.com/apps/yacht?refcode=b68dee19dbf6)

## Features So Far:
* Vuetify UI Framework
* Basic Container Management
Expand Down Expand Up @@ -51,5 +56,17 @@ If you're on arm and graphs aren't showing up add the following to your cmdline.
```
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
```
## Supported Environment Variables
You can utilize the following environment variables in Yacht. None of them are manditory.

| Variable | Description |
| ------------- | ------------- |
| PUID | Set userid that the container will run as. |
| PGID | Set groupid that the container will run as. |
| SECRET_KEY | Setting this to a random string ensures you won't be logged out in between reboots of Yacht. |
| ADMIN_EMAIL | This sets the email for the default Yacht user. |
| DISABLE_AUTH | This disables authentication on the backend of Yacht. It's not recommended unless you're using something like Authelia to manage authentication. |
| DATABASE_URL | If you want to have Yacht use a database like SQL instead of the built in sqlite on you can put that info here in the following format: `postgresql://user:password@postgresserver/db` |
| COMPOSE_DIR | This is the path inside the container which contains your folders that have docker compose projects. (*compose tag only*)|
## License
[MIT License](LICENSE.md)
4 changes: 3 additions & 1 deletion backend/api/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .apps import *
from .apps import *
from .compose import *
from .resources import *
39 changes: 28 additions & 11 deletions backend/api/actions/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ..db import models, schemas
from ..utils import *
from ..utils import check_updates as _update_check
from docker.errors import APIError

from datetime import datetime
import time
Expand All @@ -24,6 +25,7 @@ def get_running_apps():

return apps_list


def check_app_update(app_name):
dclient = docker.from_env()
try:
Expand All @@ -32,7 +34,7 @@ def check_app_update(app_name):
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
)

if app.attrs["Config"]["Image"]:
if _update_check(app.attrs["Config"]["Image"]):
app.attrs.update(conv2dict("isUpdatable", True))
Expand All @@ -41,10 +43,16 @@ def check_app_update(app_name):
app.attrs.update(conv2dict("short_id", app.short_id))
return app.attrs


def get_apps():
apps_list = []
dclient = docker.from_env()
apps = dclient.containers.list(all=True)
try:
apps = dclient.containers.list(all=True)
except Exception as exc:
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
)
for app in apps:
attrs = app.attrs

Expand Down Expand Up @@ -111,7 +119,8 @@ def deploy_app(template: schemas.DeployForm):
conv_sysctls2data(template.sysctls),
conv_caps2data(template.cap_add),
)

except HTTPException as exc:
raise HTTPException(status_code=exc.status_code, detail=exc.detail)
except Exception as exc:
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
Expand Down Expand Up @@ -191,12 +200,16 @@ def app_action(app_name, action):
try:
_action(force=True)
except Exception as exc:
err = f"{exc}"
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
)
else:
try:
_action()
except Exception as exc:
err = exc.explination
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
)
apps_list = get_apps()
return apps_list

Expand All @@ -221,7 +234,7 @@ def app_update(app_name):
try:
updater = dclient.containers.run(
image="containrrr/watchtower:latest",
command="--run-once " + old.name,
command="--cleanup --run-once " + old.name,
remove=True,
detach=True,
volumes=volumes,
Expand Down Expand Up @@ -263,12 +276,12 @@ def update_self():
print("**** Updating " + yacht.name + "****")
updater = dclient.containers.run(
image="containrrr/watchtower:latest",
command="--run-once " + yacht.name,
command="--cleanup --run-once " + yacht.name,
remove=True,
detach=True,
volumes=volumes,
)
result = updater.wait(timeout=120)
result = updater
print(result)
time.sleep(1)
return result
Expand All @@ -284,14 +297,18 @@ def check_self_update():
yacht = dclient.containers.get(yacht_id)
except Exception as exc:
print(exc)
if exc.response.status_code == 404:
if hasattr(exc, 'response') and exc.response.status_code == 404:
raise HTTPException(
status_code=exc.response.status_code,
detail="Unable to get Yacht container ID",
)
else:
elif hasattr(exc, 'response'):
raise HTTPException(
status_code=exc.response.status_code, detail=exc.explanation
)
else:
raise HTTPException(
status_code=400, detail=exc.args
)

return check_updates(yacht.image.tags[0])
return _update_check(yacht.image.tags[0])
199 changes: 199 additions & 0 deletions backend/api/actions/compose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
from fastapi import HTTPException
from sh import docker_compose
import os
import yaml
import pathlib

from ..settings import Settings
from ..utils.compose import find_yml_files, get_readme_file, get_logo_file

settings = Settings()


def compose_action(name, action):
files = find_yml_files(settings.COMPOSE_DIR)
compose = get_compose(name)
if action == "up":
try:
_action = docker_compose(
"-f",
compose["path"],
action,
"-d",
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
elif action == "create":
try:
_action = docker_compose(
"-f",
compose["path"],
"up",
"--no-start",
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
else:
try:
_action = docker_compose(
"-f", compose["path"], action, _cwd=os.path.dirname(compose["path"])
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
if _action.stdout.decode("UTF-8").rstrip():
output = _action.stdout.decode("UTF-8").rstrip()
elif _action.stderr.decode("UTF-8").rstrip():
output = _action.stderr.decode("UTF-8").rstrip()
else:
output = "No Output"
print(f"""Project {compose['name']} {action} successful.""")
print(f"""Output: """)
print(output)
return get_compose_projects()


def compose_app_action(
name,
action,
app,
):

files = find_yml_files(settings.COMPOSE_DIR)
compose = get_compose(name)
print("docker-compose -f " + compose["path"] + " " + action + " " + app)
if action == "up":
try:
_action = docker_compose(
"-f",
compose["path"],
"up",
"-d",
app,
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
elif action == "create":
try:
_action = docker_compose(
"-f",
compose["path"],
"up",
"--no-start",
app,
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
elif action == "rm":
try:
_action = docker_compose(
"-f",
compose["path"],
"rm",
"--force",
"--stop",
app,
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
else:
try:
_action = docker_compose(
"-f",
compose["path"],
action,
app,
_cwd=os.path.dirname(compose["path"]),
)
except Exception as exc:
raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip())
if _action.stdout.decode("UTF-8").rstrip():
output = _action.stdout.decode("UTF-8").rstrip()
elif _action.stderr.decode("UTF-8").rstrip():
output = _action.stderr.decode("UTF-8").rstrip()
else:
output = "No Output"
print(f"""Project {compose['name']} App {name} {action} successful.""")
print(f"""Output: """)
print(output)
return get_compose_projects()


def get_compose_projects():
files = find_yml_files(settings.COMPOSE_DIR)

projects = []
for project, file in files.items():
volumes = []
networks = []
services = {}
compose = open(file)
loaded_compose = yaml.load(compose, Loader=yaml.SafeLoader)
if loaded_compose:
if loaded_compose.get("volumes"):
for volume in loaded_compose.get("volumes"):
volumes.append(volume)
if loaded_compose.get("networks"):
for network in loaded_compose.get("networks"):
networks.append(network)
for service in loaded_compose.get("services"):
services[service] = loaded_compose["services"][service]
_project = {
"name": project,
"path": file,
"version": loaded_compose["version"],
"services": services,
"volumes": volumes,
"networks": networks,
}
projects.append(_project)
else:
print("ERROR: " + file + " is invalid or empty!")
return projects


def get_compose(name):
try:
files = find_yml_files(settings.COMPOSE_DIR + name)
except Exception as exc:
print(exc)
for project, file in files.items():
if name == project:
networks = []
volumes = []
services = {}
compose = open(file)
loaded_compose = yaml.load(compose, Loader=yaml.SafeLoader)
if loaded_compose.get("volumes"):
for volume in loaded_compose.get("volumes"):
volumes.append(volume)
if loaded_compose.get("networks"):
for network in loaded_compose.get("networks"):
networks.append(network)
for service in loaded_compose.get("services"):
services[service] = loaded_compose["services"][service]
compose_object = {
"name": project,
"path": file,
"version": loaded_compose["version"],
"services": services,
"volumes": volumes,
"networks": networks,
}
return compose_object
else:
raise HTTPException(404, "Project " + name + " not found")


def write_compose(compose):
print(compose)
pathlib.Path("config/compose/" + compose.name).mkdir(parents=True)
f = open("config/compose/" + compose.name + "/docker-compose.yml", "a")
f.write(compose.content)
f.close()

return get_compose(name=compose.name)
2 changes: 1 addition & 1 deletion backend/api/actions/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,4 @@ def prune_resources(resource):
deleted_resource = action.prune(filters={"dangling": False})
else:
deleted_resource = action.prune()
return deleted_resource
return deleted_resource
7 changes: 1 addition & 6 deletions backend/api/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ class UserTable(Base, SQLAlchemyBaseUserTable):
app = FastAPI()

fastapi_users = FastAPIUsers(
user_db,
auth_backends,
User,
UserCreate,
UserUpdate,
UserDB,
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB,
)


Expand Down
Loading

0 comments on commit 58ad2bc

Please sign in to comment.